pax_global_header00006660000000000000000000000064130761776750014535gustar00rootroot0000000000000052 comment=5aa18819b6a7b44751f8a858bd232d1c07b67985 .gitignore000066400000000000000000000000061307617767500130650ustar00rootroot00000000000000build .travis.yml000066400000000000000000000035611307617767500132170ustar00rootroot00000000000000language: c compiler: - gcc - clang cache: directories: - $HOME/OpenBlasInstall sudo: false env: - TORCH_LUA_VERSION=LUAJIT21 - TORCH_LUA_VERSION=LUA51 - TORCH_LUA_VERSION=LUA52 addons: apt: packages: - cmake - gfortran - gcc-multilib - gfortran-multilib - liblapack-dev - build-essential - gcc - g++ - curl - cmake - libreadline-dev - git-core - libqt4-core - libqt4-gui - libqt4-dev - libjpeg-dev - libpng-dev - ncurses-dev - imagemagick - libzmq3-dev - gfortran - unzip - gnuplot - gnuplot-x11 - libgraphicsmagick1-dev - imagemagick before_script: - export ROOT_TRAVIS_DIR=$(pwd) - export INSTALL_PREFIX=~/torch/install - ls $HOME/OpenBlasInstall/lib || (cd /tmp/ && git clone https://github.com/xianyi/OpenBLAS.git -b master && cd OpenBLAS && (make NO_AFFINITY=1 -j$(getconf _NPROCESSORS_ONLN) 2>/dev/null >/dev/null) && make PREFIX=$HOME/OpenBlasInstall install) - git clone https://github.com/torch/distro.git ~/torch --recursive - cd ~/torch && git submodule update --init --recursive - mkdir build && cd build - export CMAKE_LIBRARY_PATH=$HOME/OpenBlasInstall/include:$HOME/OpenBlasInstall/lib:$CMAKE_LIBRARY_PATH - cmake .. -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON - make && make install - cd $ROOT_TRAVIS_DIR - export LD_LIBRARY_PATH=${INSTALL_PREFIX}/lib:$LD_LIBRARY_PATH script: - ${INSTALL_PREFIX}/bin/luarocks make - export PATH=${INSTALL_PREFIX}/bin:$PATH - export TESTLUA=$(which luajit lua | head -n 1) - ${INSTALL_PREFIX}/bin/luarocks install luaffi - ${TESTLUA} -limage -e "print('image loaded succesfully')" - cd test - ${INSTALL_PREFIX}/bin/luarocks install graphicsmagick - ${TESTLUA} ./test_rotate.lua - ${TESTLUA} -limage -e "t=image.test(); if t.errors[1] then os.exit(1) end" CMakeLists.txt000066400000000000000000000064501307617767500136460ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR) CMAKE_POLICY(VERSION 2.6) FIND_PACKAGE(Torch REQUIRED) FIND_PACKAGE(JPEG) FIND_PACKAGE(PNG) # OpenMP support? SET(WITH_OPENMP ON CACHE BOOL "OpenMP support if available?") IF (APPLE AND CMAKE_COMPILER_IS_GNUCC) EXEC_PROGRAM (uname ARGS -v OUTPUT_VARIABLE DARWIN_VERSION) STRING (REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION}) MESSAGE (STATUS "MAC OS Darwin Version: ${DARWIN_VERSION}") IF (DARWIN_VERSION GREATER 9) SET(APPLE_OPENMP_SUCKS 1) ENDIF (DARWIN_VERSION GREATER 9) EXECUTE_PROCESS (COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) IF (APPLE_OPENMP_SUCKS AND GCC_VERSION VERSION_LESS 4.6.2) MESSAGE(STATUS "Warning: Disabling OpenMP (unstable with this version of GCC)") MESSAGE(STATUS " Install GCC >= 4.6.2 or change your OS to enable OpenMP") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unknown-pragmas") SET(WITH_OPENMP OFF CACHE BOOL "OpenMP support if available?" FORCE) ENDIF () ENDIF () IF (WITH_OPENMP) FIND_PACKAGE(OpenMP) IF(OPENMP_FOUND) MESSAGE(STATUS "Compiling with OpenMP support") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") ENDIF(OPENMP_FOUND) ENDIF (WITH_OPENMP) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") SET(src ppm.c) ADD_TORCH_PACKAGE(ppm "${src}" "${luasrc}" "Image Processing") TARGET_LINK_LIBRARIES(ppm luaT TH) IF(LUALIB) TARGET_LINK_LIBRARIES(ppm ${LUALIB}) ENDIF() if (JPEG_FOUND) SET(src jpeg.c) include_directories (${JPEG_INCLUDE_DIR}) SET(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}") SET(CMAKE_REQUIRED_LIBRARIES "${JPEG_LIBRARY}") INCLUDE(CheckSymbolExists) CHECK_SYMBOL_EXISTS(jpeg_mem_src "stddef.h;stdio.h;jpeglib.h" HAVE_JPEG_MEM_SRC) IF (HAVE_JPEG_MEM_SRC) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_JPEG_MEM_SRC") ENDIF (HAVE_JPEG_MEM_SRC) CHECK_SYMBOL_EXISTS(jpeg_mem_dest "stddef.h;stdio.h;jpeglib.h" HAVE_JPEG_MEM_DEST) IF (HAVE_JPEG_MEM_DEST) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_JPEG_MEM_DEST") ENDIF (HAVE_JPEG_MEM_DEST) ADD_TORCH_PACKAGE(jpeg "${src}" "${luasrc}" "Image Processing") TARGET_LINK_LIBRARIES(jpeg luaT TH ${JPEG_LIBRARIES}) IF(LUALIB) TARGET_LINK_LIBRARIES(jpeg ${LUALIB}) ENDIF() else (JPEG_FOUND) message ("WARNING: Could not find JPEG libraries, JPEG wrapper will not be installed") endif (JPEG_FOUND) if (PNG_FOUND) SET(src png.c) include_directories (${PNG_INCLUDE_DIR}) ADD_TORCH_PACKAGE(lua_png "${src}" "${luasrc}" "Image Processing") TARGET_LINK_LIBRARIES(lua_png luaT TH ${PNG_LIBRARIES}) IF(LUALIB) TARGET_LINK_LIBRARIES(lua_png ${LUALIB}) ENDIF() else (PNG_FOUND) message ("WARNING: Could not find PNG libraries, PNG wrapper will not be installed") endif (PNG_FOUND) SET(src image.c) SET(luasrc init.lua win.ui test/test.lua) ADD_TORCH_PACKAGE(image "${src}" "${luasrc}" "Image Processing") TARGET_LINK_LIBRARIES(image luaT TH) IF(LUALIB) TARGET_LINK_LIBRARIES(image ${LUALIB}) ENDIF() INSTALL(DIRECTORY "assets" DESTINATION "${Torch_INSTALL_LUA_PATH_SUBDIR}/image") INSTALL(FILES "README.md" DESTINATION "${Torch_INSTALL_LUA_PATH_SUBDIR}/image") COPYRIGHT.txt000066400000000000000000000040011307617767500132050ustar00rootroot00000000000000Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) Copyright (c) 2011-2013 NYU (Clement Farabet) Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute (Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the names of Deepmind Technologies, NYU, NEC Laboratories America and IDIAP Research Institute nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. README.md000066400000000000000000000027441307617767500123670ustar00rootroot00000000000000# image Package Reference Manual # [![Build Status](https://travis-ci.org/torch/image.svg)](https://travis-ci.org/torch/image) __image__ is the [Torch7 distribution](http://torch.ch/) package for processing images. It contains a wide variety of functions divided into the following categories: * [Saving and loading](doc/saveload.md) images as JPEG, PNG, PPM and PGM; * [Simple transformations](doc/simpletransform.md) like translation, scaling and rotation; * [Parameterized transformations](doc/paramtransform.md) like convolutions and warping; * [Simple Drawing Routines](doc/drawing.md) like drawing text or a rectangle on an image; * [Graphical user interfaces](doc/gui.md) like display and window; * [Color Space Conversions](doc/colorspace.md) from and to RGB, YUV, Lab, and HSL; * [Tensor Constructors](doc/tensorconstruct.md) for creating Lenna, Fabio and Gaussian and Laplacian kernels; Note that unless speficied otherwise, this package deals with images of size `nChannel x height x width`. ## Install The easiest way to install this package it by following the [intructions](http://torch.ch/docs/getting-started.html) to install [Torch7](http://www.torch.ch), which includes __image__. Otherwise, to update or manually re-install it: ```bash $ luarocks install image ``` You can test your install with: ```bash $ luajit -limage -e "image.test()" ``` ## Usage ```lua > require 'image' > l = image.lena() > image.display(l) > f = image.fabio() > image.display(f) ``` assets/000077500000000000000000000000001307617767500124035ustar00rootroot00000000000000assets/P2.pgm000066400000000000000000000007671307617767500134030ustar00rootroot00000000000000P2 # feep.ascii.pgm 24 7 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0assets/P4.pbm000066400000000000000000000000101307617767500133550ustar00rootroot00000000000000P4 1 1 assets/P5.pgm000066400000000000000000000003421307617767500133730ustar00rootroot00000000000000P5 100 1 255 666666666666666P5 100 1 255 666666666666666assets/P6.ppm000066400000000000000000000004711307617767500134100ustar00rootroot00000000000000P6 100 1 255 assets/bmp-without-ext000066400000000000000000000002161307617767500154020ustar00rootroot00000000000000BM| %%BGRs(` @33ff&@ff < $\2assets/corrupt-ihdr.png000066400000000000000000000004231307617767500155320ustar00rootroot00000000000000PNG  IHDRZ0gAMA asRGB cHRMz&u0`:@pQ<bKGD݊ pHYsHHFk< IDATB8p;%tEXtdate:creAte201-08-2914:41:3:+00:00* $uMXtdate:}odify2015-08-29T14:41:32+ 3:20[ IENDB`assets/fabio.jpg000066400000000000000000000430461307617767500141740ustar00rootroot00000000000000JFIF*ExifII*1Google            P !1"AQ2aqBRbr#3C$4Sst %Tc ?Z]6v~$LC1cX8t;n*MN\3C tz'ǥ;1rYX?g` ?@l~xU,$ J;X(TҍSʪP͖$'EܶYLD$ϕO0u^@ް}{ALOO[a0#_5ϳEΠJM4VcXhX+bD0H]F|ǜX2nsck- ՁחlIB4s=Ts1 *\6 Ѻ GR~5us݀3xh$kw;J5뭃\]`NS:L`leVHcW "c.gޕ Isz{&{&ڭNSb6'.2åPvmN؊PW(Z%RBoD MT`t́BޙY`p C,Ndgk{T.j.TMP8JfS?v~%U`Uvѥ{Rn躒]PVL?3|]lZL01O57wff/DUVt(#L96KϫI$O˲a: ç2t94&@g`:>V +#큇%#&7[3`uv O+jwěJ/@D啁iL[U10, ǫ̙!,׊ĘVx9I˥S`sb9j* լ -&|)SLu pI- ĮO,Xd׳^=ATp~0ٟ+noR(R*c*wt H*50(*HB9z#աNoIkhSL"d3,iWu8YNcL6 TCM,BF@ṕ'[qVPnNG1z Ă<ʟݰ.[y:M.:a)PLP:eȑ+ީ~ (1;յY] 2X*,w0=) (v |ir"((<aÂ7d@@BC$*c2E m£k`G |׳UGl0V @NfL ޤDg<1ˊ3.|9D `O׺H~U\`Osz&>t`K)91/6~{ݰh?g_ghMJ$SdX2UX>S.T3r\CK@F5clog21  1Kj,l妸.EcYb|?;fpC 32',P˝F*gH, HyX ۹)qOjmRd=Ν^ n(kQGz`D:sT^aB^%Tr#&eejUAkE2h:ZPG5xɬ,<8lr!ap̫y1CLG@&mTx9쮠(3:}22{<+Kdu<&"v{'ii1P_w.Y6֙"%d9iLKӄ2'ʆCf,#w?8~Rg #?Sݠ ,f1wG9Mւs 5`0&DK۴i8 匯f{4{"F2T4Y6 չ=ifX6ncSݰvjLSnaKL啀nڵʼnP[>dA16bAl@y o=]os LI$=l v 5c h9 _C ?SjԢ̽۬H 3CG#` mMZ@0iku5S{^LpGĤI|^Vjݍw{x*K S>-dxV ϾۑwUUQ bS`VG1]uH`U@LjkRج ۦs `y#['}6UZe`b[Kœ#F58AgMW`祀 dNX3G>\I* 32wm Qrj# A'q$|Cڗ9=ޙoSu`: %^I3#0Gu),̽Fh>F!|+`?ct.FjoTEMRm!>,6 9db2jT "U9aiҢjTi "B5onԮiwQB_ɒvMݙ5d^uڞ_piZT qS|\>+onQT r`5бݰ<;6FnTU`sF P(2ov\iJ5EZ.R]J!I#\W۔)њ AVUb]~g}U^Mj5+RbR̤lu;f֦)((Rdިp;ox-BiZd`n 1NF<հ^VJR 2գ"7dM>96M:&07tlvmg204wdT D < g=l7b &2P3,A1`ޏXI-li+I`Y$!1+ ]5mH`_V IEq i x4Us=D͂Mlo OdW^0y֬]&X$tfF a6x.zW8S}7z5/ʘMF\n%i$|Ol%nf0QX ӭS 0+N:/W }Enir|uS֝Pj`/m>k`U/:u͚ջ6u10dĨy;Mzԍ{^!k]#f2i<(wӳDj)Vc {Gq{nƽ} %{'M/{|\V =nmqnoxpڀ CŅ{`AMSDxSQNQ>0o-r$uG<&fUp hS8Ũal{ϵV"*!$Y6/V !Fe\$(!U'+>ʮgH,iZvei(Jyb\+eu{YYYX4]H3u͟Y& İ]̘uoQ<=$|'CFzͪ X&UW5 %s6ڍU4qpD`iqχX5-'0QYT9כŞ eLD`6[uC|퀑W;fpd-3U"K0>&q`nOQ{MSʘJTHuSE(Î4W0# <X3%hԻ=jiܲZ ݮ eoS 95(j &Rf=v{~b+2WujZJ`Z]=޳btCΛ)J5>FԣM.VZNep?>+hҭA;(.%Pd AB!kÄ+zY{ʗʠeNjpʬ/ ɼ] V{4Jk^bfδ!'1Hv _Jaba ,F. Y[) U[:LyfsRu{4 ?zs]tU3x׺U@9k`~ M懺Ui06?kQ\<ǥqujO,7M|0t+[h`&9N E`#1$ `.ỳuw8ea='KE`*IՖg4V^Gd'. }~c0'Y<쯲s{hZ TNB9}Z;ܚ(/UsN*/ wi+r\!-0QČ&R0d^ ּ׻R*S(fF۞/jj`@{€x1Sl\8xHp@:~Ս݊FLD `03[Pe9tϕuRsU^qR sGmvHu ;N 9Uל<t7%ard gX9Y_>|RH+ "@>+2=ŦnH>X nufs BuPVZSeNݿ2d9`;XfYM9`lM P4;3-..BAFEs`'##H,6H bH+.^)=Eܐ l9M \B,( `[JWJhr* QxweXhl[NB5)SՈr&NXN-,ǵ-֥zgFIYTfFEzu$ð4){x\$b<3q{ج ͗sb'uK "N"ݣxqa8 ѻ)Ѧ\̩e/)D>]SYԨɿú~s-$#0>%-4',oWu"9eMZ;&򩘟A뭀#l]KI+lU,pfXv1#<<f8 {\ST`)a7l{Ű/j2pEA]ve߾Eb_Gݰ0oar`bbt9X:n kRW]ڣ( 1}S! X4PN@˧;kfN&\SV D,=,(0AS3"3'.(l0IQӭg<+5b9˥+ ⑓#P Nfyvl<.{On^(O#<@ A *l sdͼ?`~juQW=p)2S e~Ѵ ơQ)SJDs%j`)C*I01Ao_'<++ g;vkjJd*LBX G4uHCe%nQ 9?#gDzo3'.S` 3aNf{DSQCy#=l |ZC/OX>tZW2 ]ʎ@3c~V ɷ1]cqX5f*TW4 TMՕ{BJ8 $-H#l M﵄fTC Ff֧ ES7:#$?9v:ts<,:|Bens.vg4li''/`(/ΦKy5V I °gF2#F jy2nYU S3^¯ RA$ky͛XXQ(j  `O{Gʀ*BA,чEYC{/tCPNp'q{ czwUتF,u? }r6s \{Q6`tS9&?j*,?Y=.G^V =\A6yMD컒P U-ڜ)g1:-ޙ1b#b>|n>XFeFvWkt=KLRńb1g|v}^vE`bi,~*/1<=f,d&c`ўÛ2v2 "޹i},Q[s|`O"1ɣfFԭHڅԩQ l̢"P؝mޣTJF0#ٷlЭּ(JR<$#$C+n",wub,i$}nowezj-gY9E9%t{fsxSZ7^T1fq L[Vd'*X%}$jj! `VMv00y`{h4Ғ2p3 e`r{,N%< #`gI 6}.+I<{)LggeV{I"G/`hl Ϊ0 dUٯ b56Jl_2#ZR,;NzENASLjz 9LQ.̵!=H@8X!%5PBI,Đ=c`>ԎjQ%`sЌ1#i/VHI$P 7`u~B-7q/`.답@ZWIʃ /4gyr1MW<?tpd&2e1I 4=%j M|@>6rNh 1 ],txIē:I`◲jޑ6ڟb5 7r LYfl'8 b> } Uyy 6P`Ho.Ab`yO1ߴ7ghVZ%DG  bnf-bi-+Fc`ݘ5fa*y(ҶC %\G,BaxbfPfbG/[nd+S(@[|E4 Tu%l6 .GFv 9(`߁4镂=}Xdο,tW&<&RJaX%@U6i'XIu S9<=<¥@Eu$fo3YX0m[ N#!^#[od7,F UYţ+0r1]/tVv8:PV0`&bjL`ԥQI6݅,X|Y;:ڜ+O, D|,/5DIxASm]I r|ϗKSeq`δ`mUNZ?;K^v~~y` IV"^" jg?_L _n}V'*faXۓ}4+! FPY1X5μME<-Kh:UBL.YjCJqZEmޢ͛Л]+1RV!:aX`Vp$婍lw/*IZ`(ٻà3=0MEdEa.yAG[HѠd똎yEeUNP"c<[{ $ '@Xv-U e5[p5i ɮʌ9P篝=u"M(r2Y?B𭁴ۙF˩ -k±ƦpHbX3f[ ᪡̓ϲMCdʬ 4V%#> +؜łJ\g*TYX13-7 GAgmK L`<*1 G, m.ܾa$Tj$|*Ń%oڠƉݠ€Oٔ6( 0{hԢi90X޲~ݩȣ:e!VCN쎲gAvT@'9Vh{M@XU:X"n+ax8 [Ahbamr]Y8] 9 ?٤VV ʌ#)iujZapm>9wtW}'qR*:*O(>X5Vo<4&}'2u[A=l7" @gXol0ī?Bw/) nC݃9d`^3i^?^vަ|Wڅ.URcS=zI=8/?o`i4P7޹mdH>S`.ni  lAFKņ 0 x,i9SJL^~ KT8I2z` VJ眃zm!jH ^",T7uSN.Ry. EOdldĴG=, 8F b`r?"AQ`,G<ɰl 3Ta!z랄)ᢸrG!3FdOObѫU̩PA92oH IYu}|O1[7HH?׋s`1C?i%#gK yf~BaFzeLzXҕ22`{D\ec0:l ֫FH A/1*x ?;>ھĈN@` 킎ʅC>aL+oM2ό@z S~($|8~7mNrTmܼIQmMY@b`X2ۥwr07$Քp;B#jupJObf[؍R ,{y 31#1`25_]Hl  %ݗj53 lv"q&&^,`=IyD tv x`@@Aә` w1d@y`ZU/.gFkzBNMM M'z]hS. z>wv~glFJdĀq>xRHY:`W,$X>{ ^zT g v?iDAM1GA!^A閾B߫F9X+.(QB?ZX? <$V*}A`wYyEuphiLUr)cP qn`$X|H D튒 jƋQ"~vT%fM,7;^EXJb$~s`߽> {4W_X,dp3s mB؉JN-|1 `2#ŋ\?ްn8I:bh9 ;#b`81E l{JeR'P36 5  rȈ H`+\(I},II+Rs{= &]vP (*ŦL, vԷɫm4{0c԰:/;7ҬjPDyRR3X>ovKJY q,{j5ee=0^Y I@1ÆKFeS`>c3>X-.t̚Uai몉 >,?/7Ztb P}XkvNlWsNYdIfoNV)t HX8nD*q ! ʬ箖 ޸guA'&6X@843F`X6e8b2@l ƘEd)ԇ#aӺ\j֮00,~`' gf,pPY) N?S'KrBӮʸ4bpfrTE(bt'镃mT`[34Ak^>Z`vpr3?Oi^P'͂]FZekn>V ۢ=X0m=5S[:~m8p`. FC `]ow:JT-nWI4geR9OnA>&{^~^ g(p-#+PvhCP 2#?ݰX)2q?xX fِV j(;{Ռ+-:jn,SuU 0S `=@[!^X̠Kjfs*A@zFXl{:\++{&$I?Io%28RL"}jnS`e4X}f F@s kKFJX'71 ˑ>" s&KLg$5NDDk2 X_k RUF&~Kl#1ev`L1X6G&7Kbsli 3;W,bk#]zdk,^Nʩ8URgdlo[$<`Z"36+'RI9`k^j5Cb? o:!t`DĒ '/.5$-B(o+wJŋB׺ nf6Pݱ74Sݰg 0"j%!7|kD+}G7yN: 9rP( ĒA>(l/KAP Tv}fo`cك\'3NpVL4 gH#"}|ctLD*i8&5n#,0w3^ $hHPr'3`D2S !A2|#j`)0ԯQL1/~4S@Ӕ?;MKZ@jة3V ~8v6\`ĉN^Y.i9FYrM\UP ºª ,:հ97 A$i`LCn]PZUM@NEJuJ E,YJ3;Id1kPX) q@gn.Һ5huIJJ0ńIˉOuYJkMIUpˑHxݝQ0U@"Ts63ӥ$1AHy, Q@$ D| ~d 9.E54Fq,;{ww"M*lzza !I%d*u!t fIMZez`]ܾRG&H x s`Rvu x4D H xi6,_`Y3u3LX#lꔃbF+!# 43l!1*JcKUBՕm'?s;H  Bő+MԸLzXRxdS畀dn!DzgX,ςBvM=UQ],[gGR)oX[Sw:8rAw.C `Hƺ?6HtLa2WuUb27Xss|iޯw+(ƫ2P 3>X7~ S:0M/`퇢ؑ"2.D|:4>ܮ-rH]`iО`knf=| +l%s< "&8FCx6ª +'3=[N$_Msl 춥VJL8K2ٟ * 31d&l}o]|M@&pAr9ה3&)d5dR=]DP,{ waP&ׂPߊ }*j_?{;>)=S1Pt/<^.SUNs;1`н܍ )<. `;9|+IKqa2?vs]`G!X/})3=}l{q&>3ol{>JxXa R, f,µ٘^R }/FR H ʣ'`݋3AuwcfNX^2$|/zł~dcwa8"J H=eS9R  2vӠ٣("5 <x~sD6D*FYaɇd㻦b&$'N*Y(AɈs祀r@jY<{.Y22#){rOyS띃8d"b&e}> z[Eh+s`H2fQ:3尫8M7rcN!o W8 )1s^ O[ek;Tz@6wHq!,N8Ħpwݺ)NOÖ#fKV"\'_N6ddc G\Fo}\R1a)\tPt`sz]Ff̰pi1&ӆD5+3 8N2}g-֯Q ;1]pCWAje@#nu̴@-9? oLNYkX?дHV]@zLlv1Z4P`g fή@)kuǖu.WUf=2X:CNv~)3Nh2s(RS#zwGjOHH8jxae%z k XZ L"FI2$˨٦ģH^]s/{H=:i5i8Q`;[*u(ZH[ ᰥ\2ȵxqt^howݩ$&>8UYiS &mUTwf;ãTKKD단ퟲpWԃ ȝ`ϙW4%:6Y$תO[Х! zJKA[7/`.ӟ>`xi6}1`ONV'?w9yкDŽʺO-z~VeVc P$O6-HPD釨~!3Łb* xZF\(o@[ LodI@A0be+D08 av>zlԫaU!VJ` _vkکS-QoN.hqX>l U),QO!¦9cXW_\6uDdV[FDr/C #L*X` Y&nߤԪBèSO#v㶫 c[!Bg(2bW`Q߶:-)E8 gbq#Y k{\9 yur˕9vOq߰-ʵjOFRbƨEeHj-D|*V 7.v R2:v*̐r, 3X/ޜem]P3ze`{ق2ڹ A͂Qv:dKo~V>_Dlᗻ` TSAw⢇  W3< ؒ),؜ѳQ6R#?2sKC(8r`7 W( X$ݞX8r6 5E]s9$ KNk,J`@Asͧ2 vÑu@wJ1ȟݰs:5&> 'b8Up%6 %,=%XÊ4ץLiNbIs":y!_!Q`4`X!*b=pG[D('<ח,o? [?iU֊'S`ҷ'$0,-Vۥ2C![ 608q+dAa7iG%sVTpBt:}z/\e*ȑXSGZzKvd=O`>/ ^x*z_ۏ' \2xSVzKQlC'Z}Ox,1`~ ? kOK;֖ {wX9-leC`},{/'l *|[-[~` t_As?se^l^assets/fabio.png000066400000000000000000001770531307617767500142060ustar00rootroot00000000000000PNG  IHDRCN cHRMz&u0`:pQ<bKGD̿wtEXtRaw profile type exif exif 40 45786966000049492a0008000000010031010200070000001a00000000000000476f6f67 6c650000 rIDATxDu\[i6"q7]BݡBjT(-hPN}fޙw<~Y3#ͺ}_ZҲb^d123Ylvz#d0bcbCB#"c1&c4Q(4h$@pHS6E G"o`($@ hAQh&1h۴ 1pc14L6M0# fl#S[v*eM0 ?%:X\@(Vh JʫkkkR[ :~ rp %ػ yz!&,2!>&>)2>9!!%ʍ!EcH#x, x`(boCbldljj7167"F1AcH d0N"gw]E`OcLP;<=+HU555uM{t67Ֆh6OrM9f 鵺‚Z&ddd]TVjrJjrB|\LlltL\LtBJb\|3 G4p4%E@F!$'ppj@SS) * ~&.:LeddDahS## bۄġ&ƠMqXwV]a@mN"YAxd En^QqYUuUUuMCcc#8ϠjsuyyZIzK =m-m}#a*Zc/*-3lCfVFvZ#%%HM p&`p#8P P{R@GIDo0P@dj=?"MMLP@0;&ưMp&f mjB#Yu[w"X'< 067#GP eUuu M-mUy:ZsV!4s ߀`\ *Js6L`0@u$'%$3*Iqq IP/G 'l@CP D*S p **c8 fbl4+|6ph&Y#Lo߶9? m0")?92:ϐ_R^U]YQ *(`x2W%s"d͊B I2R+(/+))ļLnfz:ƒb18@#X_{s"fÀ hxxmB0`qx<@li}6LM(pphgj#7mB1DPV8RN[K1";XR\XmW*44 TP15S&AAXSA=m}{ma2Mp@/d g KˑKyں@ UꊒҊ"NDdF3"͜C9H"U*4C8NFbqb7 +3`$DbQXS44( h13@gP(,8Op&0?4 8c4 €|}#Sq!Q.6t41!x"h$ ؼgpG=6w[@2WJez(JG[{K[kCmuEqEm}UID 18* |le %BX0oIL"pYds3rB ;NLx r@"0<p)T `R,ƒ012EaBPcaƦX&*$XF:8Z`p+pw3s GVC{sMM ;D{'0yR/mljTW5456VTTUf*$ xQ,T;3+wO [_GZ!q3Y\@$tN@$%l@"JbT̋s:oe[0~S @X1w@`ŏl,&8Ct3[榮]z:뫫qn!rYV|L̉)IԲȡ, kfkHjS uUڦֶ`ʋK ӊSMH"pqw pI%\sj\*D 'Jd xS+e&08j߀ |Bp+VoUA[bjl &`0&huPRw`m[[tmE:mfz儂NF5=GXc0SSWTH'13'ʫjj55MhFR 3h85'.޾n0D V*ZV(DBB LBTC>iT8%w0(8C$SB&5 x1lÌ>\B׳m;mRSQU'qN8͚9ro%"I8K`Gxv6ӼJOZiR\\Py1vq4H(DkspC(QU`ޠ?6'gh@X&WiB %*u>Ժ⪺ڪ"J*d22RG#]ߑ\ Ai)HFx)K"SSˎ =|l[ JINZ\4K労pgAPmB<$p^n )m\o$"Af $$عF,80Q*m4\CAQ>PF)WkʕhW@ĸ`/GCߡhH &  \< 1F{e3HgbA#'L=̑VԲ$mKpg-ěMQ_|A("EŘ{ǃ`.ss klnjl;s9v$p9(84ʘj 3ht4 /WP(堕EE(ż7-6*):<, G 21) nbFQEa!<@`1Y)dfnfN&Y&Nݿ}ֺҚށm}۶HР0B|}"I8"5(C S"C(10hVq)$?ǐL>%@gkر2by"N=H Ra x*J/Jx_CBT ԋA r d_/'sM @ HjZO)b%2Bƒ]=MA}<Yv,HS0 #44?٣C}`gw}yWW''O.JBVJB|O@BjDbL@RМ&FdhSCVVh`jLq#$+<Ōvv Jf rJAlx<ښ[z;:z6֔ʋ!C& ]Y:&P,|#`|)d J^TbHܬlP@c_w_/P?{ p]пG@ P!d{zD4NBLqH sU64}u;z+t%U%'0uww5PP23]v-Ն Lx3D8ƒHH3GؘDfo$3G_VP]u}K{7tH <JE%b #D3++sgn*$r،TFzf1c@nOLM3A!~!. )  M 42D!̒\};66&&.>6ƴh?1*!<820(,<,"ρ Η<PxQ(`EXhJ0V>QUO;5~frG޾yq7oݾ~ƕK3gg;ٱ{8ppgW]aI;jK]\H:jG!H=B+dIwtgt%Օ \ "Rk;ݺBM~I!ODRH >d4HH g.bF#0j/+ LMN NLAl2`:`V8 K!cpD CIx#=i7r~Ņ_y~/{;\05u*\_T[Wvػkg8-$bbnn 'h{Pjxb $>[m(i8Ҷvm[!\6vki>f8,Us;g/oX|J8RRb#ccج4VFVv&;9)11)5IN尢SSfH&(@"0: #` QH"ьji[TT48?~~eiefڃg?ϟ={ܹwc\m^I]SeEȖ J׼@gc{ #/=3זJ xԒ% pO\HR'!%85G+fh4 (̕+T?y ?5E fN^~0 FjR|dDt iV~'`gf322RSSRRRؙqB)8^y@m qdss*B4"LXZx3+?tvҥkyw=xɋxwݽv MuyJuaME4=j{v54}|#x C#,^|cx#l(jZ@uPST4 $%?*hGi6^~0ZHbҹ|? +'CfResDBDK$B 2 E2kfND`hc:qxlj^ܾOO=s `;_m<~TZ^U.V 9,v:WXWTu`Go$;.0 %o06C m\]8sHV+69,-[ /jhjY oCcsݝMu\\ zBCWRH,je ^HMMMKOJdLN6~_&slZTDVD 2a^co]v_>>reqzjj|zjxx`S'99~vbtC[ 3Zq&WUayDj+*woj`8'+S):N)ݰX@btRwd@.ojkx8箎ʲb&WQk@ ' !D>>Դ4`189Yق6+ J3H:CnN+*WfEx G($" " rs3s_Z|mw5;y}:K[{vt7wtl=8raf-eJ@&v[wp[dW| (܌FDo?k[{4RSRtd߳s:*@zij";Z#R@cq4G/_XJ2mf UrBsBP4tBEZ2Rg"XK$ 1)Ž#'&oܿxiy}GΎ?s#{).ikz؉]m;; rz*Óp"=";77ڶO(SQ_RifAXK+{g[WTBWxPFoR]^h ruypk(o"D<4DqI-x<>h\PTW KJ 99|CFdԗmmA#DTkE,L&S|ASO~|݇o]8u}[w ܼgώ<@&Iw>y{uUmNvyF j5*"OSUڶ@^f4-Hlco T.J"PfFhdhϓKEƶƖƶ.`:{J z^ڬ d$E޾޾0_Pk4\mAqAaiImUq^^IYiuMMUA!I2P0JDR1#|d"F8m(?2}ʵW,_xũ##J:3eZEmAw{m]Jk($L_g$%bo UspɌ c CVvV.)9@ um:`Ej5֨JPL2B$I" &W +kj*KJ+ˠZ -fgeID9,fJR\D+ O M@N1BJq|ā ^??u'fO}do{cJa9N\`{pjCkQCS'۫@ӗVenarHP1vub_hS4/326l2⭬l탒l ]+͝ t6uU4jV %q(c4jNB"~C(ҊںNH `Ih,HÁ`3*DCE]ue gO-/`[HMWtT 8)Le EMu拔 myx/s$)ՙ@ӭY&q5%P$3KKKP+i鶫HRH<,;j BrW֔.|A'WUӘxf& Z2 P" DhvDIYCG.[vsyec[kr5ڒBN_G*!N^.pmTB{8vsk#}[+ ke8,ق jE-vdŻZ{99$qKft"O'`VVts+gȤLy^aemmCGwWwG ȹvu7֔jU z( Nj^7W2xh=F5Xp8 Ԧsoݽ5b##8Mk. AìCҤ]%ft oXPR iSKW{{C30f}}ޞ0\*Wȵ/**09谀@oCC<]}|=\,cЀ(,d{Mpx';3rEC{J7zrnuiy-m-* :w>~}MU[onft=?3PCvwnWM"ÐF!S=7:Aڿ{`Ŝ@G Hss[Gd>ĸVhf{jR LVȘEqD#LRh |V͐oЩxQQAA~~.|}=}ܝݼ<hD oA84evW0yό_^z♣{+Ez}UeM[g[/&hi92vfQ-a{Y#i8c ƋLܿE!n/N S$ N}6Nd4ٙX}LdGD:'g+s˚[Z{:[ohW)U?\>;"$22<74[?{V+,h|Np3iKܿPs\ūW޼t*Ou[isC<[ԩXQ`+2b@s4wö-MuP4C™d DAÝl`7hh ̄_d"斎-;wtv7Tʕ*hz6OqSQ&p!Y8{yzx% W^ #-6!1hcaY1`hR\yrޕ=5@4$oK%738G)`x{Ȫˣ,P(z\c:l)[ yFZbp( v3<A`Po-uJaZRjzZg|rJ[\GOksW[ގޞFhyPR(ĩAnTh.MD3G_?_ؠgJ% 6WNxGxt psuquu b  ¡hc+EtjgEײ}u7w5747dGrs O'5"FgZ@gK*f/Zצ21h# oC°ZPIXW[E\s+[Q_N sb2E CeK[ uu^_]UM͎󠣑@ hD+WwOO5Z U*f3CCÂ#<=|=]\\fx,A "!JEU-t{ƞ<[;4߮=v۵}p7V*/F qdtT2"r]mFfNsdYR}0&N.t g7D# IA,tV  adgp]y}.m7vTafef 23SBI&FЮ@YQhkTrX*3SI 0_g[KK{7@!.nnT"I  G6(S܆ÑmSluZ/fgd1S2Cr}==쭨T 3<œjaogcA%)Y81XG\ ƅ \oO<}MuR c3BҝNulDO4-fd*J9 QnyISXfu.-$k1%6B2L@[YA a26@FL֞# ֜@s8y:QmԹ@{y&3$"ӚL7ÓHTC#q8wD +ZFsNʕ*`ҬԸȸ@w>~~>ޠg|<ܜF($ M"!0Swm.ƆE2S$ج꬈H Kp)gx}umMUBL,1̍ɑh(&9Y`MI@ml-NX Mp63!y!A~N 3x1h-ˣc3JdRe!ŲDee:0&[`PЬ 7ل  0#:oQIMVS|`s"> -4]cCQT'G;Kpd5Eb:BD(8SS  G,,c4Q/ e .\}*W#*@p%B2>LN A?Z JE!0VP M4շqrm-=ŢР QfHK:C~fo4 67q eb$ ՙYon 0!#h amdOМ<|c"=Dd?s h&8  moF!qvnd)J#5#h mcC#L8[Kg&:xsf+%2- [X(dR"#F rch,PMвSC^:srH 'GSbȌ Lg8E:3j$%w>[G!}, ?W{_ //ƞIp< "`$1NQ.!lQfJxlBN. wAa0hlKF(Ƃ<H4} G3xlF|l- TF'-l`ePT #G lVjb|Lddt\XPpDdd|r\t\RjdtBrbBq Q`4b{̝?TM2B;AxHO7̜FZB_Xbog@Wcvᤚ!#]l6!a~cLb(9NZ 2Qbl\tLxGB{QFkxw4 O1Ǡq;>ˊ"T Nm H<!f,njMr#P8F=*S\ToPEl^tL.+% 8ȄؔԤ n6;/d1X@;)L@Zew^B"perQ oBf$ĸ:,ll,m]rb=}bޮQ.t[7/nb HHHL#Rȳx҂nh;$"3gsgd&%ǥ&G8Qрs;k<5zdd{mߵkm[mҷu[G_<΅7E`)4")28"<KZ|Krة)qQёܽ꒼fr9q("d +C >BEЪmNff$R\Vʅo%t;=:_9pb|Dk (8${ŚgKK5$frr\fri,4/WA#ce`Ȗn=tdȑ>4{[ܳc-:+E.Н%(+4.M]W*u5 &gɀK逆#|=}c%e e"#ǎ<G$⩰|^&<#Qk y4&m3!1s`hD`$+s rlW #AKwxh0+,-.)*i im]]^ 2.Fa6?/p٩Q>p<F፜E!aNupOdabO3A`h(:o78۷nٲu};yxWWWWgʢ={ji!qn1q! Q#/_^4ٳgό?:6qANFS•\OݻuΝw>~ӛ߾}/޼x|Ƶw6-]?;rz+}pƍWVΏ9;~fl ,̊ u#M1)"F{;F:y9lP1)a!!!МRxAÌ q1"~XؚJKb}aQ`"BZPYVYTW@oY< f3}6ޱU #ۇmnnhdmܽc}|HK%{3t=&no>}ƝǏ<~ӧ޿yëG߸}{^<'ߺveue҅3Ϝ5t#{wwRbAEc50$>90:0+K+HJx&|!.QŅxCb1D:ghZ*te>/P'I\_R -).((7HrYL~6Lv7y;jvX~zӻ./]x==2:2::rz֩8F|XlfBqpN vvpL9,Fr1>~Aa!¼bQFRD@¢h2}F)HbzL m:9 I:X%DXrXdĶMeG>xfGo*1Z"(n,$grI˖s>x߼y7o߼o??/?yo_{/޿WV/^ 3:rĉFOطkK:DEzl'Y:8ں𴹬t|zaFjlXpO$W3dgDР[SD:R>?JBi6[PY 63)..9-GRx"J.R. `B]b{ m3O:67\$WK$g+c:Ҙ`̯;y~go|zhoA|?}o_|ۧOϿ}X|Gp|޵秀8{jȉڿX΍t7-#NIaI*#LNI`&ʪ?:S_Z|xD03Z8;s[OL|o_kwP_ׯ߾|~?(_;>7_]8Ĺ]j SCB",8/VY^VP$s M J/).1!!!**::)*8!d$FR4pTHefIKO,)ؘD=AS˵*=^Q_?yip̅=8{G{`J hꛙ_exڭ_oߡ7Ǐo?__?x/>}|ʕ鋗.N9svlEv֋CܽÂ\ͬ]\3ɩq!QnQLmR!`0ScbSDZvRRR“ID2NMb0LA$$$f 9]  *,?"-'ai IٙI! Rqz+%xSnfic%^Je$'$Y!&0lTP B$Ϯɕ'F'g]^9x֎֖batDb`_ Fohzs -@e>}?A ~ןG}7E߀S|W.-,\8;1q$ӧOQboggcifdfnM!zDfdd9ѩlnJHP|zaE!ZxD%Ƨ'$4#-¬lH !+03+s#PY/sb\||Sb '^?Oz˷ok(~ @?}۷Ϟ޹~d'O8rlGk˂J!(a~^>^N$4&<9%Uvvf&%YPf0Frbbb\TxRfBrZL"C+X+ɵ@o% p̴,AA$ismqfᱽ{ ޶uںbM*%1=2KJMLWnqµ{@p/? ;*2W| A?[7חWV/ftIH^v%:-N J$[ѩXopX*drx(5,49kCBtX\l\\|lxpb.19%+'1>5߃),H(d*P yY ,i:O8vC7Wje"U핕Er~|b>_+͇ܲ:OAq ?}3$# >-t~ l[W/^=sΌCnn] I!8w@XTR/;[.3}eҖdBHcsL'aFF%ja2H{14bF>DE&''d;:$%1ȁ%;+G$gq)ڂܸ8T8#-c饫{ ?o> H^?|ճ'O=}kkW/^>?>~S;ۋyd*o Zy9"C]Gێcݿo@{Q( BTjrcA&xDFFijҙ 4VzR"hjjZ&%P*ArZRC^sg׎=N]2=>9YN8֭.y7຃Sw!JU'0߾{w|㇏?yrK+3ONL 2~ ]ł( E4ahǨ4LWZUݿ)2dEyD*U"` Z!4:nnnikK^4%..5e$9lF n\g֊aC.Wdʬ=OY9rh=uuer20jArvOSiMvϯxv~;>&_޽{(|͋'ox,\9n#zNd: m oR=c2U-Z1` VR/+,ʓhj|N:[\غ'7'-"̂@̭ma)8P )i5 ;-І0;G$`gT52B)~3'O,]>Y8-!'jakEڇZY3K{v]_x)?˧wB6i>߾yǯ6e޼zOܽyՕŹY ͕ [1B+(ܠThzQD,UbX!ׂ~XNJ2IT#, J-vb;=)5E:73G*>L(syB S֫$2|ÇMؿw[S# WUD i` (*U}ϡSSW>yw@u||g.߾=8޽{_? 1,W ׯA#dss&'%;32rЁ]۷}X%6Ó]"Ly6Qѱ^ X&C731񢼼p?$hg7XF:d%㚞VD3u2Z!H)=PԩG:pcu e4N2?,*B]yy~#7=>|΃GO_{Hj^ +Hy(o=/_? 4{xkmiK!;vz#{v& -gPR-+ c&.^QBYZVJjgFfʤbf w>vf+k;7X&`Q@) }xT|]<>yͫw_>~ɗ߾xTn<./_8wv8d[[.ď@gז*q92t :$R1hD̎ sx+[GgX+ '^47 W/ <<n_[V/_^^^r䉡;w\ߚ& |X^ X_@#ɀF@+^?wk+kWVVA= $ͻ7VuKspXsKv WebvjJJ|d<1/7_W(9N4CPTTKOILSjT"Pi5y/J*j N#Wʐ_R:8{͍S3.^e[]nimcuǁ9Xo)77.2NT*9xᳳ//ڸ{ pYT$1@;ý)0~4w66n\]]4;9qrl[7oRӡr')dKY89Eq۷)eZ~Wp\'(L,#3wk s J@]VTCOM)+0k- \T^ԥfο|<3=r܅#vn+i;82P*'c,}`WU i ,gWn޾s6'C?}_߁1_>/_Y?;~b!wu5v((fT3 Cvqc 5y"+ߞfFE&fx@hg[2'!. VRWm3> h\Q啗JyʖᙹkKW/-N ?vf_evU9)!5#i(f4h_~䅕oݹwK\߽\hR_Ѐ;?{Ͽǯ?~{W/:~zᡡ}wnhiхCP(W2)*v #1'#BN2f/6>ˑ>>*hN}UYaTPSUȡd2WїV2w_uKK Wgώ oܽ@O@oM'l ,*NIupG>5>3 ڭxD-40_~h믟?A&/ ?}߿sƭ ߿cΞ;zں H8G%i^ީ;EHaq,Ơ${rb=ht+k{GgVbZ\ _'K 9<@y\FJMy{ܥ٥ >m}GP`@ch6à^^tG^ 3(~_0|O CC|Dӻܸc~f;+Kʫ 2.X3 DQIKHdR%**P&;X:G&f b HR~z'JbSIUe%y|6 Ҽ<8Μ]xxo쥹s++ӧ΍:rx}O<ސb*L'vDs:I__[~#/_4>|/_?篯?- X:_>}׏_<ś7޻pev[{r*&)hn-dC0&XfKtV|DE]ݑJpqttt2=\< se"!O] "ɖHee%:)HC|as{w[~q 光'>shGwxGpNkH!LX)`po~&~@? @{J*?{o]Y8BUV͉JԔvt77WdY`VR,]=#3R^#ߝnHard R*IKH/mWE) X"R+,<~Ft\'tER(;vt_+W''N;x`Y4[_@;ށ`傀 ff~Y} Nۏן-(hgn^zua~~'nSY)) H_]'5'Xcx2@ģQh32#]PbP<eBTTsb8ڶ<^BrVUde09 VJD,ϖUrapѝ G箬.]]:wzN̎T]̩T+OO;g_[:MBTe˹KsKKūo?z ߾wN {?}O|ݻg/_<{辖ޢHK9 ^[X^dC[`( D8!"]֚+N MN) r NW&OST6WוH]Zu &1X|HLN&lV:GHEݧ/?1r啅kO/M9xpjP \q4z$! MD騠s +kWׯygϞ>~&oA \P޼_]8{bOGcK]n&' pwJח -uZBQ4" \= zv"/$F%2c,]}s "},(. ]4=TVFDa+ Ę,$HrZ[6Vlyu.MO._ɱ'f%[ZY;8QXQTEA*"EdW&O:?;|?x[kWo<| z߿}g^2ϑS@6\Z[]<qfjz_~x=:zOO͊pq p JQ66ٳ-z?L LhS܈nX4s'(fRhy P!+/%DYoKg$'%e2@ %,nN6hUzw$:IՁ"j$5x{#Bpٙ(2 P*2F)-zq痏N.̭_qu󣇇*ʳl(.$8 !dh= Ē`fgO9yj L3qQa~.^aQ i>\}.Mє'05,BؿvMBHtJٖ#CÇ op%͈" =<i3"C `ʆf]ogܧT*JjX]Z?}~ڃ+3n]u֕c.OO_84&֏Jw oQ2ABN-^tqz =U,Q#%ڊĐVun?2ѓ߼>{f:_WTkJ*{̞K Yێ 8pCɶ6 INBcx2%(%-Krts3[oЩ K "^niana Kf@4'-d"P$ō]=;Owcvb՛6Z2wv 3ӻ\nF %4,K2#gW^9zdރVrcCVn{@Rv~Go{Go_>?Ń,?Izb:UGoYd1E&<cbMg SieLYFxW,GLd \hE*#U+]uSMeO襧w.ܾ|77O^^=}'v+\ 34BpX"C",IX+kS=6U̎HrS<=lBcbcb2CSW6|ɝ[+ܱ^HPP5L=SSY^׿'wn뗺jmk H,#-6!qiII1"M]yuZ_Ph $bY^&tsHoK:bim  asr J^D,TҒ#gG=\mCC!ah"# LBSN,-߸fpخ|IXe᡼$&'tή\Oߘ8kс-; u-[i۷{Lسk!""˝bk%8Q=C#㘠żĄ W(kJsbBm-V#$8Q\Lggr`ArDXK 5΍Lrdփ Vn\y񱥋FFR"lȠC'$p,K Wnܹpeu5 npm{TV}rޓg86z߾yű{ضk߶-;wڱy'o kxcgϜ޵]ݐ`kO$a8JH$c^QS_SZWjEAAAvfvN!@߀FP8b}BT(U CƏ]z2w]_8sjt}mb_7Lh' LHx#$pXSKskWlܸiw6;y N,.,\_ի߼5?gѩ cc'9u虳'MpSϜ=ufܡ-5==^f<'bXΟTK42+&U`hk.k++qrښ {*'lCm@76(("U[&,v{jwn\83pّU0[osF 21%p&3)fFΑsgWn߿q}űCv|2k[ޣK_Y0ug_0399}3gό<Оb4SPW*o. E QddD@T7K@ sP zH-tl>2r[sg67nܽ>~}meniufJVkכJbp8Jw' d 7DI{vzҕ{0~БLڼ악K'G'>>z{WCb#J&Aړ-҆r@EzXm]c]}εWn>:uj-/.^ٸpi;o>M^8?qfbzzbjb驉񱑽][v*pAvpr -h**o/3lecE}cMiMmQ._QRPK  sqKEDD%qD^+5)J%2@Si5h'V=}ĕ|vs.ͮ=H)(1 Xt`djqڃ'Po\xqx&fׯ/]|K7_{Gf,\83?3sqn̅/\87~[6n\ r2[-< ɩjoilnnCmk[sysGm)f NxJPw@phh",0$V\#AIbs2hiD%h;{1qcK/޾7ww^:|xpKW}qcsaDYzr㣣+5*)KT\To('&[;BbҲ%\$/x#25r4uρGG,O^\XpmOܽ}s"vFl%1RVo xpyNN. }ç|/ܸ~|[7޸yM_>}ށc@/,_^]KWGǀb<3r͍2=(G;wP C\ѿL!oTP,+Ebm<|C#Bb`I9V 2z@S:O]+-u`ɵ.M~gcµ;[;0}܉m2#*–$`,X"Qm$ Ew`o<>}+4ׯO>ѝ}h!(۷>0ƽg_>qitp}ã._޸swo6ivvf̉[{JE][*,m\b{뎮2uack[Ok}]]gNT\RLĆٻxFFEøm^~^K\JH.gqyV]Pc#+#A\_;_8yvz[4ǐdnbA \3jz-( Cb{G/-x/_?~ן?Ë_=yg ݿzٽ;7V7|:E2lg ʌ`٣ś4*ɕ_|v?Ͽ/вO^>5P+7<+S7x+k޿@ܼ&47;511:g[cS}Iّ",*|IDAT/]Swh{C˫:zkZk;ę}g@?6|񣻗V.o~(@?߾|zheë˗]ZC86400+7!<wo]remmuqԹ3'WW>yD%' YZY[)R7մ61Sã<}"C"`y\BрſR(7 bjӏoNٹ S7X囍+KKl.im1ch41r'gE]%N]\+/_6/}իW/__|^|UЁFN-]Y^y H}sW%:j;RicFsp WK-[wuzbq{P\V_`H r JAPש 9,nH(@D*].9ٳsS6]Ӈ:3rt++nhA[zdT.baix$W8{^?@_~|=4?׮^yhÃ>zny} ꍛms"3haݹc'WUn=pxȚBtss ɯn(Q*{=QVRWXU[\/q  Jɀ!& H$<:O:wtjjfbb>{tevb|ҹѱ֒z;`[ j_dڠ%NN-o緾ǯ_/ЪπV}[nol48ș ;׮݀޽ ̹sgFl+h:ң33  gȊ*˴޽Μ[(:Ԧm*k.0C<#aH&eeegery9|~/Iv_W'Oluٹ'߽ dvra򅹱3h3!Vdc*=|\ ko8>qi݇~'_#Oh>+>{77z彫?0t̥׮޼y֭׮X1frDs'RVTSspgM;EEDم%%EmΝW&=-O޵XILs󏊉O c莱71ق,DQ mp~`c#ܸ}{.l/sEyZYZ9ImBKL4½'N,_rO_h?/߾}vVyׁxg^;2ogr΍Kϟ8 89rxx+U'31; ;;EŹ9\vLDoPltll*AvaHLP5ʺm{Μ83q66n݇< I]3tz[:T'ԩAL_ kN)3OZA-OL.\ǯ??@~~ᅲ r .!x#ᦃ)r{_Ig~!3h|>22n5296_UTXNi8% ĩKZvgO̩ih#:wQ¼7ůn޾px-+?uK>n޾u뙗ϟ;9ҍ<~5$4h3BR |csfgA G"4ڧ6>~/lܴ>cͣPK-N?Vt:?3> > Z1J$h4Rkq{}CCP@x06-k|x's݆Vnq;+[{:H4QZI9>m}̕WGܺy6+/?Jff&p9ٷP2ĢhJ Msi8;5 ʙ8N]E~ޥE9_]^b-,#}鎷2C /φ:I d}N?\ɤW+RBt׶>O;vx;UɯA{kToo*/{EyrpG݋׭W@?{µۯ^ege?.zsދW=xުc3jN(ᱱaO(4BA"ȕRKz񨬪yE-Df1/MG.IS]ɹO?oǿo Zӏ 3CNOpd >A 2L6oے.h2h5r1DT׶fJ'SO?r//}󾺭 P]eSq~Q >YYe8z3O\|qʅΟ܂ _O'8RuN=ZMdɅ/>͌=&L@Ǔ"ipyAx].p:J 84 UWxk(X}e]yKEMMM-}j+o,,yv?䉓G:w=/>y{7ܼ[Av~~Os^?dJ"+PHMa$t{@#yrXa0zZ\[TŦՋ"^ƘTo{\^_fb} zBs 2I"hڝv7uZQx,*G}}w[ͳ)ŭZ僓y}[^_^[݇nxe^c֫{<{޽Gwn^;wĕ;]y.\s 4Yvz4+Y|l$9JS 2Y-mm$4*"-5)yۍS֦ra`濝O/믿|x {F³ _GM/X &.misBJkzU )N{ZM59W"Pr ^i,yV\\j*భyO_.<RRENA'nߺI;n_>u&Vĕ+׀\vKY?}(?٣o_ 2ӳss 3 ?ן@|(I_ǽfMgqL\csX\.9`6ikrXnDԽ96qiҋ7V75u4b|zdtѳ/^m4܊W7]˼s֓Ƿo^:uq*0gϞz.ލYw?y7y E,)-zېYm LOHdb^/jXڎб y/rIF$%E.IIHI^d>69=pxttll|brjjz0ŗ~/ӧtd 3:P\>%PmفzhS*ө" HC^P;o?.o&76W _z[^{{lVQi_ͼrʭNt=ʵΞ;{o?q9o߾-k# nv:D :&D4zIJ>\_ۅȽŹyv^ C,I@"`q/](%Z (0<<Ff>A'}Ͽ2? C/o?7iCplW3mw8!0\"Pi|o'Ր}iAOc7؎4r!di3ƍ'׎]}Û/=ul+W/yPt;g~w繣{|RЅꙷ޾~nw{}⢢JLsu::Qa&GǃO䈵ZD:D•[ >8T,> r)Vg$J9dxdxxdl|rz3S ?/_& fgg&? >F%V,=6IQ+5p8t*`S4ۍnkxw =6bLHC7rEyOhqq[_<|֝w?pFGYYg.^v+/rr<w **kZ \j#eUKx4X Ma|:(K$v?uȊxdJ: -G%o9 8@3ݯ}!0&f&&a14Q# rYM{{ h/:B-Ae\z^ULP, NÁ"[,\]aֵ7 [y腫7r 7n;qփ/".d޹$ŋo{>;mV0[|D0 (%LH6:*3&@mo{sȁe脘xbI#zBX h:0*dvn\iufk(C@Uġ1A.FVM!zwW,Yq!L6AiT ؉f ȭ2]{w^[fߴvWfx3ۂwJ JkJ;j;:daвrFAy^D*U*Y#%R!,li,Lmã6.G S@pIIF`ɱ+F&j,v? cS3OgVm婫Ғx #Pȕo0EH(j_O{c"Ylʻ򢲪ٕ]}X` 88֤0F/3s cH`xp7Eovl{x(Dxj҉ixj.q4R*Į×&%Gmh?KMZG@mv>swPO?~UNqM廧oںztGAOAߡWitjGvӕA)M%J ]&UQ+OLHC&'`1pTedJIU 9 M΍mP 04}>ⲙEHdpx iT $X25#X ܇x^¤q &Ч3jTnJؖusRfg` ˢٴ"&)< 'Z"»ʆlJ_")1"LF&$#c KbJ( s!5yN'( ӀìpXHc \4NJHL".MA&QI Y}>}m' Z]Z Kv:b]zg]r]_=w#79'/?xzIe]}QMy{WG}nrX91Nv\!Ka{bD/m\gҌ%KQ$JLSMi+AZZ w X\6:pp9f9: KJD&b  UQ08,ZHS0=]%ےw>yiS˖fQ~Zb@m.ʞv/BmMYvtU\;ypߑ4aփ?ͫ(˯iokj|s61:9u5@9E^)d WiXeEg$Z45)m՚ɉ xD|"2ysԙ56t&Tlpz]^d6܃>0 yˠpn` g24 Jww߀u̕G54{zba`.k}hSWHYk-IXBGNua{DNփǯ_$nu*jkon_m9&'Cؐi %z`qDlMoo^ݓd׊5VR O\4Z&^6z2AA V ZAZ)rYcw+q|&W$t6T:Hn{Yo^Ē'XY .iA.oܼruF'nJ]m3: ߾yvfֻҲw/+S&~u :olnf$4250z0&X[){sqѕKgv.aMWAƣ#1 p4UaX-flw{t:hyVJPmVohtj"< /t3212(DLYl؃<à}ݍ3u,g8ۥvkjrr׹{۶lJ؜z]{wlwbDOT5/PB E7e :fHrYIUMvl]*5m-G$$bP8d^T3, 681IiRI DAYuzl]/`IeW3^w!MۖMOK[dyzJ2 #Vzxҝv:Oj@FMY;Sc.;`L1G$P}ݽ~OJjj[;{TX"CG:ʠ{;;l>yyio) V#mtkԭqzO;yM++ںuϡS7Ν9>Q!e0=S ,:ͭɭnß<=;71/V9<6͠fӄvWO?z/†5mKY*mŲI8d,LG]Fd1cn*spzcrP$01=и0Ƒ82x"`TL+Nڃn);Kܰȭʬ[Em9 [0R|SΞ9~+O[q=v}MqeAaAquSc'pM F\ac_\FcQ4Hj)~ɣrήa KRoYr Trr꒴D@#Dlctk&v,:pbF@bTȡAtQ'Kl6 4 ӊxظƧ7wDs!͓:95W>xv3[O_z t\r̙/~mIC{kKy zBCyUû.sT1s_ڷv-?d/_k-{tsF57+)~SZ]ׅlNt46 T ʮ :UYן\С- kؽm4bcm铪ehd5(4`04;Xx7 ǦfgAh >àa,*D|GcU=KqKm?{/>)nJ)O_>.){Tqau_^['lٶu v ٵn3g_PT鳧Tɽ]Tc@X[d:; 9nsZdaw˷͏\UВd$yN^nUC%SA~/̽].u,~YgWo~{Glٹ1myvط{M=~."O^4+BF|pEs&'C^ \ 0>CaRY=**(߳c>b88JB,.uǝnP@]FАQh{ :Bpy& ]&Cb d<6le0]5E6!qKɍS/e~g^*:;@vBFF6Rh,,FЀ!"Dc;Z{hގ7yWV'##m^uٛg^x!R1/>^r97nٱ'ض{-'l|ࡈGJU6ajQE uR_Uj5:읜_ ֪ b|̫GU-]oߴ\z֑pe -0X . N:@ hBxh34}`h7jB&-xy<R~ D!S<.O ;[ZrWKC᫗WZ}3.~go<6;9qZL7j`ԧ<2y?J*/vs+l8x≭:. A! Ȕ(䔋T_.*kp0HetAg5jB 1 *I%TOa&O&uvuwa֚oز,9ons~ >3\2퇾7ܷ=c۪]۱qﮍ?tZD~McUYy釚6y6`px\Df#6tܧѡə1Vg{v/K`ߝmks:=ݾ}Ւ HDBr J@bH8"a3/jNUhwa(hWC$H! 뛲@ۤ/X "*m]}E7'ǥ68C-o^>Wx}o>ƩPdڊG7K]ܹsˎ68zc7۲†ejj{<FS2jА5oxlzvf2^o{+*4Ȫk6߻-kx`؄8zjiۣFAxhJtа dlЩb ˦$A $ $.lkjw}]^so=*>61aeo?~avgG_ׇϕ=ۭkʪƇ 2ٽe-7xK7w-YeوҲrʭrBG7r2IsN}09^2h}Di/gޣ7}pĈe(xb<@ "LLlR"bf@6V9yqCw J\$pXtHfg -ElMm=y6!;o--ɹs{;T_=WJnU(H鄺ws.>}%KvY܉wfټ@DUC}yAa3kɝnGի:n0kln  7N89}l,WQp8e{&&CS(xdl I x"rV"%Vim6V.׸!^].%H-$PM @EPׇnli,+ihky-%5>1~罬ҷo?q7Â֗'~Ϋ2z9\VY 5\i]3޽{ˊ%:4Zpt>"B%p§=y~?@%TٷoM^۰.b6X ;bq82>nT8l2'r0#a0 < [NI rL@ l"NtAt6WմuT~+nlޚznnmoQt~mɻ&.M$q5MO_ݽr8+R"_sJ;!FTՑ-\6o0$X B#cf?L|b X<kέ%Lr}cwkc0d(8#*ZZ5.tRBmp@|~vɤQksbX;$G&2h">H`3չ]xDh,{|pǎuG6l7}I%0< GE/F&'nU'̃#c b 2[!F^ &LXQUWmQ;[I2"9qǛLwӳ-mjz]U^͋~@-DwT]}˶E.KKJ_2"*[( [6jDK=:9[ V(vh$/_?LONNcqXRoYEϧkknoٰĆG/o!S(T<`ꢣc}:'-LǑ$xTQS-GOB>9j6 ح,f ]{V$%mR:_~u'Cu}G uefu7ub[O/_;8O%Ƥ%~$*>aw7EE46u4wԅM,Iҙ#s?ό/|4MlEcc0ΞH#וܰ| =QW!P (P_p8H01Q'bӖmȪm!!!V`hx8{bZ<55ɱ!XVѨ"t5W}ߏv5=<)%}D]~W::^ɫ(z4-[[uVGc"#QQS}D}o&w@ǒLVQ"ن';ÓsF?~wlJGHmnFw6vaR!=Ht{e\"8P[W\,~Tnj"$ܖ[{n^ }hQtwvl-,mi+UJ֋,ӀاFG'O4糚lKSCĸ7w/ߞvD<#!H o`q dK+I,-Q̾0zf S[]wAv \ O$b;*s#߶³K3֯X+(S"˧^u7.ª6t{5mEFbR~8zU֡U%e2Q* j`C><-݁_~^`<8 C7{meG 6Wbky2R~aIªܻ_*,kPTK(UG]8}L,9q;RDyEee2`ڔUzCS3?Nc'\vutN,VT ه׭[:pij*,11-=}$@Rn< V -B:vuNiG_?Ofˍn'4M[P67Jwabf \`P8LgɻwX2]l\&Ք676DJdajmۺog-BD'E.MXu:Nij7/ K+jr`( _g+ NMolĪQIi}/jiP8"E:Ka=صXDjbZZ Dd,HĒ_=Km6w0Fqgfg>}\ :lV1(T+߽+'1$rOO;NݜE;Nn((.z{O[jlƵ>mt} _]GYjl~EgrX,?~NNNN G-fL XWr!ueX/0dJܸ=.2&p|!j{hbCщih/_>CLO<,t"ma"GǑ{f_ٳuM\qOseyaYUMMJ߾lnl(Jt7S;%b/FD-]W+#^}WY^Ҁ%}Cf*~\ea p883 2*rS4&+ζv$m~xj$0@\'"aqx0x\<:&6"ت#YMM 4X  A{᧙yP#a?NiR[_ZR݉!NS(^:s-Q{MDoשׂ(/(-xՇ—m $r-G.[J@FD,N_0'uMzR5mO]t8wm[0"4ċg$\ 5;[}|d:d**1%@$V,g }ws Z:*ʪ[9BL ̀ A" lo(/aq8  |Cޘv'bb~eQŇw莞vX,o_;sʸظh@oKnhi(rUZZ&T\Ncׯ_?[x00{{SI8@(gK0쭼sɑO>%9dCƁO 'q1p>DB+yeM bZTB)Kd:$$rJ&QLJow{}yiqI]7 l U46n" ]oWV7{*kDWiKn8iO2lYwK1Aw`dDuSh]726 LMS02644L{yQiy]ia1O((djZ m`2,PgQ4\OgGuYqYucLRȄ~2IXl_7mٻEkQں۫s;[[AB[{;*Jҗdlrzc\jw"cˈkhTI)hFss/qaǏ_Ɔ jvWkcqaiCuF/H\Zg+ twW.瞸tY?$gD'%$C*<&.> V LgGGWG;`3+KDl Ӈm1uťUMuI4JB kS6<Ny T1+ך}FylBR,mKH@ 80@yScVoɪ.o2x4) RdJcSDD9,@i~uݮ>"aܾz"kdJ%:nêe+HNtt`lM UE//췚-*n{*Pdf\qU%⧧=w3̈($x8h`:ؘňĥ[VTV 0=765jk*KT֔SXہ1LDN'1ÝytڍDf"!ӎ]&NQ^.Wd0 G/NYfeZrb|'#踎>Ze"18>NG&?򷯳@ho;L\s5JqiA,7::K>}ƝS.urUГQK yu '11}\b#tqx N7Tb`UQ@7mNJ͈\},*@eq\,7xCمɱۗ#i tً*ټV9C}Z/YUC7.^}v:1x HB&R7" 3_*,9yu5RtP[W '={ūw bId"Z9BP,xB_g_郳>qf=aSYǡxr?GEwt ylV"ʺ8eI)(ĢE##(@dF S819]z?; ig6QTB})Ǥ \`W Uμtcӳ녥;cɀv4lێX@|#K\q^6N#aKkoijiinhk++***,-+/n%AI$L)h[(0 <ЇɽyV)W Pjl ϢrX] &/3~>*3`^y)5.lӊȸM,Jq@b|=84 y3cАȨfR PCK+ ^Lm,+i/9wѩ{<%w*.[m($t$ ?x^kG5L!f.=m 55ՕM;1t@*ʀLIDJfd|&P<܏+zp˼7ԃf1_6hFSiJ$8| ̊'G,[d}i(dBT 2]&WL+`88^ ،"M'D}hīqT*sjӻr {ƱM?d=AߔV\?X8]{ʀAU@}`Q?}ϑUU IB~ loAcI46 p(|n2He, t‡\@8\P,$2>̠'>]&e,hhG QrB+aJ,XxT|脨%{^8!Z7o3P`dfj|jvnzthiHE=6y" ^\j\݋삒oݺxhi\²l\vP2{g,&HLGG~3}JLsWo%Ph\.p9-tVVb\"\H(KeJÀAVT Ρ[y^ِ&m04tB,l*NJ fp PO?GEƢR'`)wj#h< MOO h%vjRA} |񋏮_ ]bÚKoFƀOz8 G b֟{Ü:4F犕p84πbV_MfF-IR?2Ah1Z0LSiacjk=iA]W{e?ȹT:y&8YH4,ݻQ)1+/KZvD?Lo?MNɑk3VbZޢWLZ9n{Ńq+6\f9Qq%ӓP8D $-#/~ه<ͭxEjBn؜6gj < 84NPHD2H_Vj1M#s 2'ŹeX2*˻8Ra%9@L>[XkSbSVEDݳ_?'&Cӟ>zwd1#S#ckiV, üov'shT**iUkvL.mþ_<Yuٓ/4RH9n'(+D~f*5jhgXB2Jf r_odvpVGhjM[ZSaE9ɐj !#184=!iyrjLzt[#lt;2)d6 <^g|f80R,V9L2Vw׏o]32"%qk3VJKOGHZ-0HTBꚋ/cPb4~ jJVڦ<(UjBSk4JɬI8"8&O0 N]<2'R,vy=fF#t_AA3!zp\RȕjJ d2\^ɝIqcώõt6*<dAoy.UYkbҿ_(#\]yoyj|< ?χ=!? LO!0k|d6W^jqZ\||l%iKR"Җ,ˈ%KX2%&\wm"Z(:x]x\vhчFZFViorrVD5JGBRiʻ}Yq E<8<'|^ ZO T ${Y}rIT~DZz_~:/|-|qnC>NcplJRoKmm~4.} qpdbΝV RK,KJ oD"? *&&!m%*#J,6 r|[7e6P@Z鵠$ FdȤ:rTJCw?]ux`l~10${j=lmPV爉D<lel\҆711?ϟC64}(pШN/2kL$<ؑû Q F459$űI E ! ONFD!ai+V-_سUM]8*W4"hp{8`(Z` @z3`t:# %"l}mYWf2=4:`z0m^P%*!12*?{'?.|16FYoCZj3%̃6j)&.]j|bڒD@[d5-!\C4!!>:")>:!m疭>́㤰 `hHۀt9lvt٭6b5@) v:\ox0X P&SH UU%|kHH8"),zBf(97Ϝ{tmsFBe~|C鯟džf/S#h(p}NP5Brի+{ܨؓ  D,KaA M7!Q) HxdD<|Va\)MZ!e5xA@mT h5 ]v^.0A*!RRVE0;gpaj4 C'SyLֆW{?xvlWe%Zy,P:l JeR3ʺ޺7?,OrxfLkV^3j=V/Zrȶ xBTTRJ<(HT<)خDB|bJ,&@Ac!q',]ysU6wV!A%"8JhZtjXP1'z^U%l@.pM$\k ^rǜ"TAAw L'_DF7қ•Š^!Ӄ >`P70;? ǧ榆mFy&L;½i ,Le$$C2dZ -KHIM m&Gd%n@2 |MK;2G16p_`~עL:#9 Ro{+Fi./ M"FF$J^ &R,$ S҃摚J?=PX1=<8p͌r1?7;5>:26-a߀E+aɇ}B\uBR%uNKLN |}8 h Z x,X`VUEuEE;'QzS fP 0WL63EVPb ۬ 8lJcr9܈a\0W-U2XGE飩@%$񀺳|v;5">&tC~o|aaa842< m.A'τO29U,5k޸yӻ" B3!p1Qes5u9 O`@D⺝.:󕆀°VFc0,vPBwPB!è . @aK7jbzBBKT6L(t>S$[ۚv? hO@tvs-QCp7`Z22S$W]}$`Cl^1H/hҀ$CEEFz񫚪,WԁNןx8~Ʃ:@7Z,hXQr}6\"qBa{ `kq,_q|ÁHt*0LKll A%E-|Bu黢_x b9=#@|{0 C.FgUbYGWjwZ{.e?=spzŒp$ z?S>xTxJVT7x*P:4X`8 [~ # eNe06ڗxn[|?DFF9lL"122qk֋"Xo p@u qETt[u b\.%2Y uӜOCnQoTk@K4|*Dd4'u֒%6tk ;#ۗp+B.w2\v2T䲭Nۺme EEÑXh?> #\O<.:.&!6*e[_T74q4owbAE5@~XS\2H,FR6;^k@8  9<BaI}nk4 %S6aUd"a{::K,NzQх ;^i3Xn'c ,la+/,IONݰuӮC֧VA"8a ߸SH|DѰh"DLdڑ7A(fjȈ >'4<@ z JBen@Gq)w6ƦGXg}"~:3=?04ו}qɛ`Wܽc5>U/qQ1\ @oD##Qѩ_cd$OYyCs/[4YKo,Ep|G x]:J>zT,`sd&I(dź@`fk cЬ2J9XLKEsk'DT4x#("pُܶNW0 GщӦUd 4`vm8t¡Ra)+a@gDECǍA-v4'& 4 gg~~& W2YPf4p8 `Y'@ I#@+r<<29𘵀MzxiZ& 1[qB{?4IAcZTZD֝6xba-sCD?62  ZN($3ws屉p""rQ4Fs}$41䔔D$4Q,M'?UXlnDP84ݣ46>11:66{tx>EZ:?4g4Q3 %\ZB AUԢKXD%QR N%KL DjE C#Npxaa~a~rt_ ZdBC/2,ROlBV?zֽ Ƕ\K"/LFp DQ-#GI@!# 1p⣻~s++}=C(x,FppP~rj By3L AJ:X6O) E$31>xO(-S/P:Pn\0;31ˬJ UPUO߶uyLZD*.z1ca A> m`XO#cG6l?,3鵋ϋJrȧ Bah(2!߷f/-j(6`cpG ^Ƞ@XQ8Qp\+^aql"?RS EVE$:AH%L6ts#=4?qblEgp9j,ƚy3K:sCZtDܷ-qȨcbPÐ؄XwXfbLذsξ(ichz! Am:_z\А4CAϷ#@ A^f5 h=^8z-= ZPjZ#% 8,L*-B岻}M~}j|̐ݨ &R/P\SE?KץƥG,B'E bC$"GOGDF| ^< : űkץvȑZMB!D8͉6@P+\* 8Aj*eF=Ulh)^^<. Xt?3 űA3XfF5/,}@ [mB+K2 E+Wr6,>E,Ui/5VS~m%tEXtdate:create2014-07-24T10:28:23+01:00q.%tEXtdate:modify2014-07-24T10:28:15+01:00,sgtEXtexif:SoftwareGoogle9)-IENDB`assets/foobar.png000066400000000000000000000003121307617767500143550ustar00rootroot00000000000000PNG  IHDRoIDAT8TA erwhlPܬG,bɀ,3vp5;~qNqө3-Ci˼|ڠ׼$Gv=Ħz_Gk`KNpomQpqKC/[/7NEʖ75{U0U],bL^l%9= .5O>fvDՎ&0Rl/=a1Oѱ`vtu%*dB%yBJv.*kՖ($̾$IB%믋EFaGy3#%OHb^ٙw9NiE"" ^Z`J}fj%o0PvJ ,I )DR@)DHѐwC* ~I'F g(@eڮ3U)D'$+ }ё@>2 ,O@!C!qHź@C D@;˶a3V&4RQP0&[? E3!ғ3 #fg$LJ.象$tA*O&ǖ1!=$L?Gd9SO2GDA%S$\S˘%A'>n X@|-8n L?E%h">R8ҏE8ILsI(DA '8Yf;e(a0&A(Nd[]#+()4a,.=H DX?e$[̆-hb{CR*2!gd{XJ Kb#, #?5`>T" NE%Bn_OQS#+B`|TȻXS23'ia2$<TQ"QY b)9"L[EHᏬac$ĩ`$ h%|8p/ n@4G# Api(;r1&paD+[x&[cT#}KiU#:xS+%qT6*&2t;:tn&yLe7 zdӧfaY H.X[s"YʐoЊfȄVߚ"m Aȷr?aR+"pH՝H#]QȦ`%ĥR ٧܈vmQiGHn! QIQڄ=[K gRd  }8ΥENDmP$;bqÅ1Z¤I1QcDa.`a#򎘐GrǻʟD׿=``$T`܌8]ejIL9[knH\Veħ*4*:QPqU}¼znn9H_8f Y9*a{tTu-@HNRu 6A*c[!/1D,W qEkŧ+iMNJ)B{6tU6KܕWYc hByg~[GÀ;'DD](y&)zg]nK*{/*c(X o A["KX%%ZSUP+x;IntEdq9bXʱis5I,NGv= 67) HU`u|rU\OaeT1x$A0-fUs5WN)}<FTg4-چni*'Ro8E`PD\ϯ>|Pܟ~NgHzE[wc`&ڧI_\.麗:=΢?1bHY=$ABxV p9J®'oח8E^|n:wߚQge3$AVXtu3s(ƄH #?KWfNE*Rr{i #?XUkL˃`2[d{#:yYʌ$ c*$V9 Aҏ_g(@ /Dm#eΉF^hJbG!E i T @q\TF!K<ݩ$ͣ 19̣II4Ehb*oĽ2Y$GN SE'IQ-nGv5-TØ"99H"2(NԚ(jF"NB(1@F!?,"ғJ̀OT"#TnXg *AN< P }N0EƜ Q]ƀP2EYGRjb%C0(txTKC"5ɢB$S 2jcSrfbɢպ\*O#<qƱixb[YCQ]@%sǶ*9-ꦇ?rV-E  MAuEsJPqK]EQ OC Pmv>n&x-Hk݊e>aX/IcV^jї)ΐߥ"m4DK¶DGjMLe!ύk6=ɘ555K?uF~PH *v݅>DMżL\b[$>m$LBgU26TfN $F 8RH ,Y=Agu>EUe8+zGXEFg|6UG/۫; jJ>Zp2H~j- `K2Uw8"Zg;}>LoHN|{rv"<*DDfN>ʰ47Gw4QԔ!Ax>ay>V㜽U} w q S~w=g^iٌ<8+ץ\o"Uw~No:C\O5V#:p@d| ) 1TN;sRQ9]v8B4dDx.unomC\@viuҼM.θ|9}n VڐDm]]8|Ǯ%MR̲{|tw;M'q-I䅸1簗wUgf:ޝmO1l'Jr %P'F_"rbtҫđYo e}L`?_B.L*Ϊxe/Y+';C1C>Q?/WF:Azs3fkOţb?u)~;j4yh؛mxDaKNJ i|sݐ VKsw0kۆN7Uj ^ fR[*5V'ⷥ@p͂|P,X!v֬zYqQ@X.ȋ 2)R[FY#n,)a#jnWS^t}o{$=ƨ7>jok'X.OaIg^[4Stڣ/]YDvC\ì~3:A[;/Tk}sC$\yR%ĩ\jqUxVmҏ6T'?zD>ү?; ˭ړd})yJH.t##[ y}][\x?_LzzPcB)8ιZU*b،J^ H*؈+mDwL85ǛcꮩޔDw\$GCL:Č4Ɔ[/E.Ԡ"vztLb#:/Ef:KCX_^Wko?Yz}dx X3Su ͧ@hj^o۵f2dFr"ȈDJ<+Ex[тۚCk/=܋*[SrEu rOJ{_O9d~}eP߯t ]J?GZ>ꮍ>}tM{Cml Ԝ_]KyQZ{,WE KSyUKڏTݺjьE$ʽӝaeSVlAOT7ŨŔF^fذziB$!@<\%GlB ċ:!HYb241@øO*"4% .>WfFPSrT6D[ft$Ǚ(G nOT N@5!.F؉`$Dr2^x.QE/m>[H}dtFw">ؠ}u_G"UR^J;=Fi^VM,/nJhrh56ESY,Oo꧎n^t坾t@Ay :Qb Nc,,m;L*badϏRT?[eb"><%CLZZFtEv[J':VQEuBJ\%*'C0)C2S'~_4z:rns([H~w* et|Xp ;GwW9tFֱ'f8]y =Hb 鬸|xeu=lM6tO6H o vu%-f-!R rS?HA]n1.!! ~R.$V }Ϋ:H2V;(-)TtEe_  ߧ߳~'Mzѝ9g.\ _ ̶|TŇ"S̋gOWM$UUT(D^BYRMl!N>ڠ>;fw=;UU[ڸ. D5@;Gmd)u85RTWۉպުu}) l ԏ> @~7KwU3-onqO- KZ1*+Wi(檣r"3MC؇*MקE Qۓd (2T1#<@Eu6-E;rUmJO#rnNYd)ˏ*|7.IUw˕l {6Y.l4O? ;n@C"xqv/]J:d[͏5~BnrrUAK|i\ȰD-~M1arՓ1u?Olnqm8rjuR/Rj^uE~Y3V-..}i$JLq`WRlRw]Meow{u>[!'%c*K/ Ij^sj/PոAsypzcҿւҝ:L@ >UddbSeֽg׽sJ#rr5uH7E"* uR˗UU]l( /mF8kmHV4!WEEʹ8mHB1$`w#̻=;ԭcaojsiL)T {2/,:ړLE3JQ$ y`]Я=IiZEWj͠oy!Jv䭝+#ɹ=z٩MKutJ]!S5, ??W#9Ͷ_&n-<=lJׄ>K\xwmIhHqڇuρ^Wqpv!yijx쐂D A9[?e:kkOqr_R.K_ BI$%5I<#unUŋVo~Ӯg!/]ɘ sTӫRDC K^Ōer3 'j;d2! ^a9P3)(AA% )BCʔ^Tx|gʀ /&QKXʌFny-%Д2m돪"Fa ,K< ,`dyJRQTE3@#"ϲ(1̊<#V2ڂCϗE) _0*)0wdYedAIY)9pQ! n矶y7Ċ&&=: RJ  &zKF*䓕~Z^8Q{[M~VO8ʵu.!rպH&Ź*bŏ)z"yW.oHSmopjh KTr:[:xk˹ȞY\RnNYXiY#tNtѽmvї|"ܿKZ73q"E] oba};I(}@k͹{ܟw^xWpZhs%R1PS:RIU/:BU{ҧd" T9#8Nt@\SI'X*#1ixƺ.c:+(#gÕu }iU[jo1KM:L- rRb*E$Ie2UkS*۵'ީї/nܴH!{wyI$LR* U\;+E XaiRsʨ5FwN&k tAQ> "J K5CFV}=h/nW Mun{y->hxQW%KnS@H]!TN^uWk*J.OX|@X|(Z4NxҺOK+bdL-*tlI$G weR} nxyGBݭ] yxK~ J#xԃ$R(QuuQ WZ56ԟ +UE ˯WHoeY=-0[-iE"TF2%pxLMȡ%:䇦:+'}?GCtݦRI/~ fKrȫQ:IO⊉3ټ.V^dt0wcd}Xbm "] =eVJoBA|Dj 6vu۰躷Oí@Cxyj.3{*uDx-}JubOYI!@3J"X RuY:z=&NXLߚ2`er{T=B lA_rBRz5.'5Cԑnu46:P%U/u.,`0]G>[Ou18I6$=j`]LaSO@&>~AHG{vr;E:c3-S|:6I$yJ@zq?S_AJ2`hm*bj C+I:QmC{k涹3}xJ~S;H\2OͭߟޥG\;zW}w$hm)+͚@VGn GիۅeQI쬮U=1ϋa]=GSh.M3Q.RVwXe/ oPl~H}$*~~^5,YF-z Ԟ!]3RmZޓlzW[ƶ3j#I':X}=tHЖctW3KwR\G(:ćLAZO9+]Jԕ\I|a0V3 wNx~a~kޗ_bd iG.6_$7'-V|"T꾪i+Y]\b{z <bB~ʮ'|8_ Ou\6q 9J "?;x՘w}4e P3ktQI'1!'[6yۙ}3o(MTX2!QŪr>WWXl-+}kakyK9Bg2*xˀoR/iWoҾ4vL O|73URěUb "Vmfmk[q3dŠ@]^ryVU͹ضh}:OLR>5QU(+s"N˪ƨ LUf?6Yյ"+H[&􆲷-6$C/t:K*2E/%sE՞.EXO<0Rxk bBy GC*nAۗ噐L14,H cD r!Vm`Q0<2 9L!A_TIȦ(bMAa G2$_U,N*DimaMPT]U50?14ahtE@OTIa5}e{kJ)cR X32tb,@{ȱ&(㘈`8PI\>r:(#9r<;#TwyQؐ2YL~bh  L%EE#)vkybXLG*A% 13 D$Q>αc° GKQ$F1= )-h`GQ P%4V̥mec*D7ķ5=4GtWUUjڱTj3QMKTK.4[8HcѭɽC{ MH)I7Ibn!ȳ]o@ӟTsyCs8uE蛄JH8uwF8Ǭ`@F,H=Fma?Wa u.u]BF1"ҏVl%./:ڥ %ot"*]#(vG]X?6R#֞^/ ,Y>`-WpYHEXVkUk6x#Ծ/֚pt8N)wP:EoA]|fg R$G,ĸD-CD D`fr0DGT``Iqt6UPܔfAI@rʈE_rERmxz f($FqȰ&)$6MaVjD`Q7L^|O΀-@1H#O-7kxrxuwP]Jq$0Ff^ H'/A8ӻRWN%L(Y3#~Y2$GQ /XX׋qȔQS!3X_J퉑*`5-̥$[ڏɁ}"T {֜!.?]8yµK*`w2T2R. ڬ"@|ŸiMu/M1r}hZsT{ֲrH&nHyTV@QC*-\hފ!O]hγ&.X^Md9$I8R^/Bx(օ-WV@ģ,y35 ilfqWѐ*xʩ ^B7#t_ֻ5 ^[/ZKwj:㧛u-`ABiPZ,YG2l&8׉nj8*.%K?-)G](SXo,ft?0O"vіd\d "eiM2XRʚpRLPOxNtw^ibJ)ȏq2?ItN~D|2 D}BF$M`.h;\SK!DHgPTC)dQQQ,S/Tpf!ʓ B`$fp@DdG ̄P} Bd$4֮Uxv7IL1"#tL>UCeXȶXޏ|HU1"̢GIyJS&FKyܥՄsUCa1DR9O2m:nhq[M[1H@4!YtD |n C !]{}ZtӨZ7й^ Fx5r*2(1Ȳ#N*jOKT%7Gt̕pkr:p8G1]4?TG_ Ws1d[ULD%Z3 d"l d`&IqP7#=.}UAG!!^~Gaφ{9[sUC> Zo}%`䒶cT M Q JnӪzjίP 5'R:D?P,dɚ*L_Q3.e jeV'Qڵm3T&*;UM3U5Ub*rI_IUD%E̽5{<@:Tےe"kMzq8榷T~ ]n̥!(kBUѼB$.jpv˟ʹʆ]VlAG{R ^ZgcH\%\G1@=I2t-ѐf"s.QR”912fL"e#J^N xDF!DE (yK!)׉V@"{%Idc1Jΐ'ޜ0THd6y'v]"FE+̤@ahȌ33yeJD.MH3zQLc X3Ą=4 Ղ{,0/⊉CR"b#OiD&Ҷ!1<2X8(Hqk b JP<{h<8`&2:C#1LGOPcLK)E1alpH$pL{i+wX%5<ÀKJLQʖ7@H*XeN/HpĻi 0(ȶҀ]_ok(WK|[̙@#2oj$@E}\ksiX17 SғH ?Q̧1<Ӥ`A,;DžNF"`3(ґwFAݘ "`CI,' Ja?3i.4[gyU8}: 6aDU}ڥP.1*jȖT.%0bE$Eds5#$}Sk_~Vq"XHQF$<}5"HsF"/"!.#Q-KN"APn#Qb@((cQ/q:A(-w*JvD`!aRHXV bUy<IH\[/&\:ݨ А41J(G8^j5]GsW;ᩚf^ ,UjȠ2(df3Kו$$`'[\A1LȀ~S% :]&٪*'M3`? .љg%(SX9V@ ȋƻL-j}V)&8u[-7JJnF=^(VO;R꫊"VĖd؛\g\& _UzDBj$_/KZyt@m@y~eiO4Qw`p="K KڂY^)[:(/O,۷zިvQ[ꠋO+FcW' Txש:Ka]n S[BQ6$xӞ^,qCU^ߜ,2Js<Ѥzؖ U_0!G26rpQbr'I䣾y=*6fDy \b&f؀c^#цY`pƶ ?Vm-^li֧ ( kpْ} T FT6y`9 j'M0R"( نkMtw,զPqE>Y٪|_M8lL V HWt;C3̈Bű# K Hx^< 1ܾP%>dcqS d}sx`yAqvUkLt'./4@=yu:/aq竄p,mLJ]*%7Ht[6D3I<;eb`JV ~b)pvwȶ8([@Iau  ) g):Њs5a|!) Y J&,ƒ`sU7L*?婙`6<#g9q_`STb>*C)zv0J2Ϋ5?8UfD}F(/'d{U3IDED>ڜD/MF9xxĆ]ġyF" w)DjR53˸jxI[KOl~#uEG49'>)x |]!.*p1?=p~6}U_ ǭh6}s\r"xKL{Ú\^KcgB/ Mp/gtӞ]5f$eJIG읱 AkrmCCIYzoJds~h& rrKtI(h7ڬ-\LayW|/BmVd[388Vo.$C}zUe4Hqd:޸27iYo~f1xrT Ew{BU5otK@GvC|jV:%ݛl6nR=u,݃S#/omH3 RrHHϑco-rĖK-Zn(_4DljU$pf18vVEU$$g+7Ol,#5qQRY䐰LX n+Wcee~9 )rTb*vhMw 5.X*kN$`e駋0l@~$[`DA=#C]fkaez؏i(9,J|),!t$Hy[ +z0eA5RʷL@[DR[8ʎnm-")MXAD1\Tu55Wq GZ~,I;aB?T'yؕ)Ԟi-PD\ppxmOb@|]kogpJ`eIX ̠! $J}^ʞQkNdBRB !2fq(**vzWtb@˔%X-+^RBGJ^BCni]x?k2I'_/K$N !F BU†o,/VZi(噳%b{OWZ}Q*5 qJ5VnlQ<ĒJEШ,( R8KvN\ ՞ۦFy^Bk K[8'Ib0`zZ LJ,Re]^f(c,xLfB)$]k~\Eu-p+L 11MQ%f{E1&a9*`TҘq2ct?Ö%-k5+jpx^j%PWbcsHTG\dz=!%~*g\1\?u]=C-tO46eGP5mXϤ%)0 Zw'~U Kzk6jKoQ.7UҊw@m[⭋vE7"eצ^ z1g_i eypY`w WߣEp/~]zwY&t SUq宦w]/2ګ}8}CZp!]xZ.E\RO?QFݒzpH!8/]ZůW%ءzqc$Q 39{~nT.z9ӖֻOa%Tj̈ b#݅]V^U!bHDU4B=*fDijD@B22g!K c|)(my ͞ESLf 8>.B Ep譈#aQt~)ɫL/PN>T7J+jʶpCSES.\Jd~XĂ^o&k;.~`{j(; }9UR%[zqW=tbT*߈䙟[5aRg\R?QogK:X@?PC\\_8T}NҋRP_βF2j Oe8G]C靧WZ<I"MB{*ΗMCyIk%𮮲H, J) ȽpkZk CN.FˣYMԹ\R湗5-6($^gNu Jv4~orX7EMF__uQN *4XdNj/|i/-8D+ `J#0|VFf4~ k2?3tvs_4F^ ӻlQL&ߝY)S:4]EyzưX:t {4|RWU%tu)Ju_ٯڬH$2"ևcDmdIAQTq*׾iw':k^\I82xrbmGjEcV>G^j$oGZ,baumstG})IR 6UUu65A~ϸGhR\VR:F}n _ecuM%}UY6^A1!|]IC3{PS7Kt>g!`nd {$~QC\}(v]=}+0 H>eQeD,%%JtUYC9QfcLDOpr3HEl, : |D $g!rVaIPn߾mc ~hQ @$:2h,;s, Ԛ?{VL? ÷J98 e*FFQq%Ϻ]NDs14D,&|{$ R$EqۆqbA8 °[y|"AK[De6]8TIvŌʯƅݭoB=07Yd 8e]Sh [cnQ ھ狵: VΡW]5&&FjUϪzLuΗl%-_yrj;l9λv׍mQp4L]GcC[PzDESn9:Ct ta]I[\μ~5VmaIe2ν_XV73r b?ÍR`̧`}tE+H$5^Uu:'ӻWg|4%]ᤜ_Z+UKHC.B-+5e$|1R[8J+v3u#trn\;?t#?U1b!$d3ΉzW}D@|b?P e;+mg~y!$H4KwFI"PJ_]d<͔]epkw=J=ʡꭅvqPoc_tS-ҶrJt3S˓6W-ImڒuTq!g0:y?-8P^K$9`v-/ugtc^i!k?0\IПQb3)_Q:$9Ml\68{"UzfmI.*gjWBR K.蛿v,ktFjxxvI8f 0úw-&'CS[:Vh;gY'*n p4y}!>5%ڪZn+^I﩯.Ph#JBaޤnq>QmYjGꮲ^7m_JpLx_P@9TL4^FA'RKWg8J##}_:rn 6F˘.{ oQ'ix=+jZvZrav$EH$eYܔMf}oUs#n;f<\Su>\%k[+4OVDq|ωmCZ-*ZWFPN&Ô) ީrR^uJT|[ nHD3Ku}QP; Rkx+YBptӆ[J2/歶WH~͙$2U;gL.tb[5KϢƏ&o$.Hy/]OoM5F:ߨVBU>/kj.yuueQQ˔=U>yw"̜weFsDڞ[T.im._d9/EnIY4i36ZrЫZ鮺ou돷ar-i5ǧ_R%rS &*J*2}ZyLD?1t> Zv{u~%*Cܞ-!jI*H(ﲤqI*ˣeTb|р)pw{ -ᵼ86$|cLY5݉IXbv ݯ6zx[$o.?HLcc]wAЯiw6ǎ%ˤ@24|+EHSqa~*/&W5\YNQD%։tZ]-$&.쟺N2<%Q2AwucrxQ#tE_zBAΛLK9xˍ+!8G쯂 g,#N`d%@BB{љOD E>G|U/,~"x/m~9v'D$$d"$鉑G'iilG8M  3)w\(j$iHڥ4111}4a"b}u*$18ጥnNia-9BjLwVN<@ /u10S4Ij S'3$|}4(M*C9HNZzEÃH&$?zdD?QT+]lۥ7d5д/L\7U>ב^?Uw nkqTsDjT 5${R|֚龅r T!:{CAD3k߯N-m~jnI#U{%xn5v|ETm;R~v;~0T]$(*Sw @ UWZWh6_,.|Wt*Mo5-+@?2D"uܺZbx8&`0gԷP+vVsa/1⦤,;n*a0u+ Y)?p+">ڋ~՛˒I #s-cremI&]ĕTXg~pSDS"Θ}GV'`#(9<Uc9|gV3}'իnĻ#̊wKUZWN.j ԻT겣"\i1G?FN52~5c?WIt=rXBn~q|Р 6e$R~uk7k;j-$wA,4Wr?mnfH `r$V٪iH7頮QEX].!j04+vggHt`n\$#Q.&KiWM$Z/J%"Sh&dr"@d%zwi drsg*3%DR!T/ĩҥtbX~-*»GT)Fjwk]h[\P[J^RآD7ǹqkegz]"j8Y&gDf-vZ18%„ G9aL\tX]-ejBB,cXtm5iۓ\qmȘc&tbH/u3T7"!6GU@D) Ne9׍_gYk'ܢjU5uуlɁD<)Mf_QLtIA$KC 3X0|7/Tn@`d)அJk/ dr'}1vZ:]m 5 xN&6&El1b[5l2lrUPVOy]b%#w{+]׍QY`@*T)=#nճ "frwC:Hj/Cph撉r3]]YoHL`uk +;5DJE%eӒ?Y{R_yS:^ fkaEpS(d6,ٹĤp)6EӒ&F ȃUE)vB{4gקao)2c(ф6gYĦQWeFO]\vxb8*Gxq.$CDڑ'H+#"G%EX(MIh22T\yRDd]Dj%4 %0]C졈HDˌ #'"X0(G6Ę1$VE,/_S09F%肑f$k⠀ w ":QC"$4e1LimJC2h&bm]n;NlmK#5~.?A+jFi#r8{)-#-Dry~jz٠}clKpt])${*1uG[ڛո&1q@eGQ'o@p.9&,l^m?f (K?Plnr6x䑙Jc[H۴!._ք~0Ia%WpdWo{QX@$" fJ a3*Kk˳U0[", WB.P9=֗Ɔx8SH¢Z JWkl3tܞ ʥWTڏwbcX"E$tյozm⢯$IƹLN୹Ƙ&^t߲ ՂU/Թc3ҵhkݷZ$uGj5wTFQs!HK=դ>7*; ItHF57c,E,%ot2Al/[gmH/[j)U,yj6ݥ~tnəjVp,e..B?U CaVmg)"h*,ΦH8nviXՒ bxPJ7Eirf[+`+/¶6j,-Q%}ILI@l kO]:Rh\mB?pj225(~HpP>IEwQ)X>PuŲ1&'I^,ò#T 1.eew^4cmm٥TݬmQc(R8g!?Cou'0·ɪˈG L/$Ue[k[O4A:v !"ЎH錿.u%SE^Qw$s|I~N@VJphڇ5U(RȌ8ӄXS ?(D3Ɔ39 `qvsb 1EÝ*Fc2J4L( T2؉BdB}nnk-?59%`q(q5d8Ľ#JDCz*҆d҄N^'@p}ȉ@ǐRTf>^OG)J8Dʰ!>84R9hJ`0YGn9@ J 'Dڎ݇#7`BYheKHȧDT/D#W# ˿/G@z(Xs#p),C FCF$KnB>αcl8F}ϙ4֙z}f9'.m")?E;;~\oDq!04ĦjpFg?IU-l+K[$u!‚YHKU!x€˜rUJ .)wm8!p|x`&Sa1PVT B88PpBQ.ӑK:pc3}ʒ9jR F# """1gJX@S46&+NST O?UV[G3%3F@d3-;&nCUr/]|H_Cǣ.zɮUҐ:}O0Ii$$us_h6UICeq5{Nj*h07txPc]HD\d.U/PDƹK֌[OKU)3b;FЪ\+_r;fD#"YI%E/Z+= Sj~dH o:W]LEH :H ! JB"PʩeT56}xJvH6** hp:T8.%/\7P5:`(fK g!M#SgGI [ֶ4mA3 JT𴑿l$dR$]jM4RTHO)@ӷCd5{{B5&6/DQWD[}urd~\jص)c+LIWH.x%3w/F,/l*E*~kӢ%ٻ>i{] W/ ,V0_Y$RK:r.5?Qn; MOag\IfᚯxHHTϴimO/ Ӌt5 >6vIݺrિT<ݍ˾DI7nC!ZHt Kk-JK%ݘHknkr\LiNXGw;mUtj@(#xC.T@FiYFfI`"%iHȚ}5km縞쮉P"+Hꖜk I^[ovӍH{TMZmSjٷi3 .EZ?AXWwau[np_kK?h{ I:a:jti\e@㧵}. ER9GWvGr+wl(o 8&vm % t&mB̞^xdj{J[zimn–ߑ 94k@$Sb ^TºNO3Gf٨a"e*|dzr pD 8kwSi(S`Cv*Yz=5hfݘv&턫Wx8wG ߴVmT*o4#O[_KιnkI7nakf|UX3zL;iFtTWHRCJ[:n\|PFD]7W@& }<$&h@b8'%cX*ŷB c+* J*T>Wl`p4C0ʜ-{"42H"ReMbP xĀV"#.%I(*JHYaʍ3Ār*3cF_sXS9Vp?*110N 3& b${ß?gs!4*Dr"(@=^Z"3ib!~&fRc*% a d;ŠC<5j0, ^@HwRbi@"g,GDgXC\%!#cR$QœK\C O˞A:Xf&KA#&]3PV;ŷ=%ê`eaBD)bSʮG8 bq5e-IJp f (SΤlYer̸_J(@ȧw:+|\9\ ݆\T)01a%*?MJw ! ͅ1$ɿ PSJ&KzXCI[!HO+hJȇ&ʬ]?y7SUQUݐ!sbǼ *QKGX( 쉘9v3PaWQ0.(!ꤢC!csCٞ!.i&|.]gC#ℋQT#~8dGQC3!0m;鎧=mB!WDo}U%~隶.?4%WBMƍ\ʫ i*|?Vr!)\yºeSUpCMmIt sX45}/a mtD$ 88#$?5k^DHj__:P1CpY{꿃T2LgAoə@ 閗K5ȡpKYk66.E0VhVYGc|̅YǴ6˴*:k:V.MqbXmi.|ɨHLTK5z='_ ]Kp-TWt坦7C[]E4,%Ѡ:&g)ӄnRXp"r4͟|ji`0^!8"ʚl QIZ8V֪a5tPV_6C kig/I"ym׫3,mU8O s!:.4[_TbB6qp$Q%Jut 5\q-:#A!j7W$~E) wN>xJ(>&y)PtN디(w?dJ*"L_]$[{F])))s+sJpUa9R%ES(X7֖gyDyzi`da-°BĢ$AAQS #Q, UKwRDEC[&#="]ڰ2Wfz]6U3I Uoz%ZiL3bl)Q,K^.jmll$4?< )# e Fb%߈Fi3)QqJmUr(kĤ#.\%k5W"X O`EukDI Fx?lꮅvKvΠ"Js*B'ZneuA x:3͵\9eW:'T\_ݑw[4,?tLPta3LG鷨 ڇ&a)8yS4j\I/{kAG68*]o` ݏoa"7VuSX#Jn얫y *;ipU{{ ԩe\;JS|%C#mV騊! #>UzSQV{mjm+BqXctΪeWGd&,?0v!fyhL9LJ:ڀ6w|GVnڂߦY]́(d@{R}>=>[WTIq Z5 fPOpr o-ZaAI!Lk5TLwEN|=~~ywjת(rIATd8yML;RK:+5J*HqU[QGAr_.&DyuGӅi=Va5q\Fz0,;yIڠ#Oݏ*fE2+xQ;"|iT=c.GZ[5) ɢ$'DL!Bg˸h*N™PS 5>jMXE*Ji8@1cEݹ}gVmzE$鎣7QrQqdJۯ|01*sn3R5]톖L;y3\\[i#U@_Upnh} A!ﮕoIk-ё;EĀYW'hKuB}.PJ/apOY/)ڴQ:QwTY "y>!g 9aorZs=.[˗'$H JjC[/#VL6 ʴQ9o3f,߾OG颡[ʳFf Iǚg>uJ;N_"\ u[}; 'Y}U{Vnu:E+9H,^W@٨G*)Au=6/|ܼ>2;Yfmd$#s/V σr"?XwRW9o?b[+H!*-۬4EI TjΕoLmi1y[H *9"7,%] =i^YNWSIf(S)0czGz*ѱҶIrohA:5y5y&ݫ%'C>ʟK'x!ɲ$\ս SM* \_IMmm6K(V#]*F3,63#-8c]&#+j9^LSUOQY[R8_x}?f6)!ql`],kLZATg"3lꋳr[$8ׂ";PTb p"ƒXmyp)%i$DÒUb0\/(l..m$RVppٻr2Ow3O9_+`qrMa4EY tn7 ն@cqsFy i+"AW`MLF͊#վ; Q!oݘ8__jaG5Ec#V~޲,Ec3дp]YgUDb+HwCIL9 3GCSʺ#2*P(AJu9FIw3dtD)===5?w!ҿQ Nqʓ3ܘW Ib\IJ XwѦcHD P@KmBT T8|3BfY1ՑL$HJfD#3o=Hcĭ"ULOĒ>^5oNZP 9RبWYⴶWJs ɀFy†I(xӧ 8`Р)@eѱX1Lr9ќEWfGꠈJC҉5EO4%e8D~U`~$zn6jHR)Գzj?<6m&UaZ|gm5P \Q#ԤqRgP&jHOK wR1>E,cR]HicQ1DK]%LmkPJzqTP>jyf0mrbT\* ַc%RTPI8PgJ*$z쎦<(hm@G:.£6V* Fֿ铈\싪l;^#av 0ITm3]6uDPp)pԍ+/|,oRҵmQ^߲5SW$d0T"5{4mZnH!*sGk#n`7W@`:FnGTht,KV j/1UkG~ԼNc'D|FghY(Y\"LUnh5K:n.[*jD,51J鵮["M_!)cYRb7OJ8JE,X{+bx?`)ҮYh], ғWH",|ﶘ,"(Ȥ[D_Hw-̀+1Y&f3!)UE:N~{HHBA5})&i. `1g˜pL6[>ʕ!Mu⩚6p?qe芏6 e<.~酳xu7llCB" sH]%u-'59o<|EepHKmIXo5T$"je$Tv;լrfD2%bR]?XRH )Yln3 9 V׭&jr%4 lV}8ke Ct McܙU<$6񸤻A/5%.U?sԬk7bow&"Vy _$ uTޥJҏwFjajlH6w8/B3wW%n|%W1jzXұ$K,@{ucf,FKW3V 6 2Z*i_Jn *jE|X߃$`YAYdg,^ZIqq.TGE7+G8GV*]VWsh8r,k :*{TÎu}dx+ѧ($nV"%ߕ%D1)}4GGȊd]RX a3[r)GTKuNE^taA04L1˸B=ѠFDHOʕHfolhM%|cQ\6p_&s:j٢bdDR?uBxy7N0hW=Z iw_L6wL@aw7nf.QWSj^(8㪮(4NXjulzfb5"麇VU@|eUb\OI$Is窹8@f 0ǺS TT@XӆZFӋK%r-?jF0 6.3+W &A,鮍7&ԍ$ ,gƴn,ZMPc #w}1z |/ԟ$UJ}*:u6}!eL@"i7Wi€[ hĤuiؿ¥pa)ZQ6lfz/𪁵t=~fQ;j&`ȄqUc.|bo# G(ܗXYTNJ2e/w&$Csr",Fbkj7{Sv~dь$JM l׵]XfCl=om^UJH?uiqlM]"o@OQ/ڋ`V)9ҥ84YET$lLU-[c4.H1.ҮYkM\$fX>%\R R=B#RU܋] ǩ+qUB6IaʤTfi#$"C8d5`2YW.*Eu@>Z͒)wס"9)tl@HTS# Tn,ID0*k&`ߟ1\Rkz$bMˉfC9Ơ4rHr;IYJCL n˿ܟ]m{ 3tG"d u\ڃxĞc|ѯtj#H"Q]h$HsR])?p_Im/~O\ְaM 5VJ n_.N_V_݈:^ق?&4ϗ[B"dbXDBAdʢJ姍^RH4؊r ^̶7HKeYS󈋇ba@(e_Tqa D s@ DgkYȡ4a@~a0 nR hBxe*tENT1َ{ȹRoDl*P=E(Ed=WY*GT <@܍/Xʒ_&/;H I қ$~Z)$XGQpHf.a{xS8ICpONKhE/ HgC%awnɲvCAĻS "O>Ijaȑ#%AQW6P#8Q v]ybM\gg=٫% s0%?$r>[^U)c>TJg=XJS pUI=]EL,6QL Q%#9zm1ƏHIg-TsZXIUh"<֏m 8~zXAUWi(X叉S]5z`Ae#!5zo&5X4vX:?UE5M^M\dppH}fUS.@^,Agj+y DkWE>"5r_B. zDM]6t68 A#*Gːj[ū&$u AڱUԕiUzíM/un%{̔D"琁F~uVtT~[aN)>WUU92=OT9ˍd# /aN5ZWU/BE7V?GqDSIBT/]nT8VcPL1JL|ݻ(Fg]B8qP n jGX\teCg(#S )v'"a$SBXL8{b_Dd#JPi8$/%!}to  IcˁR@HTA1#)2I$O@ rg1 z脩js&4a)rʂ&"F,$LG,Idaҳ#.E$Rfc0]M X4<Մ{Udc#҈VW@+ry!yFta@@RSE)B |y0 `R\m n1$Q,;))E%\q9R J ;|%:X1P}Q7]A-B7V&F|JSmaD-HՈSʕ7[=̼z!L%/~ U][h%Wv(ÕA{}O{.ٚ(28z٨$Aҍz-ԥ}ad\%I-0HT]XV<Rx ~/V#"Ȅ=T8nMP=8E+c6"znL:}vx^(l]^%0{e21`໡Ó+ b" N{y`t3Kh,kd5Xs WRkKp1" ?RUvfcjgHp}UuF-樐 ]IyD=*-k"dC*eХF'p2`) ΆL-fxWPudD!n9*==n[KW>9Qr(ջaڄH`=YFRr3D?{qt=' RI mGLo7[-T4 = tl$;FEj]TGVxn;@fBYC*.MGm:#P,`5Z{/7UNc5܁T&DƷ# ;=6Ym5TWʑ#;z/QFި5O"R{_? ]~^m{n3zn܌[2[ߥr_PI5}p ]MTC^ԭ) "COd0Tf"q:4}݊Lksc1U$fkBYLy~:#q՝VZ\jEoꆘ:+[hGj'ɓSD;D5](m..vɿ?xwE+  =,mCV5HvdU~)C_ZZV6KGdE̘ 0$DS%:NщAPƴ^˥}Jo $;}YyϏ,|Mdf!W*TqZ"IT E:2.cY @| ) #G>4D{#D&iA@ L@;.)xRS0"|xʈ#j"0bR E DQwJgĻgXD`J 1t1ݝhh28($d^r DBxBY~Ua/O:VqbCDPE,q<3$w#\9VAeG#TlS S \(˲K1!DH⧕ntRr12.E4 G,a.ΰsj5Y#3{HF# 3{SjRxƶ%7=A<5OGzy\>ڸɥ8ĻK9S&gP*A-K{HJ/FC1X{WCYK:R)HoPG"*.%ͺP>?_J4hhzv5,Rڟ8Q5`/Jnsps{ٰ:5κ-Bi#sƢd MeQ#T>T飣K暐oE?p£ÙuOj\ѐ🢵G\I <ƺ~I8E{EȎZhnҐ];>Լ#"[)&(%'l #K9`&-9DSϳPED^(jKji7S(xl;dRن$p\DՎS{JN[]Ԁ1!?kt']F|& GVgfnԅ+ƒϷحQI5UOHCAV!fW@$\HKMgUߤ4ze\{\ V{ }2W ?.[8ݽG$9nyu~xud_^xa E|vem,.d+˜ GGˠc=U12.F#hT$01@4 v;|I`@9-iQ }]TĻT0cND@T""|G鮵=Wh ;[q91*p4SD=i [QU& 2؞QKK͢|\TvځgNA-֤g\8F7)9n 7[yf&j"UG}l'/΍>3l'&¯KE!|GH  iDNuW REQt˛3V=\ɷ='y)&v:{ O-9al >eXfۺV6 HQLYKX(׵.WC/'ʝY&_ 3Y#HH .x}*_G~c/P'Tua%Waͨ#YY~k!$"#)c[yUº/hQ;Iߪ?晞 %y(w4IX.jۑoq%Ԫ>CSʜKK:ZFz#p~˶t#/nzq\q z^꧚\ߝn"5Wv\Hj1~,]J Rk5̈ȊgE-K$R"$FQ1*}USP͛f HJ`i,4 @gu)1=>$>i@~ gۜcƌM#aXP$͏$Ox@m`D>Rxef$H`26$F}TbEQXMe %wRI2ʇya]ẫd"m(@<7 %Pd}8PȌ"YFF/}bn:n֋Ko4㤒xC%LD27nƋe\ͩq^Տ\oR[ݶڼlB}UOQ,YYzpak\q j~nQ+)ߘ9j)$gCRG332T8 Ϳy$w&Ļ=u-nB '.DƵ/ʹ kcZq!,==[ުHO$n8}U56+i,>P]^-;wN&"j"Es﮹{[~\~[v &83Z^b^FsiTąr1ʒ m,F r`uS"n u\@$e[ӧE$"qFvVk./(D<c[!] 6DFͪ ?tR }G :Q|**i C(ou (P$wȴ(ʺ{XrP^ct?h~7h5 %WTUy%t$bQMbJ-uNif.Ny\,4&[C b7I/ZW v%DϝZoEFJ3tggr&+rL Ҫ>$*FO4Jd< "&IaD1Kd*qO,1!(4 p2#%2PQ?99Tzbi)X{i##r!@dG*D(I-XiC $DQS@@KK*tXYb  $Y~Z%2(pU^yeD I<Tc@ b(K`78Lwg)=M#N,eMK4g$ys+]R?⎦іq&j&UEImI!"/Ґ xJASD֯/%.ҋ:J0|r3Jna ~ҷ'FJGa¼ǙNb9F u}jtD'*|>9[>CHv6SE\jtF ynf!IQ?GӮh>ڄWQ,2VI"&FL?I,OߟEɁK ycT*%;NCݍ`" QK?@q<HL2ktHUD-I3=4#!򉫁z;D^aL0°1gNUȑV2 ~:PI$fR H%"(%@kL#JlC#}@C#ʒvB#*pH#XK)IHf[.8aopB)hѺ΋]`x$~Φ}/gZ7S *u~vIa !?Hq[UIndF E^c~ Ll*/龷h*d/Y;tt}%^~ڿ{9]$\]%H}u /u8/@=T.k 3M){YO\eo|oqQX v\GV/vp-$*uwOM[H5HP$yzm+'ڶ{m"BQ7A|C/pV~j4H@ k.5%LcB ֜st|f T\U/*=:ҮaD-tC >>_6Ƶh@F&YDDƶ-;a6.|Mo1떥kOL4--hrDPsU ^{N(Z׮_*3܏=Q4=5",HD1Ƥ[0LRAhӿ? VZ-ȎY A2#⣪^ ~WL &Y?<*/u/Ц epWi+%rBV/_(ۮ*f^슀?xF3/Ti҉-%8F}&&i/] !1#\3T)8D B|T>)mO@)U h.\"T=ѓ1ib- $3T84,PJr hn)HctRXL 8s:0KdDcM(CtTG")lg(~[lbI/"1"JDODU-Dȏ)mqJES4S#OēGfJBG._hNsS P,VERP3C*RgE7#Y = YD $&;AC9FELĂyDk8noW$K% ǀȱ/IX "$uj nD / GwzkGkNX)i: +wye*űS$%ͨەkc/Ikj*%^ZQNs3U+EzRP\kwO],?k1Hͪ9p2χ;$wb6@B.'9nD=X4WK3zT4=Dx%ը+#ZZkKMvYܕ$US'P\Me \+gZΒ龬5S:Կ/k^J֌N7#wtKg8T0,N>Q$ Ǘqm`o&;Uݎ((fVL" 1s) r#C$b1,cF!bi9h)6吇w)B#F9%`H !G)+E^H "d=2-)Ұ(a"7@QQxcD!(_Um&Bm]ziuk ,M͵580K͢jMm 8ŧ~~)F]_~9*3=duqXhŸf)i&##_'Pmaw ~nW4\E1/IHpONxnۧ]@6nLkz%Z? _F*IF{Q-Wȹ•HL*WwkkP%L sz׍e ٮf3Ѿ!%U3@Z΍;?Yc|}OMrYF2F# hhJ#"FLI4q$ûtF"1NEJږᔎYё A0A*X OG i*J eRBXI#T! 8.F"s>VXP R ~:JB xrA(&(@D/b{;XÍb31*0"(wL8ғj{")*G>py.2)ELv V!w)$Œ>e%DD@!&QLe,A_夀e?6"Q{/u)5LJ PW.$Ia}4a )?.G 2.ϑy*& #/yRq)5mcHȡumq*!*dDEE: BD,J4Ǣ]ǯzGu  _~D9H(⮀"}D@YDU_ު2OThK2?vP!}t:UXދaI @l-A)T҅`vJN3;Fdx}yao1|PU z+[=MXn(B}MNjXɹǝ_ֳN[fB3[("Ƙ0FrDPT<҉#Ud)K<"Vc]#Wnr)֌3(H(cXj@E3 GRA &E ̣JId'9ǶB?K1Id)"뤀;0YrLD ,0"@7O:PDf$cIP@H JM/PCP! qk$HȦg( /:"`~ '7FK/](7 r)RH'^i!@ġgPV% -""12(`DT1{UN2U}1TM˻et%V& c>]i+uE#hJ]H j4B1`(fC8]Ɍ 5YrR>RE )꜆d@IFBrC(#۔~vO:ڹl[jT3TF]DI#@=d+#8b!'{"ވ7Ɓ4pQyK~mnTuƔ+ݍfy$=*WV~0[ٝC_>մ|?BJ^N?Hw\7 Ǣghe >? ܤCN.,U"=T.Dg97ڎgx ٰDLcQ̧){舠d2Iy%IA)*H` ӈOCt Ҁ "L"塔> #M4=DxN?OpPF.s~ZuPS2~|׬^,|t&/)\|ťDޑOH =x/J@@'}MڻPz䫗%'ڐzCqeԷU}}=rtn.ܮJR"|*-Er##$XK"XN0,G7nǻ*0qGI#r $ipGVo閹M]ܐnd:K6]˴#OuBg%dk~d16]1 c[@A|uzH{\1K"TMrUFz|F\XKj>tc Ryb"aD׊&M_vH"sKhS !<IPX*r@D e c VIq0S'?!XJ ]c^ 0UI%3Eq_2Gr*IGf`;0g,\2AP/P c YRĄAQ$r!1f4. cDV W2o S2K&%9t9BG)MUG13.^u@ KTdf%!E00H}d]?DH>҄QsEQE( (LȿkBXS_%XbJP'XDOh8}rGMdc8E<$e8Çu'!f(H |UrsDk \}>?"ƽvL&F(%UOHtSzQnhDOۮ~YA%UAt$?5dn/EƫYO=cd9B K/"TG WQ9PT=NUq߹Sg,ʛ1&1xaMU1ZS/OPWBS 5848h I=V)BxÔ)("C%y hp<}4 '!>f$w<8xB4A*@ja# gDby)>#@ÒTeĖF9ĨylUHۘ2 J?5! b_h ķx>0"]&O2")+8`GƣtnB)ǒ]H!&I>h}Ķ%$qЧ` i}N yٜH8wpIi&A]9nq=[*mi%}A"lr 4rZ)ڴD0DYUXV0*r%d^Dn *12.[K '8g'e Nq@24 fPq%24(˺=PL}#Ln)`2[e=Ӕ2ba$@<{I璾H02)5Q EKFXq'?_*(TǴ$r +% ?wXVc{Ȅ75OyoכwnjeҐ"?^-uy'Y.DBݞZ Ȭ֛*#?\JO(~L#q&dP0jDp"*J)@_D"8)T#a i?E`0`DV("O]DzywFQ( `57d2(G,r#,H8:qojo62[tB]dp&x qNBx$$/IzƆ0,a)ƌ e">?8b>RFp(2kxC+Ya$q)bG!VX"%c.҅ 0RF@>QD| 9J^a *HHw@33@ "k f`dԌC顐lH!IG[BSe)K=#1m 0?U%)$qRrK">UcSxX>G> TݬVKk!8% HQE $DN'? ztO[k+Rc0/?MAx`襏=CzIu[S`FB.ԸO:z7OZlmVSfmmvHE$m*H$l=?!q@ h]!45Ed }QiO38 }"(ZޭՌp>C )8 _q5u>f_;e +K3:j m龉u .62A $9כΤ\5Nzo.Wusr_tfU&%! )  HAP9F!";Wm`LGxH:d#/^tCGG]7PVȡ`X;D"& zi@̤EX%ʒ,2i,h G*WhH0DC!n^RegΒ[ H=gOˁRN4Ma5Q.Iy }e?/b~wd|J&1*Վx)p)S`-0"c hF'p$o, $9FuIVοyIo3hE=EXĠh!#/[f"[Z:'.Kקv$4V>~ܬ%/9Ȳ=)Oj-2黝6D?e«.A{:twKhV2y%,%QaGՈyUcƉ(DĶƁKrBRQUFKC)ꌹREP2 P HpOzVJc>DPqLDGx3i`RGhv꧍1>5'9>Nj2!TAԼ7:U-?߹$;E>-bd_IĿ{)\6waW!1 9G"/TkΖ^:vؗq_Ĕ-&_Y2 HS09S\Cj0t) ;TV"ɿ ^(0e9\DrkZ,qF ܱn*&ygUO\?s㨺-ѷq"קIRYw`"\~&u:0$<ı]K|āS~qg7%]&N甕Ȋ?pĢ~ATUE .l((e̅>t1=t#\L Ʋfi+攄UCA$e[))+,DFN]44 ˉxF`(zBTQ`I1Q pHw4ȏ:DO S2A/ݖ<@*Y*$<12a"F#>.M (LgKIP-@%J ;Xv,=vpc}c^F6.W6C8a1]3q7F!5i:cK5jd4r4% g7=?QʝV/d]sMj$yV&H ȣVK5}A#jY-8OyN RƕW1Iu> $.#FR{#au{:-EZ6vr$IKbD ҧ ~"=/m eիm@t!ӱNS 85akd D%bRaASg r9SU2[MԁgJ"Y% FDA4/m`{pgH <) Sbg;c=h@q?_@7f"fB]xxy]'=3ҠGj  L~ϕ*BpXĦ&C4eOGt@>FFǼFahc&2wJ>h \i=P,]( R*bAQ PN\ryx'WR/ `dF)O ]6Ώn..EID?RG#1iS(\Lo[Cmcx˾fSuzj24b6:N;Dh}눘4vkk _U@9GuWvX%]'stm].R&[NT>5Rԥ5rӚ@U]͑ªkmhDC3㊤P,fC%U9)q}jq|3Wq5C!a/4\—]DȒ[A7^^ Ǿj9 K۪nJ3 GMv=(_>r5%bq$g;C %;7eպ$ByNB2 TaKô{(8bP- L߱HwJe_UHKKhJ3vDvD9z1^ITwĢiaNJWUI÷(wI!5d ˉ#ǴǕ%=a lK=Xayh *%o]NP]+A5T~$ky ZKWh |0}U{&_\D[|o:OMWMŸ%چfXAM:i1qOĺ9}i>B+"{ʈ^YJxzI ܒi,^5>RXuw~6.325a6U@.,c78t=]@җXEޡ{<ѫ+Чl"l(+o\_I\>Z>fyԮ꾛؂{Y}P`UIA,T sABe'W'L:m ୸}8UNDQLDY(8kr[1,ODxJD;RrHGRPbq(Q, 8!%Q:H@$u‘qdJ@ȡ`X!>P>*Cu9%)QCƉ B vRSassets/grace_hopper_512.png000066400000000000000000015127761307617767500161610ustar00rootroot00000000000000PNG  IHDR{C IDATxLI%u&v"+kP( nP7E$m46ҿU/LK%iX֢Av)@BUeegK2Wi~|'ً6c&dq` rt{J~919={7=-_Wj/w/ο1œxNLe.KRlVWݮLZ;W4Me0sU_Bк!Ɣw)0 w/??rķ蹯?obipnW׷'!Y>,"q,1e]CHHDf{7vl?//nw>ʇ}뷾7?L%v,oPKC3g!^]=߆ݷO|_dbFKWEﱔ,Pg|ťMZv{?֓g?wKcN;<ݝ W:'O>ٶm[ϟt[<%@{eLϧ=bvsw|j_ka==8'ϟ}|7o"R{-7؝g !!ܝϥ;l<Ϧw$bmp8lVTe~fwwٳ|sssyysާyVUU!Ɯ|sse#"=ĸ-r<TbYtBR ]']V/V[m&DyN!!q`2!,RJ1@-ā3jkMUERLf1fj=O?[e۶~.bҺҥKァJ)sf滻ۋKUl=mYz߶պfR5f[3yuQU5#SB1/_}B`\]] 3Cݶ"33n3|&"UݶbVDDR  {%"FLH9%jv!" [k1RWuYw}w{~,sUEEݙ8s,/^_|׭u1c0Vk(UDLӔbbٺn]ZRnp}w;:luZ*o^|9umm*"c@)RBcKr)?wRdVV7D0Zt˺F2里n@Z tsrr]>'_~<9\%EDTSȁ-8V0sINS O>{G?|t5?}y<ƨ9,zS7034j]!bbD7Į nnn>W/^~y!;"8V Qնm=߅"v>KlnS'͠z`H)qR,"BSJLF {$FB4VA ˔r!bn'շwۿ?/s g4Y жﮟ?ܜAZ@5 2QJqfB1%fVBS!N8ꦚ 2~O<3@۶Kΐ9f"Rr&ׯ_;"2x230[{k!&js)籆Rʨ,N! #)Ń>ıH""c&&$cr %v uں.̌n1FDJnw"BDwww"! Q Dtsf!sa`pr1\jD؛!l&3%gD1iy?tYB1M e* 8n*+!c襘p*eYQ3ﭵff!B0l.P[S> fFlۖn3H)1|V˲D\-8CޚLdf1B\UU5ys}kR]{wjÌELB)@%İm?Y}?K{B>ۛ7/?v"scإreE[k[S("b-))Z.oB"L;V}㙪Ҷ;ecB}ՑЉ088PCB~](y{nwyZDimZKHDT>ͶM0ZH99OΧm.K;3CLQǘ<.alcLCϞ>},o z۶&"sMN";׺P%8tyq1E$c"Km}o9'wV;P~t& 9ˉHu匰/>ٰ4M嶔w5f*:]ADDmc"$R޻200#oQ ZB̈D9%xwu޷m{o"@HpPSՐby;1b#޷uc"{7mᏪvq}so[]]*"j~U&@YT{k#!䣊1@LQTR8)F"wQuqҸ}$J9/Rr0sQy:@щl,=}x)$#k-~S]Zkj.GE1b!bB튈j`k7`r7"K|RoLas֛OTDbV-="R[QL3Q80ZMm=%s1sbc cyCOhxD"C.C+P֤YU+.f*97w˺UTZo!bF3f_Ga,muPZ~?kf**Lj"CtۻP#SQU3wX yÀ8J'S޶cևRAi`]W01h1 "nDnb@"r2w)y9حjk魡7ᐌh63;o޼!"QUհͷ 73omӫѣcރ*sw:g~q9Mv@ibMSIq\#(w !#mUb`.RJJSuiN.]LU00fD)QUET!Hyc4Vp}w6U#"ԮuSP3`&$ `mwTvsx/?y|9'f7rޛT@-I@ȥ$ʱ q"(P>@7s›~gonNqzxjK`pQ#IŝCLC5iVo4C9yS@ LMǍdrf 8` L!61 1S:eVc` X؇_{_wj>~Ԑx>x ^l3SDR׾ETU*diB4Sҥj27o jD)gBraa8> $qVD435 4ު4s98Nim<2/2"TUB[.b0Ja 1u !Dnc>X1 fbH  afpnWJmwwws.e]TB6*pw$4[A Fch8!RAJO/B8|>Ut9E0xzDp^Fa%DRD""?yjUU$ |K JB@щL1)ɀ6}*˜E5e|La BL!s֘h7uJ)cm.*!~Cl]V;D̔0i=yݝNۋZm/[@$N7!D"[j]VٳR׳R2uoni l FAN)Pnן2FqF j}].*˲νnbѵ.˿!ͩJJIVwPED^y݀ nC(=y4YzSirvM2_1@j9>+Xʁ1 쑉"R TL/_~?<4O4j"5HT0Bpc! D$2Rm9omJ"`ybj窽X\BmflGgU@2yY3agSrh!2ftCs+%Ϳ?:^W˛׿ [_7>7[P.<)t;y_p1aU*"!B"FYוE\z7C}hs&!AuMǷ1=@pE<r^]T j۶GD"΢2HK 03RnOt c*AݽՆn)%.63 [8F@Oݗᄌ:'OvSs9aʁ$”jJLQ:/nY ʜspD[,NޤB77""Hi^-2ym#DvA^;".v:nN;lbyv3Qo`XBx|g~O~/{;_{߿muS1bp_M6Ii⡗Ǿ qK)"" ݩT 檺::@l?S[itTe;z.eRJ@$8Z!R&"Zyh&2hŶmC S΄hf7 !ٶm~VG%Ama3CѰٕҺCꔘF>1xKk-_۶:$9kDV `*v,{TT5*ظG ح֪!׶Y-h`@"s33Dt7KIL=0Dv߫D]ѹabNSLz}}=J{).}@uĔs&@H)LSl A-QU; mBbB,Tyj !ֆ" !p@v)ZKLImKںjp_OuY@|7_\\\mkBDD]ݭN>rJ[ksw,IDb)灱u5ᦾJv2 cӹn'})FfND߬?o^]/ne7Gew^&{8oe: jU~O}I~aJ7q{|ų~t{>yAB!a!b "x8\>~|+>{ـ#IB`nJ-u]6Hw eTνz+9?xJNgo[ѬtZS"p)#suնiKUp9ODif!Yq غ7/|_+ 7>؁c>>1rŁ0Gp?\^OYU.rD׎DM ^[K)n&):z4m7}&G"P\JoU2l(46b.rߗ&$wYE6bKc+Zo"DB4QݪMu젱6P{!;2s FBduj !j"ڤDիW5j(1b C`yXyD4{?N뺾[%e* Ymg۶!#J)D$* FQ֚Hou|1H a/s(kNx<}< ZF$yk]ΧZvyYԥQٶMM(́W@4HEm۶!TRNMUqۻ22u;/y7!<w{=M4 CHREL ̝̄Í;0ǜg("0"Ɍz;ݕþS"1Ƙ#B(jmDR"B??z6M{Uqbq7{ݦΧo|ޟ={z vOۿ7ӏ? !s,SSp0sUCxMDlf"1&{qlm "̾R|c}s{}>yyQ,@jw}u%ewN^1?82BET<ͣOػZvAB@ʜrIM3C<~cquw_̵ڛݎu=[d$BH!.rI{GycֻWC1nu?90ZJ ̭n7zH9gpF2 1~7v.%Z!"8 Ѻ,ip8 ѣr.t^㽽n8V+c 27L9!3"89SMy7imh3Ѩ)<=.B22bW IDAT,˨y7[THUnFm@>aQLRMn:RX번h6|@jj)1@ZRJ12@"wq眑(4vfkD]11RR)-w<!w|U9 =cImtskS@e/;/>w{;0OEf1n[RJ--з&\)xTi}O.?)IP Zo" (HN)/iRn&{;MSVphUՔ9Jߦiޕ=*)Xk5nKNci'000-84i¶@Қ8|%[0N3'rCƲ㩼C&t&s>Jxu6QUp.KS0p) t>F6ˈ}x<bUJ!<3N'~ȿA>}f?nޕRTtA0O0M?p`Х67xo/ |1%N)&Qj=Nlo޴چ=٦i7o#QdNǷY7km ݍG>-@f۶s)(85nt5ݶm?(+4g5a5UU񹔳ujΙU4)朳Q q~$ʦFHj1=ǗK*ضMbfF!r`VlYز஢}@L{Sa^]^23N;smTTBRRagSĘuBZ3k-֘!Gr赵^/̈́@ݭm]WNĀ#$cne8HDj*H0KJ1 g Eڥb$d7Po9VwD_^P0"*nA#G怮cn0 a#jm)kSP7Ҽ37x՛g?rկ}կɍ Ǚ)M.ܑ̌)~ӟ%Ǧ=@kurݴ5qMH&hGrοNB ,) 8yb̑l- ͵KJ)"xws8\ y.1Dk[*F1i77SnխIÐUݑ9pWN}[':ݝoonZsA:rZ)Nnۆ÷ 10lbpPG"{GvD<ƐcQ”RD fn}f̬UE#0=CA]"D8q@UUՈLE:k!z9̑0xo- smV8_䧿:- .*L{Qc258MSå@*Z߬b* 3F`k/L%=8|߄Ջ]Lb 3 ``Hޥں[DrgW;O~tOܼ6Q0$L!aHm6f2{Y=zx>2?-XDO?9 l9#LgPk̙B Wi m;~w'O/S9' z=Ƙ[oˢaZ[ Fs^ N?䗿YSJ~xmT])t}}s>i6;opwC:Ĝ۶ 6<ƌY#dz_  nnxk!aŸ<pw{+kLYb`j ' ̀6wrP3^C$NG)]t?``"5{gf)MdƈSh%1nD9%8LUSJHnH:mm攘9L1" !4\b o4ԌI|Gg%cmm۶bqE%.# Zk!$:Xm14&ܖeI"4}A'$@юTc34PdǒEᰉ( ݁/| 8awƛb !˜[QčQ99lTkW:fy0P53 btv&318SNSQT p&sJqhG8^pV,rumxbr޼<έ)~1Oq@LIstUMDLfݴ9|ޛ)aR:0G ȀP-%i{wwh<޵nwQ4ܶLтjd;Zϻls[V}~ѣϟ?|C8$=g @\xMUf wǺ߼/^gnx!"B01ioY,6\r= ;1rw13&J!曹wOmޗ5Zs<b붩Yꢦ)%!S)4{5=)wG1F.|T7S%,1Ē>A#![kS4<<@ʽ`ukS3ih`PY03C cHDYLHԤ%@KTp8벶ֆ)VP+33F2g@`^~.FB4ðRJzTUsPd}_1l^ctIwb~yTUW 3 ]?-3IAbI 1쫎̌xG6F_ڬ23".純e&D7e)_>ڮDqnevGoa0!tr&d$3E `V9 re4e\MqB*䶻a2g!i<@H^KQL4͖ZUV80.[k C993C^\oDDfP{f";JpwULCZ֐ZQc v[wMZΪ[af~5IN$jjH{֍lp<*%jIXV*"fּALa m n^ øk)17Nk"ֹF)$HJiU o,UfY]{79fYRkNiqxw3K{ Hh(]XHM-B#HFDdhnTW x L2kq$SJn\5^t:e{)nW͒RNК-sqX֚$ 1ڈɉK0S" 7QZkwLl`̫УnVzIMQz[s 7Wi\DY\BHсIUK[Zت&ZRn?vI,JƔs7XN ͦ0m Y 6D"HV;v?N< CJYXLULTQ[U)0z)TkAaȭTAYzfF1 4[{궗_nL;F>ZirCD$3lw@<#Snf>c)em vic}P?ar/ZfJӃ̬VjaܱZp({L+Rno[;v-e g?Gxus9fnHF$)ɐ@0""g~}}pssŏxWSM~}e?|gλ=MqRzQ2T cPt՚ qbZ;pC<.2 ~O:o?_>Sތy܍ ݀jw1/GURF bi(TM<#y 28󰙖ZmԪice„HÉ t?8(Rm{J}]t>,s<Od5shjSljYy>|y0KN78^^^H{CωQ"("!o唇q譧x< yp@@ XfNbnM{-uF-2X$tQ[C@jjYW#9ewS63ݺF=ڕֲZ "Zj7U8 <~7DF!xYګ0Ei܌#[ U-eS<8xk-&9p$IRN7&ZORR^z `kE0aDBĜEF$ni"910IZ'~'PY}W5A!@wbr>X`ZZ- սVZ/dJ PJY(6̜Uk2'D4 I{)5,Ե֮0 )h?N_.Nx8lFIj;czS uxq|Q V@Gai ػ` 9j-,-e O4 |:-+̌Rdn=̌Ǐ9`F}OÇP[-cowݴڽN {?oǛip:XvҘDɻIս"}ؤuy,{QƬ XDCa1m>{_/?.v}v{Zˌ(Hnʩp}{ffH+fv?ZK|jF<ލb2sN@h=`Ϟɔ2COjڕ8AwGTj匽5kP$$6tጀF`V7\B"vSb&DLS-e%ϱ6~XRz׋Ytgi<OMk*90H< Vנ@fRw, C 2ZaԻk&0f޻;jli5 RNxMbf%`< ,Kza/XA|{;ED$^"I${ 8$}/B(4K=-bqM HY̖e ^'a /_˘O82 IDATiz{34/ ?o>?1;t6n{?\z84mwm1'H@9 u)u93 iog8  fֽ9f234ve>4m3"d$S "9كTّV$tE,hO}ѯ_u7`~:RIp2V[Z "aJfnBЅ+W7.kk\ۭuf:q;퀒!LU]X8 BލP]\@pp47N!{0"p& b QCeD@+c~yS'i<2L )8˝îAL+ϱQ¦\j0adAu@3mmeUTf(Z[vmQK)$-t8iK#bČ`GD_OR8sMb`͝QR@*Vu6?y2߆+ ;XkMk f R[ff6!NUe'"wQ;톮ZTm3 WW8MEw~~| t$T̓ˋǿ?~8m0 Ho/ Oxs]lQuE&JyPcݪW0x\^@m]8w%$v^WϿ=z|}MYF$"Bc&Abpx 9G ԝB1p8>}x -$:/i{" Ř 9gDPA@{sP^Ø "bTҺ88QW7 ܄Ĕ#,U9 l$IN˯n׿/FtOծrcQ8S9Vowߍo엿4]>{k|qoȡnS)1h©,ƙ$Ntr~pr県J#uSD"^<_~bO{?|?oGO`dϙL2A(؁ FQ_ T$ɒՂd]UPD;ieyRN D$*%05%gBweȵ4$ɽV>.=!$$f(fBBEp!C -/x<9)!ޗZB[VJ C9GŌ4\{CM=09$FwB<RbMYQ!31"(oRJmuOhf7}HαZcL KXMRJۋ$m>~?Ζd]ӱy\]] ?G)o^Hü_Jdn՜w͋~uM8o6s;T7frmV dZxmۏ_>>}|8%JW#1$ (@h:јte~OW/y~9{<O'z> u eI2w7eM/Q?^mIRFHWϣTUE3TfZ7QCS2^9\Ց\]*آH fD^E| ̴+{ILOr71c; yi x,F`{W&vX*-E-N)Ȥz{s&Wѣ%B7P7YN Եy (4\z101sFA"k)%ȑJ䩹cqw(03Eĝ$լ>sjP| ~Oq9jeU[[3$Bĭ^Z"o3$E*x )? p*jŒ)g"@6cY 4m`aoӫֹLQKy>҆9!(HD8 cknDd&u5k9H͚5b" oDИEDT9?ff&Lws[T"y8 Bs)u"BӇTkZaN"sh=StJx<)S4;bfi4qi Zk1i|⇃߼>TjvptpV?.ങF__ɓ..caOPVJY1vN@ôeyrꀥOZ2& qȹBCw82P n}3M0$Q֪~{?}xw'ݧv|Rl6OXWpd"Hɮ:wGYizo0jV2卙 4uRZ[ߌ_O^|{Sᖈ/\4MU-ѓCo^VۋM47}ֺkwt0<f@BJ1 *XZE&BDIĶ ca$F4jAjrh5Fѣ= H ƖySkfB{m9"Ա;LvNCbǐlEhcbB;h&SwN6wSÇqrf<ZZ<b^#cP㨦VBN>2tDd]I2.,X;! nmq0ǵTkiY fSښ.s?^,ݬRy6qPQRÎ(ޝ8-sI=eANa3z8{!FLϦi@$ڵŸ2$IIZ1+1K-M4/ZZ'B7 cC)Yվi~OWWWxw?0934"z4^]JCib@çHMp{F"cNx:NuN9NiL2Nq+bw?{d/>7>?' :o^U}t^1!ܿ9O!OɌj53L~l1qn_~_|Go7yǏ:1MSRxyVjPF 8NSNp$$/rHдi~on^}Ozէ~~3r~6՟~2_L}t/>?}ŧ~ol|x?H^_pyե<Oo/6hȂ,IOvLne^bM eYW3xZf^u|"('ĞaqVjbFrd<8\\\tuo֋r8C!!EԚvUl&3ϒnLG9\CȷbN5WsY0{?NlTUXa͗,`SJ8Da!Ix`UE-"&͝E64SHZ,Ak-ՔV=Dc5rZk8DKmUx!baq 3RytCJY.{"|⢵'O]w1Rb;XY8"2MSuVޖ< K-`H5ަ1ZIQZB2MR}N)Ͽ~l&2Z  nĪ-C޿޻o^w/_Jv]O?`B?xѧ/LY~j\VyO/<Ƚ+lݱbPFw/}u5}⟀6>y'4hz_yz݌840N)Z]:dN*e>( ̪^ ff~Kw%ZcYwaQfX }AyD~x1H" wvACB*(Dm6DPĈmfbUEH$ARK! QUwK)) 8G xƒȐRRDqe~7j{ۛ Vr/j[i4RQ챆T$DD2l^5sggfW?_~ )_=1aR^Kӑ̸I ]jĖ8IQ# ʱ1eޥ{8B7^<n!R*1!yեts1%paa2 I&Hpt:z C-z'͂983qH:>{?/>C5ipw8 m {_^>};~u?df679<_ߨ:%=} %}"'a=eYi7~WPxhjKOl..W~{-`!8VՑ ;9|]HLRa@Z$JC*$=fG)ս73'H~9! {X@Y$Es^^(R܀Lc2S }JqDgbbСL n"9$k-EkEKZx֡uغݑ ~Ym-Vh L[ *K)Qxျֺ.!FvZA˛!@zj.uUY.3X!uFЦZIIj#0sC~ţGnoowV#[WuS#ahQJi 4 Hv[5C$lf0ZKUqڌ63轪z<{8ӌH ̼ֆ fwk-aaZ*f2MR-NaQXM Vb}uuM!:"[Qԡ{DjMy)|*ϟ??X=TܐF@fZJB'd%gr쪊wenC6*Bztܡ^Von_}&{;.˒5M u:9hM4  $NH"fՃ=o)t|{8uI{wU'E< uUJH fQihD~{sRrYsfR':/bez3na<޽2i/r]щHFx.k:ae>-[]̱wh=rj4MDf2, IDATi9Ƨ6Č޻v4 s*f{3B㨘nhͻ*135:U!4IZyoX\'b|R'HO`q8 0 %H4f3N(V`ey3ǞQtcibJz׈t #櫖NID j=! 0 "fGVL$WodYvi}ι7nt71h4@"2 %iI&CÒ,Җ%9l끦l!FazlEHI遤AB詺*3g{oVΧzޓ{?@EtܟV3}NM" ׬T wFĆR:3RX(h],{cY5H,e]bbRͱ)417ūd0 /t33DJ !rrrѕ+_ڷOU12:n}px[u[aYxe 9XeF{#2q0P7p@sd,f ]Y@kusݽ7GW7[%j˼Xl3S)% 9?^liI0+%$Z}5/V3ly͜8 ;,Kfwpsv;:\2z}<ך5?k~ktH9%dۇq&.ȼVL_?NXe!G~k]b쪕ӓykup>˸[ۇFƍ}"sq*KIiyq[ןx'rj^(C5-9gH\r} ռh-JLG~) |~0sn #S ?eoM 1Ľ$ 4ɗ.馶E ԭTGXh.%i4P67} n Z&|vvvaGaD\r^YUC}Dba<=K^=5@ˆ,˼,nb؂A]GDbL.^%@^j }ti_cgAoüjl'akE?-hf|~~~ƍ{]r%R 09&oh†P-lFEm&"1J-sAb*[)i59/6i$p!<b Z1 ŬUISrB$HǍN‘pvzsYU_sn<4ϹpRJ1v-T3m>qJ*HZ!<1vM l^?ٝoW\W(sɥXB՜+8JuڷSM H 1y2'p0be'5e=T6u7cSpʠ "!c1.‘Zu0E$!b{Uu,{!֭[_Wo~nxb>xmƕ^|9/ʯ^zuZuefE:{e]ZfsFn'8=ߜm7a}P+WJv&Ej~|8,q̻F+;KjnxoO]OeJy71bd #zhhċo /{ޖhP`y{vB3WJTH1| 1 L/:IL|0B̭i\mf(4Ku,tiYbNU/6~_fn 8/K=4d2m5j<&1=+ R ,0Wm&TNݭRO(=UyJ)fZZ4bJZFy>G=gۭyc"bঊ䱋"o/̋Y1Wb~V -uBDnF9"֌T9=;X4: :: ^iN)U7Q0 $$2N5Gɳ: U=%y˥f =f!dfjdN۝0Sdf4u ^ "Dٙ20aurxxƀā}Y>xW||ܝO_}g~?_Ʒ 噛Q lʾ8͛uZ0 =?ݭ=<<|+_}ʵkWoe)o|泧ggGࠞ%Z#-N˼L5{==;pQrrH::_ԧ-'Rg VC`B(6Nkmݟ-s .?{ j!2VgUq~LBlK9Л~Q3ش"!2cEC[\do(yy [U6xP7fXOV3HMnE@o@&.a/ΔZ̀H4ԖZ0[Z]bIijк!]̴Qȃ;E Jkh2AhD"fϿnnnKyYȂ[^S5UQD.b3|H1P&J(PbReED/,kðrdեKs)K*aEL ]@Zj.%iY r[UѪV/ZR+V.l;$RDVM)f9ypb-ZimV}WNu _"ou ]ŀ]7Ӓs.}? C+h}߯ViZU5H(}?t](L)y_}UydQ{%Ӽ7pКI).41q׊u0 2Suj1t1J+ b5P.F]w;K:.6ϳVێc 98n[kׯu*V5Xoͷ/nwvWK97x_l>pg"Sׯl:Um OvWՖZ窋Aeft!vĔֺImONOĴ5/Dk_|]N7~Tow>^x-bU8D1ejEfSGdr|xA"d_4;:I(j!>"/Kn&Ajm51-us~VRkW4vs!DٞPNݱbhKWpN6mw7\]EtO-~⩏lBg/k c49x<8P htcqܡY˽xWv]I!/~[ՐRALOgq{r[_?:e9vxtY_?8^Z_~L/\?^E;ۻ8Oǫʥ''cAh=DtXvQ)5_λ@ :[*;(N!a j Ɏ`S-A\Up(% {B 8H5ڸZqcR#%Rw}w$=Bjfi\Kisr-b\^U7@1N$A BryX UK's]Jf&"4Ĵ^_B&X87%WU+jPqUŬ{Bfqa^K)]׵.%CF-KiX N+8!:2I$5uC^}zˍ_wρL_ f!f̎ (s1q#0Ŏ arvŸM3`&I3C?sV9Bn];r[U+#䜫H,|ߎ&"LlEеJ]!8!BHy^ZI[SSHD;" ֮뚗sae۱zc\Eկ_9T:/sZ(UDZ2_9q€ nܭW7vZ8v)qhCOXC$Lqb׋3B޽NG}ߕ9ub-d-Rk:c(bGhP#. ںD(%1y- (C $;H)ݽKEwcu|3_Ws]^@]Z(Z.LDkRppp8m^{O|s_z9H8\~7>/y/|??C'2ƴ}~SO.}xcOf|\?#?~||9@/o㍛''YKZv^_w?G ttpV+{bꐈ) Ck%]g'MꖶdNwF .f5(`ΥIX7 n1ty"4nRspV1VJty>n/k:R38"ܬ}틴D|Ծ#sQD_s`TuvBopY5CUoG~ZMJEՐM ŰiGX.0, sRjm)niPr)PUEyU''`.¨Z,ZxODts7Yk=x)z+Z|`@,uOkmYi!㎙SJ26k 5)ڏ5:zVm.pm@h3'4L$K L,~q3tM"3TrvaX:/5n_ADJ gϒeq"247bz{ b)5ZJ)9gkfy<ͥpչ!kץ99a~;[j.:@b䊞R}j(nZ hjJCXA[01AR ({_x᥇޸8 Ly[uʒBV {ђC8u]X76ͥK?S???8޻+[vU۷og~j88`Lcp0{oܮ>S~Ws??? ro/ /p:qWsYnݻ=?{ɛɵ㫒nr+ S7v.h]XΗ}P߭<Qs; YZyp[ 癙\H=0f5Vfa&y90^Bbb6؊KU+KiֻRjWK=又J΂\ff,KD 6nhi/GAm̦#R EB1K)g377mwJhYKk)k3"x VRJnKVLd܍]-jUEo4Mj6vA؃YRl FLͨ 2I%Wyʻq FM3Wp@ley{@0ns}$ tQ㱷] c ]ZYdY21rͬfPj4.9,y*#Da}B؋ar!wV}Csn#b e\|ւkHТH&uy-.C( z1"w=yuUDJΰx-KCh6NejDZu^wD,G:"HqLZk)K'}ת!XqڞzUMHj)yXi"u!pKeɺ"BB km; B,\rviR)NI i3헿ůλyЙrzv`;!Uy;+njݝ <8{N<~gO̟jt4]Wn^#?G{/'2|+w LW) e_vҥ{w~}_~["u9r??kҗ<ׯ;óW-ZNgrwxW7,+\r5rz-jSR :[U@$Rstf!/:딷3~j@{995 < 0Iӆ$Dsdg]-d8oy^FDҐ\Фl+ШAj&U? +Dn<{’3"ꞦޜUm,N-_pކL=iEnNx[Jߺf46t c X*T1AcL>ZЉE]5L[!Q 0Yabx*.fފ1"y Cw>-g(w;MTk{2 O~sڴt]$B>;sa_l$H ̌"/XV6"SJVSC3Q؜fVlW0C705kUG$cf|b)Dl"RJ.u517P a^&7-e2%Chx2&,!Ӯ~j)3Zl#]]K)4ǘjQ" eYm\kfAd4G" uNR(F?}Ư{ХU+v#65giE YKhTW׌LG[.>s[sZm&K%AF$%2tkUfcbu3Wdts!Z kv]7rΛw^ܿgKgg3TaHkGz,8qK1T+㸭nRK/d~ۧvSkM] =qLoy駑W_w^?_}۟oo :/;g?O~ HH6mg}?g2O/ҝ_>%/w6|awivz4e֗.Sb$#;n% c@H`LHf&b]rɄ{HYTRjђK{B|\r`~df&"k)uIsɍmnތc>f}funnwC;s_wn^uZ ]C}[_x\},7y:0!LyQYxY5碋; rrƁ6;"5r Ggi[D4F{#lt& &Ш ij1* Jl}-#,"(3ؚ#ww]3›^USlRlY.`^Cc ~[fɏtlk8#"avt77Ӌ"%սF$!Zj.S ߠPKyJi7~7/l6nKyK˲t]`L.@LAy4ٻ8t϶;buߞ&TKVRKJUu nX$<]fYf 'Zus$o@n)YэcJT\HLRgԕwbf|a| 5O,!Aj.t}7<~{e5 !7c> m!0#!s9PHt0 mc_|'|!`9\_8DRSwk7o/Wc,z؜<֧c9 Ax ǟ){x*X֒K܍E9.%s!aw_lY҅= }YSgfBfR՞Pbpeɪ&/ZiX )Uуg-2>+D@ YE{7czN {EJJY(Z[X£@AԥBؤUZ#m (*7`p#z%H 坈$bv 4.:U@}vur"DnU})$P[۫6M*0QEM:NJjxA=B `i̖yq냁咃vgjZ}j .nI@MĢ]Ǿݡ![矺4Zs=7PK6'25($G +j9O.^ iZx sS1W4qk B#SCw3#2zݿn]Z.lv1\uZ%:RMUsy!S3"m'wΙiݸy =}xv3H9E$$G/՚HEB) } \sh888v7_V-cs{ Cq0 2BU du4&G bV ]t|#$^}_?v퉓Bys^wÃUZy%Bnn$"KhnsΏ'~@Kח`;+:10H l"o”Ʊʰ,Ͼϼ=n(\3tZ-ÕO~G>&͹^sȋ!ľ˲lfz>Ŀ7Ư=uv7ܟvґm7gٝ;;kX6y^r!Ru9SKΦ ЪSJ{EJTMaHbnWk=XcmPki vz# O"QJ86Cve;H3 }.}]%fs"ӇnjE"t] 穖bJ g"V Q0 mwD).KRTIVa7}k-MbH$,yMl wfuhpȞVb2RLw4$5Fl6U!a>{>K0ފ)/w|w{C9ZSJm{):P."^Jm y;y^fv*hay< ^[Nλ0~:cV]3X9?JZr@G7!^Y뇧gpMq!E!y;͛W.O9oҧ~ӟ9wy;}CcG_Ɨew敇2*2}%o秧g?{RӰ:;t eQZDԙE0jf{okSG RuW$uiWkqu\:,u1M- $<}Ϗ7(tvI1N-Pw_dzo[%~y}!s dJj>='"]pi8>K)t 3ZKa c(bZ\{!ĘbKަiFwR !Zynjě[L0 <4@pK@]km$cι-֔lpiMCAؾU Q-ƏK ^Lٲ,ãRJ;[a!HsMDD]׹2VJ]֥Ԇn3"Cvv cOi۷yULXBw3Z\QŰc"&b k6s.Kj#SiRZR 8i5ֽc2y9"S`kJDY 4Oyr's`n@K^TJ5p&TJ C kuy@]eAjIfVr%kK5K!Z<Ř%hZ*1juvf:Νo{YZs׀ ՗i ,] !2%D8{pcA̵:"I6s]K8H)êَ+W+gwRq&p jMďyϿ/޺}͛!H \iw?Ϳη{sz_vstȨ%%<[7[KJ![%gV^yׄuwZLb)b0 ƩҬto>w;O8v&õvA{9uǎmb@wٶ1), fD@**SHe[y#5u !MF1ZcD^ IQ}d$`c~S0[cB9 0cB[mưQB@]1`YHv˦p`H &, rXͱA5RIͫ<9+1BAD@rI2jN8~  b1R+8 5 cGYӊl]șJ2INpJK9c1ى&)% )E p.jB;6LD2ӌ]S)uEU\t3B)&u-hW{`"!O/8dsΊOsDl81CJi/ eg uΟ:uj6)Zk)om)i.HYQTMTל k4ȠqL5Vh69tںovvvMoEk "bDD$TݲsaN51ϯmvG5wZx _t"UI r"hYκa;o2vG;DO76̯\ɱ  &>}[9tЋ^=շ~oQypA @L5)<ʍbTTTv>kڔҢ:]n_w/?ųguOg}@qz:h:7ڐo~?9φШb]h8v?N{v|ޗ>| /^ UO6th fw8tmՃ̛%*16ICIJe=RVRs "$́)eghM HI}=GnIi"+ +Wζv?*!0{TOO,H.ᆰ.׬nA/r1M$brAԿr( ##T r ")9V ]W31wc .IZ?^r1VġMn9WF)fH[nr#Zc3cj)23W4+&T] $U"-S5JjB{""WJY$C1RJTqPQ"R 9ak1*b&)R )ihT.O Qnc9hDR\ܶ]ΑY)% 2 ؜CnX$1ny8$h.w;ag{{X,ZVULe=܊Z&o%@&g"Ƒ2A sѵ;dAvgñKsbJ0a"~@1[ЌDrk{ҡ×%~+^}/$p.ż7u]y/1uayu,{|P Qkg﮻^h;-f+/N]u >cn: ޙѠ6]x/~;o|6k_{L8 n;{O]w ʝ-o~n?{G&ۻϸiOhʃA(:pv/}kW6;wᡇ򵽽't}2<:]Ljo\ln^-:"B"`E_ku`5ETSX:$"!A1Q-RDT4@=MEʹ TvU۶i6u>DW0sU14UUmciXlbL+h7&*~W칕᪪TbBŢyJ0eCJKT9kLUUeRLuEQLJ<d$DD).D:+\eEĬ)!1֣%d`A\WURݪ" w;g->̈́?ND5LJ]8ϫӟh. V‚|n,XT bH b2MEv^j蚦QF"2#b!o}ΕUJO ?)>kWRy撄Rܰl [-D^BTq+|6hc VS&<)%I9D21.ĆxXDZ )A@j۶M102NrNmnSLǎ_[Qjf AgPScN!t\ik2Bʠ.e)B5hd766〸A /ʜh5 )dBb# `8 Kwbs]~qϋ1 ]$6˦y7\z;vgwءfǓvrW#o[[W]z=|VL` ]Hk4qwG>1E8th=ẮٺmO81=uj6];@w=/g}m[7 д IDAT<ϽԷegO_ڛwgO>yO =o4=n~ݟgvwwǓ'/-f8?A- +_ٜ`ݙSՍNS;<ȅ/+}ٕO^s̕A[uYb"30Q΢"=JJ1'2%KY %KCs yz5>~ 헨jhkf[4b,dz))=LTXcʧ;,"S,R5GEWUU1 Uɮe(abU۶+C-#byP̈gIU%2\ئrC  33BZ̮"FJʞFEgDrY25H1dQ0XJchSUx4Β hH,󠮐ĕnE P?C 63z[btٵ̙ ͬrpUah8ªdrjv/T $ R9f2#Q^Mu~z!%_KkC$V~UBtTY̫9֕[oPעRsN0VRJr X^R׮Vc Bd29tP\"жmkh0l̏+^zc_C7|_ͭ+\I1yMvuן:s=ȣzٮZԾ%/?2 GW~_Nt"hT2Vy'Hߪ]k[śWC?}=}}>v FO=zcĕo>ozKe=ri6\xcǞy'?Ic>{}U嶕!Lr@V:l<;|=WrNfow{w6-7ߴ_9&ZNMO9@,GDE EPS"ka`0X*ØDUT #jO(X&SXDeQOBnx4:8'`b"3U_?Adl-~rg 1;|h1RozXar(K.go:h}јUZ$0 \(m dNEXrWd8S.3P`HWzy)Šըecl̒Rα%2Lh 3byU5Ŕb贏( UH9pU$HXsNH$ظj7o69-Y*2dE!p)'-)$^E16< 3WUSN "ȈH sD!KUl6+ZnJ*mۈdc\s٬b1^s.~<c1UU1'V-9ỂFa6 4Ok+Rλj?㺓~x7>6xw>O|#^پdإm{\۵&3l37﮻'\1Hξ +$p뙍3g^?{5wm\?:oyN+kv۩?1o׿uر[d<#6ȏ-zyg_vk?nOL7o~ӿ͇}\࡫ep:" 1vgtXo^~r`HO#f L dL@ 1YLF#K4 Mgo񺣮[\۾HM] ¼Al|UʮjnR) R3( t2Lk*!ضM6Mں3um%;gwnILl . wl8 }$夠124 RJup>U+锢J0cJZSʓdXMLiccc:8皦1fMmlY.vа ]@9)FsLPysPk{'9!h]yɹ21bpl*CH 8r`JY+eՔ0"u܅@jUhR֨!vmװ!fD ^rDXI*WƆsŒsH jyӴ1)zދ3uՒg&E3'{Qc7|_W~s m;B0f1/ D+bfvW%m{dFDlKS̆x'Hr"ӏ{r.F>BBO !RJPq"rֵm;FSb1$qAy2/Ca꺶 1Uƌ/th~˿ K,{$PsBУ vvvBFy ê t]L?7Yg>ӧOHp7˦i^_ ӵ5@$]dc>?tCwBxP r3g>O ?y"Ej;~{?x5o׾zߜ-}]&fY۶sΌF#c4tyo^&ehT OqzBIi|us?~o]s)4Ulc\$3nRY_8/BGdb,(fY漽{_U^zrys۶PnT°ZϿeWd^.cxmmmmmmssbRDR+e{'+/Uu)=xu e"-r ]̀h8hһ]yBu!duYkfyUU%@bIBa9!6օ;4Ĕ@vΗAP#u]۶trRe]H4҂,lrJhZXC ʙXAr XCBo֛O䝽o={4a4HdBc'V@+ ;)2DCZP\5G$+ $fF)uehuǘĵ[Da,K&EUBfC9H4@嫕,f3#2Zm8p7(3fSc +[Zy%tt)fG\ 1R0lP]DD`Oש麶7End@pks[6w]w.̛݇ǧO-֣tRWGPW^~_0'Cv7*0&NΨz7O&'^zySklwOaco󹀒$YvtƱ#˿>~csщӧ/#.ݺ6 k-NښG~Wn0n]rn<~s{u]m%YR9k {[T"s~e^)=tHVF&3{ѳ3W+[aqLW W&*`,-4cC<reTvs  ʡ/Y1lb+ /򟊤GUBڶK1AU,Ƙ`cM߽HrNU=(WHm XEe{e>\E!sk3s F$,E$ཷN"*R"m% CI5R(S^0bL]h:`Lx*3$X9 ?"VUUr: ]kB^[[Cb뚘zQs~90`U?rs|(Kmg1.HN@@23PقJI1IfVԂ@IEbF c;lF hT)gbD6i{Θ2jW"F$g+r9#1+r۶ȇO$BEh*@L }6:")3 D5@jkm|ĽԺqU$VD Ёt:~?1F5uU,P&pޗT=ZUF@H9Զ 0+ ѵi+ mu1Rl /sUe߄ݪ9qf#^;~{_ -S۹B(el *ϕS#%]`4i1_h4bϟ.s=rp73o?w<,@hb7?}h4:}X,%c,8}?dx4tM'{KUf<8q\X^}L{7|ơֹp҅DLU5(tWUU˹JBЦi//{JM]?RPA;􇅶e~X;|36{K^zׇ?k[=~z2-]7o9sv}!"0y뜷J7tY70Ch*wΉyJ]/q0׼]וrg鴪ݝmƣfc`5`Xե-.m>. >ƽ g8Hb4 bXX(giq(>Уw>Hʫ0+0vXHHA][kK slؤVvZ()&$D $(Kr5!FD49K Qc!t3v"!gqU~X{?񗗮n"E;Jrr )2A겱H BF2@̱Dr!J9FL3-F7ȐˏUrZff#ɨ*PY;fJ@UdMRDL(+sѮHcdP̙"VBb%!Q˔*溮kW LXB`b2,k…vs4]ZXB& hdq=LD$R$5,UUeᨍRt2z)20xԙ :[m~2̮S_ՏV~'ΟS-x)* `/)/7nQ1&ǐ8g9t'Tϻ~?o+_ol*sۆ`g?;g.0E$_mnzmnaX y0vcRy=gsd@l` m$#CM7H_r^ 3$L,xHc0'oԧ?S}]czT}5(t ;1>vG&.^z4evlm:yhhYcf[[[2."A]S:$sď){)TUE Rsہ2T#_DIUU0 E)i r]i.br^\ѮK:[*VbD1D%8>^xxJHUxD$Dh˸*CC\e5pJ F5ư )z_T)!d<@L i5UM|k ^$%: //ŔRb؅al&R59v4x㦓xk+:s|vM%" :\Yjʄ Aw7 dQHk`eܗY hcC9",@D (buh$_)!B!P.tc2]20cftL]J䜕de \DQmd 2$-15.HD8P\2_\|T:S2_jGjeD r"Vwm -iVPulURQ 1v29C'4;k|A$"4kk).S'm.\kɟxx}`.ˆ{PŌp0.6!d>_rQ2m]:4]Nm)?󏝺[Ə/>Kd<֫1)BU -T"D4h"RI4m;!:thgg5REWI1ri Z-66̜U(De1rJ9[c x)#߶B qyWJbvX5MӴmK+& K47Y ,KJ>LEK%"@X.*By$*JX=ʤDU>&UUYrJ>ZkЊdb9`]Yy |:斛oxK^6.PbceMF$H[KRfŰ,Yrm `/ `**H4v]ʰ !K;BKiְ+"P)T *Ť+ɗ1fRBe};\CL1XR3!hoD>H9Y!@r `>> IDATe$f;q }um8cQEB+AVll\kx4ž1c ؟/HYxf%0GP Ͼo5wKhT0p筵BIAm/aAAA?Iv9.4TS4I9m@J1Ĕr=>yzܥS}_7 P{@ɏf,20[c|IBl h?-+Rek kUpsoW742t]!tˮ[_;T}V= 'sT-+:Xe]Q ew]NIcLيJ!u] 1DD䜛f"]-)gїt!8cK휵֩jl]nw&q1 tr31#u)g*"r:6vF*M 뙁ZC9~Ty0O):7h.Z%g^YrΜHMUBHɍ1 'DhXk3;6+"9Qu6M8-mdiO?:yŵs/#e<9k5H)%UT"C"~ʽs/!t"J&V3)u b!%R"9' 4X * XU0uQ%3S !S , ?0>!HJrg}-"c(m;/ HD2sWl)A+]~ӴÄ87ǧMY~ksT%&4BM))葌1BzQ*73m1s#3DN!D Tc/DX"cݞ>T/O=/s)-yf!b>k SK"@@z*D45MC0bF.Mh4ر֦_O{+;y'=gtqy$̻z2 )ZS"*m``8j]宁 gp,kvKHxN+2]C_CzO$4j0\"v$Vue'!S.9@_j><,5@@ Au&~>^B"e`[MWTEPIEzgO)~; Ke9>U!@E95kvJD1kN@ι U .K]uu]a孵1Ĕ̅\ѹGH(!FcL"ܶJU 1T̳% bD c"+Y)%ܣ@icuxI w!Z!b6l ׃m.*N;)UʗXE\fmu |>3d)H!eis)Ƙ 1 j*3[쭭z/\LFƺl\LmiP6$&vT#[ZQ$DK$Q-:$4dk 'cJmH6h]c~$kC<rNģQ`Vr>O1`qց[,˺vDRbӵ\lDrJAYQĶl6N)Ůx4 QID !xG8ߦԭw]_XKp8pPw{W:uj MƑ)mY])p^"' u=ٝfAzPqE(F5OFCPBMX#<2mh:Vm6/]?790yGEf4,b--mMF75NȦmYkt>0ug@=翉bL֟BQi]D:)PAAsږ}=DH! 䇿W pmr;}֛/?>zŋi2͗s+1,Ѵ eJ<ĥcGHL<9, '0(%E< SL|csn?G=wx]p^4l>}pUĥCjۖwڦiBLWiR驋g@SJƘ5EqJi4o^1٫EqmRlۮT!`X_B)XkaR Z*]Y2Rp4-+bQ U5֔@>Gd ]Usmוn9AAM2XiwD"T*@%cPe6*iE(RklVY.k]J L&bQGFLJhJ-8 5 ܅ZƓzwNq3nvh}0[e3RR+fňh5%#0͋ KB`,Hf@91-f]6qԶ$Ö3L^-h]I%ƘSJlZb9+rΉ j IȊ *"*ST.mjca!*}LXB``!H"c8&i.-{{{! 용CuUUɀ*c1eRQ I ,Wr&Ml[f>yݱÇ|&|NW\Z?s擣h2<݅%gC;{]J@E D`2e&x ڎlP].ө#8c~#̓4w9[ b HJ%EѢ,h[DFlJUbrrD*Y+Ȏd'rED)Ѣ H @묽۽,?p2=.{Ұ`u^cH|$ !ekOZ,:y|gNzs/]8Oڻ7bJê!O!66ټʓePM+UU .1|8.Cz=g}-?pwSpw|[t%ZΗ( \Gc/"cAD!{b}/]PPƐa)20ѱbmxș>o|1 @8~ 8"3:HS gd 2"de$!Dc6Ŕ<,YgK!k2I6UW9%zꔤ9VN,뻱-AVez%Đ  %&]+w]u]Lpk; ,]AL U@ hHa#"QpXkbLl)YM+h)lDd(1H9Z5 ^SLלEAeQj۶]9번1EY\u6L"%$S60b m`YJ|Oݝ×;OHQ$eH̽ļFQ$k3S 1" U-D=s8Q#YYmuh *@˲)ЩJEN@  NciTBa3#fj:WD]9g˲HIb43$%2*IBŗ^:=97j!h2#Q%TPL*FHVկ,K6oSd09+Q>6M<<8ܱƛgy~7_Pm31DHyX9Ah u iRF@lōλtrtatv]8X 7U4).l,LUwg8qkN\_+I{7C1tmJuQ5ؘBq0U1U`ipa d}ۗ>:=y|՝:5ѭS49gm=(%e4Ei CM4M3sΚdK*:ذ ƒB~F/%h *%-#~wN?4z칛v)F )%US&dbt TD>Y$F"ι(~Wg,^ˢ跦 zjޅ9 R/jϗAY揢ATe6\)+hxj%1-!v]DM 1 'ytycSh69rHj 1w3]AZ#kaZك$K ͲTUճ٬_IVfyhA[km;TUf|vjs1I0'Ǻ *ĨX\Tam2Ͽt_~<?Q8* ڀ4F&./$ʌY- 7zt1k1 (2 , SLڮS Um`YqLQ]c-H [Qe2KdΔKf9g@&Ѕlc`-Afe+FB|oEɥ3#b %vE*(\J2у6 >ͦq<,J7D#Hc k*`&,NJ,3y^6$lWbf+1bHYˇ;G>߻k7 󃘴nQV(֋V`i@ :r>/]co+ձ]*D1.LL..˲h4!6v2W?m(-n,M<;44* F a4y.\n;D/\x.%_be]awuˍ'O_~'ٷ[G6]+"μ[[S.iO|c'LL$?l ppXO)M)t̮4f" #|_L} (PL.# 1EBzײƏ~d4\.dv*DĐz^o9k+IBBlL!a(dV⥫ wK(ҳtM_4D4%0VeUeQ沺 EbIR}뀽\ tFJs`V$>󃜵Du(P꥔c)C#jdtϕR* XT$aǼĜ$6J^0@/Z"jm>˲tE bA= (ƀ7UUB"'+8M m$}5d2:w}kfl޵Kd2onN&.ʪr")'"H1FOHȮd2v+S)j\Yb|VP=c4(&˪"GЍQUrOV@IDQJ`@bEyV?&5Z ![pzVySVэ6SD0!Ɯ-#Q̌QQ!t@Ql'̔$d Q}J !vQd8oL._t{ \̚x}\ؔ2-S'>) -(8@,=d|쩧;n7U;q= Ia07h )%cX2`Ljz@]]굵Q~v̖]'>1@~׻ Q0"Q$.aJ Fեa6vٶˤ]YzPWU,sQ5bӆ ǷFh]>]w'[ƈ>͇gښ)JRٽ;vB@&+./bganr6FYFk %%/; dTَc @a]iXPP|O˅>rȤ5P=m!Mgs)Cٔ}$kE18-P))2S.sU1k~#TR@c^:ДR!dҫ}C<*3ضPc-l( C}>f7|~麐Z?Ip@N1氚u]J"bMŸi":TU]C|g @MkH0;đ *Ce H@̨ B%!ںa]`DD &IYzc(xswowxdsw&@5 *~m#{~O*gy铣= lPhL!byt2ٝNΕ1&)esq|oο5o}7ǕdfTt0nW~ V0/-fٵMi |w/^Mfl (/<=%S?5tڇ??1Ѱ*m@ES5>mDSUOTu]a[K O*gB ؗfӪH7] 9 )%dlv #vwߠ*D>L*IS#jtS|q26 f[b.TQՕoպyfV 3ĔRX6+ul@FvZ-X)I)Kn(50߳ gWqv/Lz ,E-CZ,K] N] MTHP0e/IQqBrbK9 NZӁ1)-ʮY;2;x_?,H %Q4a5Q\y%QB'Chur;?9?|̱^rܹlڙ8\ $tiH!hL .$;"<~`)zq6_7ۮ<~7Oߛڣǎmhl6C6EQMpfoYS*]a7ڻZQ&6_K iex|Z&5&E|pXע)/-T >>=2(zU˲"mfFMPZޗ2rBF$9D9^XDQ"/NU+`0W`XqCǔAUsO`^]d):I4۩ƒ(IDh =\Fd9j!3PL!Tesj$y;lLխb,۶gH IDATXظ<^|c;}ԍ7 }Žm+Fo^!cԅBU cW1"3Yk _$֎F$id&TnƮ*: :*Ij&p\@ۭ`RCp9[ bAp_| `*`bffڄmM"$E@$X,رcl6>VR/^f_TBGE&P6@> la@޿ݽ䵧־~ם`җn .DYk cK2&Ut6-ᠮ(ŋox}؁ˏ<ݏG泧n yK0#rH@&Z&oQ2b5&$2֥:h8XBs\ySoy_yO}οx9 6&@Jm|~um̧S?^w/| ~1f}O?v5-aȃ EQG?W'~ oxzu>n/Lw /]tΕ+{./ D!* 0s Q8i'cḨ˶i33om'kq]\~ W02$eYt)88<(Ҏv.΍55ib4<;%jxd !v:0H e *igIC$`. hj 1.4y$4+g' .Ԁ M-4y@W䆹ڰb2UE:y5EK6F5qT>G2ƧXLe/h|=ܰ:6q0(tgO ~bj]Z^8ŝ7~?gWUl^V--jYl.L/SG?mwl޶1_ۊ/<ُ\Cw}Esa1LJ hBx0Hmj,WZkhED(30ecTʀCd ǷF= x}6kFc@H+]AX4m۶ JBBeL LHsXċٌ51i-]9Q=eQجD̈.k5TE:f7tr!XCMS5CC{tnp8D} ys5*Bc>hYVBLNz6X[ S/6lTU"Ƙ,\ |8DV>;M"$!FQABBӶ$ei\ufыH|٘94N)қV**R7<"pι\,]9cq "ɓ'|Gl]wuzX.iN)i绦1EMжpzdks,Q51I׉H1%--FkGg9}S~,GD$Qy()lUNP:3:B"*e2%(HU`fdCJY4*_SIs+c:`YƔL%Ȯ&l c sP4TuyB01Q\5c#$%,i}J1 jBrlż><!9[bc}ЄϽ]=mUr- h:-  `ģ_1f=%}[Ol-Y~knyo~y>lp8[< Ν;a!"Wu],]Y8~2Dea5òLn+׽ėŲU=h ЙԎj[ @T jȪP!HYTD)=1~oCm3+:?bt10svXY"2 ¹V D&!ŲQc :`:T{oج:uVjޓuރ*a@#"1"!"PוstmS>;ڮ]4 R|;K&/3m#,括e1/Ed%r3w+i:ŮˢL*!̫P#)edP^,7bL1v11k5>z/>[>!fB$bPVBB1 jWM0B<&~+' 9/T56M[̶ /3aD&7)\ X.cV^,M# eI+vE+ ת,d1xEdJ > \,1(B ˗i97__Aۙ Ī$D (J~aa~3~Mx}_3GOy_\QxxoO<W"㢪&dY ŮK+#PNTNeUkGo3g!-Y֗\N}=/p=UUO{ l9{V(n}TؤqZoݢ2Uayߵm+rcQJ I\9փ 1ƶ9<>ԋ՛~0Ч#>{?Ii`ʅpIl)B(AY'TE(P?'J_7oCh:%@l.slV(ATn`XgC;1u>=z R\"ejyNyL$9OG 1"Rgӊi'FJ&yHb.Ga!S^Mc8:(@,_X.0g_UUTE'85)$bdI"bzN̦U paXVutA&}ۙ|9 }+ C()-q^Xi}>S%y]`&RK K)rM]|HW.o?̳^UP IUYU!S6g3\βC]hO>FekCn8wÙ=q^ԗSD&GĖ )x ɷd0{<[5jҠ{OG2H.*%Ʉ Q 9QcE9 LQ$>3뛛^&lv]\c Ev-'~$d"ɇh*6(!ril&8-v!?8Wv', Їְ&-8Ųu@胛ÿw~ @=|s הE3/ἃA]Ȩ^V|%dbpU K/Cp,/_8 Y|ښ-CS,+"3U| j=<>ȗzpkk8;|̱77pt^+jLнt߼y4M1ODXQXPHh6}ַ%dT ,oy/l:[)+XMLU0ovbUr0Ve7!*aTE)۶<Q]gA{ -|&>"vks$Ƽz }:Xp _3~p4"W)ܖD,9NjEp2La a, ua"bTȕKv 8k@{bJ*P%s`"jD6U=h⻥HȰOU$5d%ukpEQ0KLqU5.iH"jj+1y?(Z&IPh^ٳK{??uwtZƝo٘n}'>Ri@E~o~>w_9ؽs7qT>yiO/w?}z/u[b(GËP4*!x["RlWUuQMwyggҋ;lgx˻ۻ;|绐zx4jh$jWtmPvW5㶛Ex8MK_зO+{C}SMl[cl5Y% D[G8Ǥ18;(#tp^k<?o'ݘLw7\3?bl ;/\8rm :S̻eYձ ]4A ejJ$vm?\([QT<keKD DI )|L]38gyf>}:$y Y'?ۦh)y,sdnRvMsND`8kMlS{r ()=Xg]G D,jɱﰒ@eOv9#TS&;ud 4\w]gSn\@Κ݆Sxg2U=dK!'F$Lj9~d`p:vȑސD$&RL>"!D],qeY>y犢"S, QdbH=sQ1Acl霂JmZSdo;c:5S;k'@e0$mȴ>G))^Waj˪ ˽~)67\ l$g RdCb e%Đ%rE@'L`] s*FR]+"}(b֘s+Qu1It㵵d0 ^bd-p4U {*$1J 4拹+uTzʚ lo5y74]MYj*@HNsWˏ=|d`Q]tP-?e0Kqh|wk;P̂p@閝o|F$d:/(1FglJv5bNFy3{ӽKO>vmö aYIXM[k1c5tBRT jX#v.9qGGD:v݄RwOnAA beUƣay,x05x]Է͗a>}3Ǐ{2fG#BOlz<8v bM`L(TD̨)al+g ^pßzh}!!rٶh0U1aJB(AWy*uE>v3a)?ƘKl!}}5`?dU|){3? Y}7bUBYmۍ!$ c36^= HhP"}~dQr1bR!>bYYRL㵵mcŤ1W$l [:QDsxe6 E*{ct+$ )2i!YgY:B@uYJZzkN펛xmrG66$ rDc,@aZ/_/.u1G+b,gYJ"]j Y&dPlrh gl1p]̯?w٧/wa  'öY6&h Q-ǷF7ӾB$E"2Q< 2f (aLʬye}15J@DME<A@vvwK18,XIفUrbA/~Swם9ӛ~N[>Ȟ`*pitd1l mǖf́u}DFSbd$WTp41}&& ,@Ƹҽt9???5aPvBq.|1"(eFAF| XP:@Y7Ξ~˳ϜrXAYm*OCG ]c!h}.fP;W?;ooȑض= 5H 1Jl0}Z85D|I ᕗDy' \#QUy`%Oq&Ee0ZZc˲t1@zg~,1E*":ߟ%]eNĔŲծFY.%MR"L !@ yˮ9aJ%ΖcUιe9m1ۆв5Q&ⓗM[n^{d2؜)VU Āyx从SQ?TSLۻ/^Λf-/0"Rn-J,&cۥrh<0lKVB;y|/~Kґ/iz42pX]0=C9^n;oՑg}+7GCd2^T`e2,ш iPۍ񀥞jJB4;D&M1gzjPk 1(c ]B7iJfLh/lb 0*X8*r8 ) &]Lʣ7Wm_hQH2Mǵ]͆B8.}Sg9ԍyXK0SgW]1֣K{{e]DdX_[vs۲ rH  kʪ,"2*L8:)*Ff, gλr ;Toqϫ:{_ 砛fה`\_|vwg> $hVpA{_???~59'[?s# IDATǯqm*A$ Brc2'eFH)4m^etzs2.gs6U9J[cOh23\|* ozP7={.]s^w_)qJ2 0l?&SBKW?O-8pD 1͖ i8TD,3qAF.c/ 2L+h$UN;Iy3,ȌYRJ ƾ@Y]!bi[쬋H@*/ e<1 ^ s1z+ozݽw>}tm2 ʺJ$3%f P}_\qXG8‡t(DA0$!;I5tNϴV?zZի}ߵR@h3M@X@R`RFIBR(Z"E;Ǯ>tX׭ E\ٹJJkReF=v*.ccUWyM믽fXF~Kgee)3X(u Xg0PS&ljy}NccRV%eJIDjIʢ0چAD$RK!%DX'wvdC5BEPa/'Ӵ!E)Z㊼*Rk#2;q(؀R[) پ^=❣,@wpH CTq1TyGoΆ`"$4O/0?^EMPfPU uL^y96Z]k7ZŵF}QC(QSiEQEO0s{^'hf|Շn㖑roZ cԧ?W_N{M7"CP@*%S$UZsg^:'=vdbj$n-1ffƎ]{O-T*np޽;}ӓӣd$ixJVR5XGe`頶 nP=ʼnKKP]HDDusn6hM꼟B(K@y ^Wu.V{UUhZ9Oи B z*;RD!Pi-3 Z$Z:hCs ơ!9' 'e)PJh4?cWya_[]Ĩ㡑ŵKZ1CedY`4k:Е.p 8\_[Łq--Rq`ZkR֦[]~T= VN-iYMN`s >:ҩ-]ڥ \j(Qe//zVo>;ǟv媫W);wꅩ)A!AҨ,7U^ʼ 8sR*+XS S XyZklݣ[YdP$ȤaF]剋kZoc~û>k23^묝?}‰Ω'hi.u)d?d3g/7ٹgu*\dK`]ketx}?ΟΞʗ>{f~771$,}qHQKe&U Nɢ+K=1<_>|nRmz/Hv(0*YEmd-CDʘ. @XbҚPvK zK0dZ骒~ -X)(CrMU% Q1eq"s.&[<om8^6Z:R_灣ADQrhD3*Y_9)"QFR~bAH(ej댵EJgYW `L鐉RjG )g9tZ%+Ɛ ʐU*iˬ Ik, UЊ$)AYJgG^}M++gΝ o3;':*:ȋbuu'|nOShJI!Rf7y11tN['Z81Z#qg^<4?tMcT0ĂJY ld%7LFlJ0#tTABaVd7|Hrc!0+@Dt'z%Y4%k*M5B(qX T \_}YX{ OSLnGđ,fKrԋ^HR$!$IFR{[!#t׾]b]3_eoԓTՌ1bJ-!eʲ@ȥU`Mc(Zj(-kUJ4jcSa'wo׾M~[{?ҥzc@%i`٩}]m{ftblW5;sz^+F۔rzl,($$,n&Ì=eETU Rʔc#wO(J= FJy-jFy/.kṕ][9?ڹ7(ۇ:g1\w}kr>WD T) Z^@F5:zë^OH:k-!*$ob B"HWr5Z+MbDy"w yt.CD} 01D$ ?b8J2Q3YЗeV6 mǫd׋€1mwBp{@H ?% .*YjmACQ1FO'oi cֆ1w_UB5v`QȻ*W)8gMD 9'gB#J2CG1JJju٬OL-o@PT16-LG[Q CZ a0Jz?1F0EM7]^mizͷW:::** 2DspNDD9Xcm?_˄a/pZOܤhpR۲`to2 eK:ݞ`-Ё^,RJ+|^!.Z0s~%tJ5 ΂3N5gAi_~Y_(g<a `HMn+KXV98FFFgv攭-/FFX !z&CM{`4:ì0@+Z-|skmih_W.UeB@0 C"etHQu^s~F^;SՒ-/3$I(@W.Ü$ QZ?uq8gT8v]/IULe眭5UUc*)}ׯ} (R ;4`E7jA)S"8ۅ'0d^D  YSSSW\5,Xh@`gǡR|gv@2sd!=0RC7wDQ)#)! ֶΞka<ϋ (ueYtԁag hYZa8JQ+idB Yc+ky&W{ssEUqcYdRJYFZiXY(Ѩ *rS76ZcDʼb5E<)iE$l!y1n(A۽&?ņG, 5phR*Fٓ?OŅ姞y  2(oQ̴6z$j8?0k;a3WffrGaV[{:ruVVVA4L^Ypa(VۛI-^(Md(N0wWV[#kK+7s]kًKKQW`ZZ5x \$.l|3я}կk5ݿ/wja62;AZs@?h5tz/~0& [vuhNL(%DTEa@SJ#!"'eQ3?3PijQ@H+e./_hw(@"`fbdQ/thdh|[ɩ񅳧֟~la8y}Y.l,?w&&>VOb)%Dre6: xZ-s{C{JJThMp 2Fu, 3V8X~ (AktP%9~ſ2gvP9$2n9;p--GP=d_joN`@+5yD/B2_=IE)uUh><]Py~,z<5LRUR)Mt>_~>:35!kr8"aX΢`10NY崢" 8,"JD,+gyªR50Ja $s`uH("im k]%\k-\Xv{˒)h AăPj(|s_]\s ('&1 F鬬BB>s{rϻX9,yK+vMk˽o}.?ra.lYOYē IDATFR˲P0ouǎ9ufdh엽̎]GƦBb/x )͞|깗^|q}m#~Qk>uk˻qF<^#3Bis^Pysү\*kSTk~=30sN |9TJ)s>H$NH"c`e5Xl~/ {3~? kRX⬩ʗhE IR$/ 䔐A3oM0>O T+ O vO ,Yz^tՇ}Wݱs|xlGZZK"]< Pr h '{zJslNoM>u~IQv1ĄRcѐ-TeiziFE떷,(-/QP02h`ƨY({:}jQc:Xq3SfYKQRF., \Q*Kqdĩ4 UY!8(- XWMnq_ЖA#N'4~pd]X[^'{Upx#!aJl2;ypE8=,A"ed:`R Ƙ2-BIL X300fk̹lMkP gGkk[IG^wgɇ{7>} sԔS&V.v2@ zj-T"zmG@G6oI{使o_+΁Ъ/{#_rH?:vî833w{`t./zᅵYm~{?zmFC z#g$ y ..?п8~Ӌ+k K~RF+'g3tY}Rm 0͉Psl#15D@,l VܗUeZGg5pJ#Q"d7#F4553g|]zN-PθRZu_bRUɖϝEx/^OizU@xɗ^.9³tW~nۿѻ_eI#7(}SIڸoyN=7sOozg8nn.?܉}cku .T0i1!0"dyUaNLNqn{y[:[OxF/o>hjחu#[;W;n=|Ѥ#CEv^̷(@3Zɇޗ?_Ww?Ϝ9{Ua;~Ny] `hd''HX3KF[[Ǿ|ŵ|[_ A[kqį|FۼȽgt(E%It Y.8"Vxc?yDQ|A J* )jMi K^% Bp>, u"WT:g?IxgYƹQ2 h16}l ^na"lT J晞ivEt+ԛãca;t0 N8R!!(D"/6Np-80kP.[J0lT봬g5 JNBZgݾyCĎɝSڹk|vvb92F!s]iHUeFVUdeO>uUiZP+]UET25$g"HR[Kԥ2YG)! (r@4" IMMOO /溍zb1krFZi4kicgk =ͤ`}cciD0b}666Ƒ* `-EV`ArĂYC !rYHX.,,Ϻ##c`?sMG֬ -v?ڻw@/;ɏl\|{֙o{ozM(xluW=oskYlrb?8rO|C'N:8xsk]uA9Ua[|BH426P?{#d,w3;)kAh#]-uGXQRT~uzt%4mjF)yԮ=cO<]1]54lew54]{G?vǁ+Nh  +]קƯ2G뮾?qxݣ_g>K03o~cx} m]ph^Xވc3J/\ORuϿ`ΩkNeYRx{pG\ ٿkwYVA+%:2K.[å=;vwrYJdFJYhɘYY 5tPS @l5) Ni0 *bco5JߪvZF'IRzO>7e q=ȼEk=9pF[G#=?AːjO!sXYhۭ By6Ǡ_$J˔-Mܦ Í/}{׽@hltvOxJ԰sn#߼p~k\{3kwaʍ7ݓϞ?v9;=ͭީﱥv}#[^{ELkЊX# 5{i}f|]gO rVh94Sj 0 r_`6fPEQ\::B<TYyLp9ꜣu5PƜe7*cҒ[_0(`L @iBP0JsFߡ2$g6xicO?~ž^8֚ЊgO>?_QܲKfJ}fl3NPđm[YYo|7HZ! VsJXBO< EQQ Pi555e@.,kӭtѣAu{S#X}8 RUZkVrz=@wx=ߌ0 (K/tgΜ,˭*ˬ=9{bά5~G߾/SfFgsvZCh͠/;;=;uh#QogT!ERl%H]Ӛ$"r0,nf 9#*=Y*c U38w:6$~@D_[\ۋ5cc~?(j'z}+ 9gojLZ n 8b7A9GB8cQpN>`|x<~%" ){v@UҊqk<;՟A 2!檪J%wy.aQ39 : eAĀq,2ZgsH W8chm*cT+;O?LLLi:$JpHW7~YZa┮z0=)o>IT%Kܢqf&:+z7QuGY+k˖$"%*H l Է=n{y.\<;9+@8uuMa}K3ibRi%Vt .bh Jgl6ԕtiOp֠d.ǶT*Z2I,Ʉqhg"C@eh!#b)p֣Eǵh;wW79ﮛoqũ^7ϴRbhuu+qi)OMmcQzTK(҉RƢ33X $ȑ{\%@E3V?ܱ_|AH766`C+cFǓ!%BxFOIiFG_u&g\\79@v̾Tͤjiom_>/|_pe7t7ml'MQ8oܷjvI[] SZғ3KoHڑ"c󋓿/k_+/vȡ#~C1#K6;ȇ(-,e5Yo[U]םvsl?olt\k$/TvdUTmkM|"°?LMFr45;drifʀ+ P fi@)>3B()vVw >V!"vJ_(1]c#s1E5~Rzo2$`\CUM1${-Ӵ 6xtFDq@.P{+67OcpXZXc,ɳqQk;.L?$B"#Şy'o7Ziܲ0`o>t`inwy3MBZG!j`. QF<"&J""cGCvu?/OԕKVzK C:Ɋ<)LjcC$@'J iZJ-RLUe -u㠭'{oqoצ@UYٺ+wyy\t8xbZW"u=+-1OS `,FcY"bˆ MmGgJI=*ts6CXܬjSi#KSt;mD!pdGr&J̧Ǧb<59۝܇[vn]ZZ|e{q*{S}r3w'^MY{x͡o+ ߳woe^oȝo8ꊄ 8wj_?<珽!MEqIqZ' x佩B2~~uc|c⇏sh.Fi&: @XA #JTvHBȘXgjaK35nNfֆ/>Nى)mAM'^:}⹿ƱS߀;*4k}|gxؔ~l`kgεZ_|>Y n 2m'9Kfʕ`8u֚l IDATqҁ¾F@=qc#Lt;88z 9t>X&I]xCPDͦR5I;룃)VvYRR@XE]UUi40` O 0A |G"`\Ĵ_Dp',Z*uQag T~cAq0 DC  JvBaӵ,H) =q!Yxd3XI|6I!kfns׽GHNjIDBH<2d=HQ'RJk}W7Ovk,o2f)$&ѨT*RZ )%yu:Sӳw<3=Yl6sP 6iv,Y#!z@<)sPUe?Z^^>u^0: /JbKHˣqi3w!$IC@ OJi)R)SEUyDf7>k<4k[:eY'O:EAFʕ++}Se-Bj6(O=HDN΅#E)&Oƀp} [sv7Nv|0j/~/v/-?:ɚSZE[[kto^\5C}KsEYƦ,1qfR)]xoQQ1 !Im7z F# JHcj*Nmqcev:BބTSL{ ;W_cλo#HovzmZ$7<43;oλAp#hr(j9oǞ;vJRo+ӖIQ zo^eq7Tom\pg>//@<2[+˗{b<ւ._rMRfqݚQlg9}a@\q!ZN$,x`7EYZc8[ǙDChj0xG 15cL0^YCƚdѤE'A@D\# zXك59ZTV7s;1rDdB ܚ<"'EՊ2@ȯ%??.@滑VUMNs.NB|@Tn/bn pO:AȄD(fHÀg_XJ%\o5cؐZ*1 )|i,ܰgiiel:KPI@4$'ZYPsiTc8Eq0$Ƶ[=o\Qoooolnt;8{f'?v N;o?xp7HApI$:XO- .ifUfeZvʷG~x}+8+W'1UT9dY7wzo~~=.up™ř7# R>Wοkv2jq~n߮3߿'^v'rN$W孇oY^dU;.}<[=.\]=yhrQ%zkuڥ׃PK7'aDȸR$e@8$)R8EY6\*ɅB@UURVZQXYκV1h<(KSyۍ #s$g~  Jd@i )1Z+.3V%Z)jιܯ:XSRFhfP"M3꺮ȥI4Ơ*s:IZC!f\_eGXxlx 2=D|1dY7<(M+c hxy֦*sq@0"DySb\u} WΜXDH7m=.GU9v@ 8YČsιR#h|(E6і #4B)sjI.jdB ".`\흓RHZvb07;73dVf U] z,M=kI$2y@Ηf#!\Yו +-< ?V12vu{@`7rSR:|W6T7m?}0q?t/ZWCWLQO!f1}>L4)ícG:r[2t믱}Y7i Rq<Y俓5އ%8:{NtM"1knw4M,3Ɣ!RTX}\1.`0,M16CȲLk(938gPaE"*SI b;VQ)pcU;1BX̸H>5(1B*$ LTuYZpg ZDKH8B As}U+׮\/| qҥ1qd#@($ %s8.Wfss#N߿M)mj $Չ !1IbLXV׵E_5R$ cbq]wvu9 K%2kV*ǜǏ @:p.%BH䁃d!gA0L%O\]躬P2^zDOX`tyu?{խa'k'In]0>4qCK{S^jmonn׮2i*.<"(Hd 1M T Rme؆: YʳC.K+ؾpya_uj7X_U $cwq;b6׳i(e n+8v>S'O+''&VTlX\igao6Co|k_wt|Ͼmo|Á51/pcMf>dGM[/O=o]~Q9Hwmf~i(.`~aa{P ׇ~6|؉n퇟_[?g}o~g{a ΁gK+W.om@òՂ_տՠت_(禧øc_ln%ӊJd&% Йn6f y5^>{Rwfr.VWkB'ZQY@ 0DG+ɹR$IzM85+NwaGB'H\k2lƅR2w@ĀJ `5 D@8E8֚q4fGj"kT B"@0`ЉRZD%I4a{ =+Ηms.UvE9^wP#m':I5cgED)E,{ u'!P8qŕDq!a Օ=sbR l ɥDĀy :)\jl+kV [ްg4O I؛e8!xcRmx8*{Ϲ ney.\Z4T\ '"Ɖ!]] # !5`{٩='{hl퍑{_DŽj/C(Ng_8'׶jTn{^?w_$/G_tO}{hD[2_teeeu%(SuAJ!1Zpp:I7G3 SCD[*bt(#PJ1kMSPwv:`W,khMQ c AU]w;)PREkq4&k,GJ.8qPEZ+V:c85?BFc3 HDE1דW'u]E}# c( ag(Itz7|U5Tr2'68HN=jn9gii=~w\ҺHJqQ*9=:KB&1BH$ p֙N={յ-yQ{d˃gƥqFfBPт@3@h-Đq6yĜ |ÊP!s !H0T12B<\Lwn:r}m{'znZPp޻UADRgWW^Aޔ\QLMMRˋWG'>n64L~OSKnqӏ8J Lc* Xqn#%e$191 %;&J!vTG{,811f-2x_UU;X)1mX묵X7r0ƣN蛩"~X)M|hx1U0vN61$IlP$ c9\h!Ieb1z8c\\ .F"+N۳41C`Wn=؏|LJ,EYyiz#k4V#4JeyƕJ$U.WV^|.^^zՍ֨,J[ mW$i9 b d!0`H%gBjv[}!T9r xFHsɹPjƂɉ^67۵0q7s-njdVi\(wHg뢨"x'vlll/rW/\ K ,jf$ ;tx)enNLYh $mB`qJ"8pDhys` $ܹ;">`g輹6Dɑs$sδhT@غ FyR4TSI:菀`rj|XKpy=Dfֶ&Ο7>.?_>R5tsY;/>u?($W2I\A0)J<@Jd`v_Bff4nהL2<:xva7ǟz'gw{[|峟s淂J@˗A ,-xfb'xL62-#-3)Ta 'fJ/$4VqTL \H@z}my. h75,X u¥@mJ)l2OWWV?sȑ{vO,94f^?`}e~bb,Tqu<ڍ?mxכK˯=M{ckcy-Ctv43v ƠJ8!yl6 _۳{;FrNb>1B O!0 X|1c\QR2X D RR5|l;H^Dy}؉E7B;4bE&$!9!a:Edp!H+]8.",jd&q1G>\O 8 !D`YR8V+\)19~79!Zw=-9'Z[(mQL:1VYÅJq$c<߽ɞs~88y}A=j4h})y`2RZ盍<щJŹU5ci&Cdkf+%,nWZ)V)%gİ w(pɤ0 \V3pJɩPâFq1,˪֥Y΅˟o8pñ^ڽki8 u1ff^U8B1A 7( !40 iYoCL .03 hڝvwr<.,BDw#gIaGWiͼ@o=zɥ,S15'NssH܅=TJ>Y(~n]d@`MY251o;~Ȋ?֖}f&UDsFPp>mhi[4={_=G͊b3q셺 "ٳD_ZOr_nX@#9 Q%lU !Ḙ V XBژǨ"iN?Q")n !u]3iipkD$^F1!%g<`@Bx&xMl'.w I Ai)c$I TVJZ:4NjQD ۝!u>=2k#3UL X$1L)>qgjυ:睫*vRJX84\kRQcV G԰sN4}y3fVPUPI2B+I>+}Ցq׭nzWǞ}W$n}D j|)6xGA B$RIے뺬}0R Z;qfꠖ,ɀ[:>t`yV9) ֝ ^JB AP$׍jd4KÍKWCeSkׯ 4Pyf޻+}ۛ964vuLSL漓,[RjȄip x捩 H:[< t\FBM o d)ݵM&hcʆ|$ *QLm,t,Fkegl]ULH-uJđ"/UA}J)dX"O2dU{V&xJǼ Q0Ո3/&) !Aj@' VHUw5Z0!5:>oe٤*gj%߳p#^_)K;o/;OʗK+W+}6M7?y1W_9dΦ7Vl{}چUsӁ=oXǩTvEl)dC\Ÿ2c'/-0$*DL+!$c W#Zm]%{x" ,Y $UU#t&OVր>4M=IR X>|BbSUFw[zB?ؔ{^}sZWE#Bd)bU-ϥ 491O14 0 9gv)q562ꋦчH1 f)g >_>WH֗^pʣ22uZRy.uU9\]kK=fV`QJywAZn޲Z,8+oG]zonV}kK%/jD|;{½{ڧþRDHReK2mb7||0x& "vVDƄV3Q X-GLe9aXU|fsԀ _x;:Sc)$@>}?x, @ !sdOۉpHMݻW4Vzn~C(w;_pq >W? |]\.w.ȼvf7v[b`V+vyC0Un`i׎2t){ HJ)_/~asyUժ"ޛ`K9 PpF?"]αޛ)8@H&[k]$cTSYWU2Rsy\f@RN6ZZK(FNiPdIzKd拋ˋsg_~O,]y)n3 r䙙3S91i#;!\rN~%DaD wJef=d73Q@j5a/i~WbH(o}w )NS~~xOo>GO۩VC#n>4czɃGkHCy͇Ww~yJ$ce.Q9uڝzK]W> `*KGTlZ~&Sf&) 11!QFop%nw>ʈP? ? g>vF^V9Gm=c'bەRcv< ^Zz{W)3s!!C{ Q V1!$L))ȕRy&D*XVJ魅_)ֺzsvs{^mꎂC?Kw_z{_[+%z&P?a{?l7skwwnf} 1P78LfVS%i8@R9!vl&= :j13U!"Q>n7 w9r6RKkm^jIMO',,:7gk!;gDfCc= HD\J=!dn~3^I*D_"smp8]xpM]8:T-%Q^Rjmtj_"}֪WPȳy\f[u/v{9vl|#j}>kQjyf&[zyqQw?xy:J+@Duu =TC`$RJ2]?}Zl697*#tf^W~jn>[G|у?zx?yPD5HDCCܿɣͺHU)^>}?=lnϥ4[Q4̌D]DZ79Q.ښJ7r͇?VӬm joyh^/VyhT P%f\kUEET\ Fkr;J׫ qW!~<)ZVA[Aqy F! t Wr LK:Z>$^ igB!U J!r`8B0+x"tZBofl/9 U*DB$Ekg F0AnI{yEֵގg+? MR]|^ % T1;C*`jvM^qH)"&e3ZWۙy˗/֔<j8χnGA6댤m!Ú9"C'4 j8!rV9 @V ';ZPY `'%&DZoo?7Hb**`I^ ~G=x{?OL"~{~?p\_7?qP~?G;b!tҚo{7a Uby5l6vBHo}|g[y0%Һup޻ DBsJ#:Z!I4 6 q5"VQK+TJk);AD H⪍ˡ}=!~/"̎-SN^;} x,5h?=a|1̽ڄ?13H_aZ-88.I*v ƣ<ϧ1N/Sh <{p3nڵ;ZX)ЉN2} 5Y-8VC ENM`z` w~oKoɟ~_7׾/~{ߛ!m}7Os1FmUWbZi  1 tܢ i;=bS&doϊv (@ĉCh]"/ H!](BpR@.Ym6k,DDQG]'4j9" IcJ)OT[3\;-xT^ IDATF~9$5kϞ=wSdU;$fyaBÌasUq/%n{^;>V6\oD@O㏕oooe[s\V4 ď?^x|ӧ"noӸ7]M>7uwW|wʝ6gwwEGXqf4THī7_p#/|vw_-PMcFfaA["U  |՟ 1N5UԂ+`CPʘZMې0aZ]_V?qw%gLSAD3ӮWpgg8tȒ=U 'Ep:mb'8e aYO|߿UrJPj->PsK&9A'=z@iciZ׀zmOyjiZM6@3"Nd39.һQJj3;aÀkY> ۆZ7VDD|ƘB n.p[_dNJB+뀪(ScmfbVEUU繄1ri"ft/N à*weZ[m5u77כ]Pkܹ[[}|z{ yuVofޤ2OtQGVC^H9 VsQ~8L4π֚s"1UY770fԌvֻ}n˷o{]6AM9?O^}p\`Tc-u^3bszu=yɝ;_bD)Ax2!6# 3fxw~?d=Fa-[m:"N勫3Ϻ;TnO\֙U=۴js1_]]*{_2/-\q9'8ȎDx4Rf~׹BC_W+_>Mk#k XCjU)c^N4ƹM%qқy:Gظ"Xv;?hͥxʹH yO94MDS&V8{~)?l ֭PG5GĈlJenKUj6 CCD19d] 9&Tk1QOQ=(8s)ő41  آc`k|uyyaZ:Vzm)G9R}apZSɵ<g֪q9*l<:Zq:%GL1ܤÈ>1i00sMUNUKv,R"U=?ã圽jfQ_s)fzZTأ3m~lb뀣eb=33c ڛGo`Lh1vnj@c<Ϯoc xy{x}yrvf@UMޱ[Nn?8lΆ;'qa./)02!C ~˜7~:oW?tQ28jW%3P#>v+ڮFpb<2' UK0"HCk%Y^r@X{cԇa W]~ȥA̭DdG_1kOoh`uБ?< +fJiA^gq8rɝ8=q {ϲ۰h-]b+:a3bʹR+Qa>ԓcl6켛ո64!Pl6nS[B?}asM)tA`sf9kBWVSBCT00Ruڊ!N !rdV=<-Xec;$s M2x֩a.6UРD{"Hjz!Z9 #|$L)|}} .?ϳ/=<7GZ 25j.GΚcS'pyj^g~y\9m%<-#ґ<iZGSq o)^8{sRVůMb /$=D[|=$>fUs5R2Pq4>D4ƌT)%2F0һ b LDv{7RRAP%[30Д̭@=PȵBNlA1$~,84y`%#3)j^4C&yn_m_:ϭ20"B@BRCL}ܺ3^gͥL#}ڬڔh"8}ʓ면nb~{ۦV`w;Ŝ1!RJfb!r"lfت4Ĕܞ>}2פp`~7~WÏ垘{ism5V}4d<rp[S4ڛ%F=tt3aFTzm9&'m"hDD& !8nĕOYN951njfGAcf )@_ʹ5QFD" KoO"mp"W\ĿD*I榏Gװ~̌OBB@]/23\JDt="aI1q@ :g ȝ ܷ|:4TUI5/88ʼ=h9u0DD(xƜTaȫ4bL8*w6jZǜ955>Zc 1 cJ)`V,ƨڞS/)+(]ZND$-;-|x0-f@J%P@.s3<9.ٜUP+VkC<ذ +GqNQ8&F.s2'"G[wDt?\s:'i{={)_yhվ@u& .{qgˮ{qq.* xQ},I7÷MEtG CZ#'2ô>n&FB6S##W]Z+"qU'­Z,j/\,0O>K#R2ʇ~~[zO>z2OR~Ko]\xFS^_կ'~̡kw9Uow㡵F4zmE,ܽzYk(Ppz+p-֠h0yĀ^A"r I>~T#ѣ o0<Ǿ (g(1- :IpH1!#ZR}C l8tLxd^D;Sk=???BLcGLTu a18IJD1*Jm9fX?*D@~1lwsjiE(`W!@j*T!I-HQ QT;"ұNONHG'D<>e=E@*ji0yS̄Z^tV!PDC>f="j Pv+sUΩ~TˬU;TT8)ag;j0}~ǔjv1bɋ{dIx^=uYƘ 8fLk9{Uq\$8=޻߽{ԩNDy1p8zui@0fڬK ޺5"c PMTQ&"ЍPԣYmu8Ow}g!\=`M")Z2M AkmOUKX l<)OiR2VִW% )f//_z0 ha5<|+|wn[_| w[oV j>|vE,+Tŕ Z=u݌1gHs>_m7T5%>b+_DT嘘ݽ{ut1n7 SxˆNdG9' {f("R+uSe"R$s-]d6ÐPLovzLU9怨n7 ‚"a @#=x"K2 nS$gd< 앗_a0P#GDK%)g%᠑yV<5KU4H",̛qu/ajRh|~)؛&Dn`ZyNi%Ȝp:M7kK}meح/~l~z?Ů<ճ0t!ǃלfnꍠNm{ 9bmc2˯0OVC*|K{.z}J9<~"H0*bLQDetִ, j0ǹ-b_/\ꂷvUt*]aS 0}ͼZie.@@ HB КL &!`8& R7#E CRnfY@X8ZbM5D!2 8(*1#ꁍ#eЛǁ8!q$f5HG󣖮KB)@È:o}䗶:LMu:7RlIiUNȚT$Wf`5Qc"s>.dz6^D:#Pxb9Cs92)!1rMEz5pp )If̧1nfbZ[5CYk]LC`N\PrJP*`LݍUb f0qZNLzks}$>L=xvv֚J4I JD[3jǺ#B@ **}JahtI IDATtxfD>cٵqf}mWʃ糛n>1f9G)j5t>>~/Ɨ_~7|`MW5z,C^lyuwaZS;G,"aH1DMCs-6tnż݈|i¾,ցS,˲,1Duk޽Ah^<>gZE TsH=*#TX5ȱp ܴr`A%$N5$\ k[SKVDiu3KÝbAO0m!$;ǼQG? S`ã`ٍe0wH PUcCkT -˽HmS[JD7^8cj"쁒07?4n׬#|:'>CubM[²U*?|2ʅh2a $B"Dkuuvߪt*E\Xt]W)ej'ƎTIc *e3DR̆>Vad ZeulLL˗~ÿ\:^J=Ppo~?ꗿr1,̇& pDuS+>g(DCz'MDnED.""%Jm=OE|L0ES`6Q|4pUvSX.ʻй9m#}loM]S0}԰G!/Thy㝴z:Ȼ=}1e^>b'AIG<:|>)F&wwzx'?9Wvr4@A^CDYY?N9,(=co""ӡcdGqǹl fH, FZ0/L XJO&qz~!q|{Αs iYg摕*:OOӓFfDE`D,jm̔}l8=nd|0<bSE",E4 Bjwr-4o)!abR`:Z8R&PSB TS@U[`ig7!BB\\ '2ni:odY'H7fƶɰ4))]hPOOO}]TG?[Н=TM;Fd}Tk/Tn}X:/eBOr[>}37ba@"t2>}=-ryb: K2+/*R9C#CV a`3O=gc8 Mޅ|͵+l [hӘۀڿ:|P.Om{| x%wEދkv+ 7Z[M֦AD̘dTrI8>KwGLDkל9`e3d̮{@$z/u TYm?j #)Ng>!iR eb:l InnL4GDXJP[DPW  q>EDݖqixq ^wqΧ5i!:"nc Lᙵzoi30͢? L-lO6#Ti~j_~oCWK7j\:i|~?ij/hnP*RяOSWH p}]w$hZJH1Tۄ֚zsL6)e$FDAQ2Vs߇1 4%$B /G׀ϱn?{@l84jmJ 7ކ;3(,LǯCoѶu?v=~ M<? cM+&?6 }C!_gi,'y;JI?ILT$(t(Fqn뺲4#O3;SmXPJYo~K-yi:]oR*fwGȷ\t~Ro+}iŜ0wfS05,b)Gc:??}vTmai>{4O/@qK_KEASX0g'r'4MHZf5 qi2T?>*"]ż{4+˗Hez&G@;Uz}i}#ݾ1L#ѾOr\a6xHGҸOەvBqYxSU[+z>.}˗˹Thˢ 1e.G˲\.@;''c"|yX R`N,"r>]1-g9E/O6f+穦y#8ry8P>pDTKSȄې"H]L0=|˺kR(`>(qo2B-ϧn9]==ttc#"K(^/)˗iEd/tsbZeYykN*"vk&̪ 9C(TkWUJ)en#GuTKe.) ˭6u#-xq0e-Pbز,rՈޗ_OX*Q`)`ND,BR u cJC#4@5̈`a6iODo;]3 ODl86@G @ 7SpG8~^τDh/rC`47DX5VC_TN[!O[{h8yjhSD~Ox EGla Q{]p>k&-)28;.FgҜaKxGWoa@B-*Č3:G tcq|fh +78 N3җ}"zzzjPXtx쟟Xx@X91zHn2Mm p}$R-7ng^-i)r:>նYȡcfgi!WC NCujhDDd΃ƪO /9a 衡e߃E`$g0Y/˚Ç=F̔p0 Bkr0smm[# 2yX3Ӗy$Iqf=zFN`1 c/_>!CH (an,9&|"=Ȏ{+j沬 K!}$!u3Vt_z$.}YzQ*NSfk_&m"s]CU$*BpFs;~o>D01"lDDU0jQu[+>| +@;{'Y 2˲d^1ΕA"Ҏq lѲ#[k> O}]Fu]uw䷤MKSxMDd{}%?y;k HS38C6e3"E}tϲ7GN{k?qEp,F]sJ Q#"LXǪ:TeFvw.s靛mO%~XB>ythnCS<:ima:Bx0qe's7U3S!N^a5[{*Oɽ<[K!Fk"LeAR+>s;3\.Mr!F K)n@Aڗϟ1 9&an#B(c1yhq,$SkV˳ėDi2&-"4T,DHJQrGpn*:("V P:ip@p&"Xѯ]kWׄs~汸 \Y@$az"VjoßrejGئOh `YUsCÚ@M;Ep<ȉHX4 aL`+4\N9.?0ӝ͝6L=y|xkZtGgw4Rzx!WE("B&fس0Q=Bm8>]H˘w;o=g|͡;Ut;./qU=88"8[ k5Avy c7=S11Mi z_o{yq=c=LG@0Q |ϧ3{x$h.£PO{kЮ;{ɇiȲuL}>fnQvG Yp@Gqq[3τiŰovjo#N*_5{¥\ !TDJWaO6RcާOpJPm]G SW H mz@DYϋ)HЗe1a`F`aC PP ~󗛾̷RNOxCs+)M3Zuu~N{=ө10 @r\>.{y˿= 8 4Ms%S3я.eKD_{Lsu[)4eFN.tCwa4Ky?#rX>| >VSSg!e=P S8޻R<6ƃ{Íע&|8y' w =zP dTZM# ;,"Y{kvw>tuGUuck_D97d[я1,U Iu{=]x"4fAUԼ 1 OjI }|qѐ1C~s=AD7#c]t:E8&cHTvY!ܲ-Bs$;{ܾ!CZm`6.ןq IDAT˲ܞϭNmo7l?ͱr"8HnMCK^d'"ZzW "à+"ZS3ݮK%P."`kUH#M#b[@Gp~>qcQ }EmdF mn7 <| >=iͺ?TcϷ7\>uȣCg$^զ!jCx $ևyL92 o7 ?XͲo2"!H$C@YQ]V`8 %"j:uf3tEc- i]VsOPim\j7Q>ZL+hXG:'.nt>/"_zbƍR>E_޾0lf*EJ- Wef.DHAKYmӨ'!U`˲@fdi>)!xc7#  lmo>Nsy{hIXPũ?~'a@C쇹,@_2>>갉Y p@>?;`kk>Yם}{*q)]ځ93 `xd),RK#hLCy\ߟ||8HqGxݭ5 wQ/2MS:X01gR3JEjN7'Vk.%X|v;9ʭ&lW(0yoԇܬ֍7dʥCb@fS{0!2H:4ۘq:M’_騟=ͧO>O=3l"f3D5jVLa)ge8rZ.&D;2NnvJ!)<ݷ/|&j 20$:3, z~a bkxg`B bPj@`Ych)7lYUGt=> 1x$ >٣`$H4"m\/`&5m.x',LLL"2VuR+\="tUfeA6Q7# L].j1t: ~?.5:8$ btN;Ӵ1[j?8^<s46C3{}}].%m=FiH !(ydi xo|#`F}?8}aawEqj'qz~v8Ǜh{SK;6}Fq["ܢ/}el\.ol 1BD"E Za+;rPu] Zkל l,Ko[koooGZ-"Cj ~Suu_L+3.HBI5)eg("D8U-\9iQLd=&>)=sk?}vl$8bVu5RKb_"teoooCGݛ+s:F#Ee>4mPk3D:G[\V$yY{QJaveܭ>T~o/kp.~~øܯG%P8ֈ0(Zk": jw^qY.cUt n@&үINv#X*OE& X,Q7uE{ ,Lԭʉ*UP跕Nec׊c"?IBucɞI',qGP WQu#G,=8Kw>J#T"20̓LC~<===MvJ|^|dxab51Z<Ǹ S$}*^YC!@ d;DJQ @#V%I3Q# Q%R03, 8B̪;2ͭJZGSD}5 U1@D%dTs0MH&r@*@Ѧ a"HHו qRJ$'?e%"OUy.@0UtDz8{Iʤ1@} 0=14`1$o+2"u9[w6ދa RrYJ-0M[ijC s#Bv[.̦izEIUs2O蒘:!b,qrl@ikrDxlbn-\k, iֱ 3tfљ=DjՍu:FD"J!v^E*K$+t4&Ё/xeh1 OGg:$&p'y'PF"BQKRJΘ5PMiXd"٘K;D G gd}- )-9]([̠:ᜩ__z2~"hM $6M$qXvػxLL SLd.G.Jejf:(2*Ǒ O1IL3vD$`QO,IJcG &gBk_!bbDwGJyej !ڦΧ<$R{pi֯)j= GҠyԲSY%J)zN2 ֲBF478Rz{#13q-|:He`f(EO>WcN簚1kq]쵒,[Y!"/R2t/Z[EK-g%1֖,mZ1$0 qDl" %w;Wi]1j)p$Ee ,YF}$M30! ^~ߎ SJ%"sQ[   \}˸|~~~yTJ~󻿃ӟiYnM =Q<0?MnqC"76"87)" X\)2\pաidHKs5;v_T05BAV6 3ڗD|,De" r"RwYB.0܍@6 T4bPEĊ!BUSH pW20ň/mJ=M3}BT pUW]&ip ~iq]BᕑdqZqݰ#bE yrB0rs;q%хBHZ<5}? ƒ|=9¾Ͷf|8M#%W3h,vE*M24܋b˚%dxhXJ{?dsSdZ_pvX{/ROӲsI"^H)Ӕ"_GV9cc;:zKRJ_V$v{v>[6g i]{Tf HvUyc+R.xE_Ud fp') c )74#=}!@YEM1 BfARJM \ʦP#y5LAaUm'[8H.̖fI:=@MR4m!svu],MsB 3bjZ___?|XuL4|,t'uJ )?:fwwunc\-Y8 9w73ZY 8ta#<վibSipϡ{˲Rӌ?|nڏԊ~&a1{rQ͋NLY^pI8" C>"0솪i "*3TDvҊ&:3wDcwLBvTU,BoBN 9yXHE14< &HDafr п$ z2Tˏ$I'/U53DDfVuwL$8Xby y#A.X.v1鮪ʌw7Ӈ 1q@dfLUT~"z0Ӈ'&!Av3hDuzk+>k.%4$BJ" 1!y&D|P(|8TT{m^Da9Γ:ں\zyYϿڗ^[DIX&B3NfDĻ'wɩBxr17c,a^{+QDF_~]o4\oZ00AQʵ{ʒuwf'ZG8c 爄l>t57xg;$vo-s‹,lwcZMmX4pEMcw"rfH0fӌvCH)g@Ų,;:Ք4"Λo c#"a"ٿtQMYDǰt<,^CXw@snU0%Y}V$"7o:1ݨmfP|`#1cJݥ+CO)qw'/Pw}: _6T]EActW;l x0ÕzC8眦  "Q.%ĂZrN3+{)ez֭2'ձ:-IMNp[OO__^:^^ЁXռg7#RTсЙJo,+)Y}3?==n7ScZo/'β;PrX^_i$f-CF}[oov$"* qc;1-VK{b6#1BjJf$x<#RJu ʲoUjc@p8QDLR"bU \"V vͧsNB2FGbR^__eiuw>r{RJHG=Fr<)Oosy{o//n8ҟ,TFǮtDDֵKYr |(% kK KYE˒`u>F*9Sqv1tNCo)GrضmD݀jn7ƭ{(\!4/lSʌs<->v}PJqTJrHJ(ԵȲ"IUá)%\k\?>7@#O|\CȘ|N.j f$tuBIBݺ2Q,KZkeʔRRt<_42Ok]#Ip8r}=NY#Faa4MRgp?ddnޏf74,󴇎DշoT/B#RFym4e^ц7`X@pJTqmvy@.97 IRzﯯFD۶\ z\ YR,El^[-6Qu]Wv\E$VXacxcYC譵0sLHfȦ1ua .Wp0w&C#Z!'I)0|}S[n/Kt̉zHĻ R8mR|9>8 0Dk SOr"Ly719߻̀>8ܨ 70Hjn0G4_~_^[;R !fšJnDwBH8pPxÆj-d#4t#i6NKL}|8. #}:22H)YiN˴K$;\HL$S*\7e9| ML@ n۶m|:.6师06|h8uHy`fm)ED 1V[N:j&뺊5t /yP1 =l3U{* Q8 |8/q 7hbn2"\,4Ozc82M#eTu^v2p0Q(O8i,df""4FoxB89O6Jf6+QUMd&.PUavc L"L+)Du2֘kr8M|鸹 FxYk6sDͭ37 8!>CR;"{nLLT=oc'; V""jF0c'MI"M:# >m 8xM=QbкHJB{`$2QwS:o㱷ˆy)֘<;'2qڥqH9߮WD4Do2*Ӣy/9g 9ZDNQLIޕ1g9bXeOЎ`/"bpv#7{}!BH]Um̢1H)"4V37dfdHf!fB ]A'ĺ%P:fn@dI;[u- 8(Q&vn @݁HP)dNzt#($'cI`XCC{ct:0^__.% ۱-=y*LЗ9}43L0HNYq*#az}ۼ,9_|\r&a|y5ħ2;@i2ۭ 3K4aQ}X2/q%Ġ~8,5]3Dw{'Kڵ͇,i) ڪ(S"!qJ))u%˱<9"VTDRWimnU2`wu>||kIfD&fl2)q/>?=_/___VDG`B`B#ww[mZ<."yfM^:FJzSMB(v1G)Ii P0stPU572Uv$p2+Ɛݯ7wXyYRTGkM>ZǺjSp]0JQ^wo Hrc;0z[d Gtm-nӉMGOkk{61THlF}S,1fc "dN=Rѱ̣Po9 AD[(%;xs݂ 7Zw\Fix׆"{HZ AR5z[>rRh b@qubTU;#7}03{_h"Vj}XWnu^/CJۺ^](@Z0%Bzv-Wm43p/8/C}AD\XJ`g[#smpLJt:-OK.t.|~8,C>*SF>jl%O"b t Zm]Ah jk]e.4E"GsnR<!GDR#b`JRrsk]YdA#hgOx|u"eZo7nfjCyO) clHEa]|L+_o9LV?|}y~mnRֻB*a~gwrH(Xs$)O)Nis횈nkSZ]/Q.f@C{2m"gZwuUXcz'}elF"2N$S)~ eEb`M6s{ ,z;p&7O:7$љBF>ZK]MQw)F{ [º[k54wmm;cwߞDCxja99"CDz"R!n~aڇ!3 u@f[z\c `YjJ)pa249GmĹw{ow m6y0D|85,o[w*T[G`D$"5 ]\l榥$$2C@Aw#z cD>ZL//-zBdQ&j:O&;o tJ)zۡ'ȳ ȔSG>;˔O9iLO~ m>l]WA b_zt%'r#3 ZՋߘY`p9Ӂ隆1ƠZe].Y`9sy*;̩IVsH`q zW|s9Y@rL\̬WQyj L,~ m0Gʼۨ#:P9q\` 07L)ĊR=kew$\Nx3[D815\`DhLIhpWtDۨVrI)] "nޘެBU5Soi?y3ŜTq(h 8.UE87dfntaafF!f8 s:"r03<=> >o?Hvև $)̣7F5LüD$zXsC50s̵]I xz8񻏟>}4,˲|֪,ozaH{+!k,y>˜( Sku!9@pwMCJIǶK'sofERu#J," t/~ݪ{'"wE<ѻNFHoqT=޶m2͛>q!wS3E$AVD#b-Rp9[3"~ 3zg1WU QCCî+g URUȨߟ<1E{1hwwaa˫*]u_Cpo,8D$%bh1z'nSFD5ٰADTI9>9sQjP7֚z:`0`A[R2㺺c݁ۛO6LAXJ),e[{ےwbhz}fqD0f$}`{^oO~!З/@o9OXy~9AZI֍ wnhsEh $L{mDa;xz,nKɭաF 0hJۯnM(%Qe x#21Td:Oyz8{u6Z 4r*eYq&cvFTJBg'u]~^p>1eXD#TJnkE`y$'e˲ؤrl×I kNOpJ۶8I)m^^_Z.DN%k]`pifWw /?n:6Ic1]2Hx[R6piq:>el}A&|" W)%|*t@om[kkm$w>aw;1Z8K)˲ޅ+"Yga=D[[4&ygR`C5|Is Q0!1qsDaq@65H3>VIRJN)N#!V~Mvy0= QRx"AzrZ?.GazTXxV69w@Ĝ8e|uGu%'n14""֐0lf ֕(2! co y6hn; *sbDK al0@JYv^! !DtԳZ 3$< Dhνֵ\>|Gz}yr{ѬE~aMJJT&/@Їb ё#I󅻟R>`[8 {[X@R98Oǧ>ç>?ӧ:R94R/ĎEi:4FI0Ls)/˔͒aEr S󗯎Pv hnt8Ogb !"'J9a*J3MbA&HY"eIef.L|)Ly~NsTHY vzt g7%><>~_5 YpYz[r8n}hq{JmXs^ڭ^Egv:?ܶvķRWIv\工Oߝ/2QL∦r66!KbN %Ip.c[Z oOq~R91hyRW#2N JFס}t614O,DCݶ:( !ۏޫ:a!BJ%Pj DRۺ߶ zoò,s ZlzJ#3`Zl4k]G'R1LEehsuވtME|ZLYݐ<"OJ,AJ= cfYFCPGjO,9%w?f,^z l*]:Z_~ᇯ~6m[s"RX`S>-Q]/Ωw}z:=}82*.%gIi)SZY(aZ&awa"U Q$B(B, YLu\r Su3йy:@1ԒR)zijip4 i9[kER -:!5jmF]7V%m.LKB.%Z@7F$쓪2!9@X Jctoj6@ٴoߋ٢ o%E,yv?Z!Iǻo I}f5Ẕ D'9G#>ϳJNIxkk* scxoo1xs Bֻ:HH&[[79tt)cZS !IaGj`~φ=CJE `!$BR`c kd9#s!m <8)ouk[\f8zD B%¨)tP@{S)6FWr\2EJCJpW;=#>{ G@#&:u&"7CrPޤi.A7"Bqjk<?>/OeROpҿ|Ŵ:Kmh}" $JNi*ʜJaN LH(}SA0rJwKڡjÙJI,A >; #$ #*D3[_[kYD, vE( e飡0J?S"JLob] 놤D %*"̂8)a6Tm.t:/Z%3z]R) kfBȂmtCa>Lo$!ǿ?.9elO#ę}bdֵ['uJֵ6|X<1 Ք2nĻ2b7ԡ fȉֶe@f8pm ۶=90p %".%W%R@ 3@J G[va2QLDĀ8RdC 8;(@'N0g ޷loF1H8.,$dA_76:IIKN\e9?<<}UD݈r>&q2A"AIj$qc R$x=?!:t:"]֚< ^7F=땙,4?)(4 <+߁W =4#zpG"QJHDڢ2HIBʜ( Dd@}`13Þ-EeRԻ{,%3sTEmb`Zmo ;ZTrʴn2O ?rϿ_{tkik10ef@g*Mu8Cu}aYkH5CAt(JPw@] `VsJ j Sʜ2=!k>l~88rx6b b1|@7)43,}ǿËdضmjN d$)͜ul !EpNLI4MjNDj;׺ӇD@Wca܌&"JUr月);T:hXRR&,9͎pf9Sc DTkU0IfH{jcY`E>0%bDtB\LS:H*>ȥSI#Ž"Tw]d枧;3 s)h6nF`׺R)% F@;!8{t[h1Nk46fp PЍ%f~|x8K/_m1K"0 ^f=6pLJqn[Χ1 erՔB=tEA~Sάݏf7XgѶvܛc1*};g=눸@aW(Il'QGX`pL-{S(" uXQMUDz,ZkJIRJVh s⁶Zhgv"2ȱcHk3 @(xOw_o/i-'nm$(3uB!̮Õ(DzG$΢ZA5g!.N)ea7!M#XؙdJ<9D R֚ p T$GI PH;"橔vkf&Ӓ I;̪Ǽ 'oIEJJ27v+[VWEQ"Ef03=]Uuz>!: ]7=ǙxK0bU9UkMHXjcȺs̆a0fk( j6P0n|-0C]r98?FY[k:#G=g*$n/Skq7G=4b@i'-BK4B1s-6Q;}*c.8y;=[z?D`{FifBhSW 5+2 kVn).La)""{\zVoq2}q(;L"w^f;e ߑk\k9p͝8m\MIo=g{JvR54:K.UJGZj낷t+rX3ru"5 !xr>䜡Bk[Iuqit_ݟk#ranۊy0I9ǪB(LhfURQRM9Vcݙ€@6Dk '$T$f_JeJ$@NmkZc֡XW >Ld .y64ܽ~/K1S RL!8O M$PD Lնx"]E&q mv.sBjlZffA8(d 'R"4ݟQ{__^=v{{a g%RC8;n6!hLA@[-)u1!#zvLДs ORwrAz3B߲=xjˆLjzrC)W"XJֈov!Rk-Eĝ^`M΍Q5笪㰩&t,0z,)CF[٬nlVˌ whxDH]ϦC?ܪ`FUŚ:rW`?r*W/b)g'+\SJ&-tߡ^=u.?noGyzVUqZEk ,P`ڜ>AkDsL @5ؙMypj-oin7vi-mwc2uŻ[DXos6g.r?@Bk9_= &(eNF+sne}f&a۪fC`b* a1=hRJyv~_xpy~wO^ Èa)x!Lg<#删$Bu!t5d@1N2V!c$% Bɍ ڗiUs04;s}M+Jι^朏 Rjc"%٠#3s.ZsZkѦ*p~q0%R[h)"jk=$g)tV95pm$"~ϛ|xs[4E160?0L(C@2 3Y-UJfؤ)eW?ÿp _{'D勫p|9ܻyy "#)B_dc5F+%մ5#;fBi7\{ל uZ wHk/$ZII)1iz'7˥$'BVw)a|<(\W__7O^xG/^<v457)}bq PUvDAk@BRN=0Qw5p c|JYDM{CwuG:qεޑݖWRkOݓ`0ꩋO]HOy`B舼s]'TFքuxgYd}Rk>PjSK1&w*BZ_SjjwO(^I8Mn1|sxw/>O9v* !85/IX;:/E8_[?~ͷ~՛Ϟ_}/{ks*"=>&B ar%c󪉨c}Y9."&X?Z}5]7 Yk3UCP,ZLw­İ wv2Dܩ `uP燈TYJ v3 R,B(8Z#V@ L"y6gggvg/_8<O>_? '=ooGD0NqsU RO-9Բd`ȴ;nSw ,˒ȇWI{'QJE"޾1sto y@pޅ#=jd9E8 1ʀ"c>ơVK9km+ɘEJMuYf4p9V*hVV%͇/^Ԛ{dIqCjy#J,VؤybN\__Yv;1~]1VJUwDn}SeJ#) 9IqOʹ_٧!ƿ?ttL VC֊$b#S;@IWG' Y.T;1*kg*r*))lUErΝS]f֤SޯUY<S =Tέ IWhܡfcMx&yeVa9Tk;RcHPBj*=zzӞ=:B@TkiXUy۝<|w~_O~|==HmnKK)ɽ~{ʃWŽ>s?Ox< ^";0>pB$%FУUĴwc*sc:r6v63>7 wIOMo05EZk)N ZYf@L!Rx\lQa[3J;tzNj'H; -Ǚ\4a8ǪmL{K(#H(+Ѫk;Hy9V#[/ګ~ϟ?{vwY'޹ԇBe!\|p , dA sNkSB쬶Γ+#r.0av9AC$t9[| y6c {a9E[~}SŒ~QN,<4yw6ͼ>w*RDA 9~CcRo[o?{+_>}lJj3@تik\7W\!ıyhUGvg3{zd1G&Zk^du,A˲RB/QYYwwDo* L}62!sw.h ;e;ʻ&D.@m''Z9CSVN D·T 1h),K!zTTD]kQ[shTS6/?ɇ?<xJ6)~ 0So/ޗiBxynnA*!D"\q))$9n&vC IDAT$w-"1Юsq8.LVU3A{J9Wj^w޹9b1 ~)f؝Z*.Rb" D@@B@n/SQћyhMqN%L{yn..>{*_|?ٟw?!ѿxݷ9?T4 ga&"(UawF7>{ɳݻ~yy9yfLOaOffU扙gu֛ܷkqY7j2NJDU jU@4˓ѴZsfuQkk"3&KSixt.cvZJʵUa_&`o@!,k~_o?w~߻O^x;ovӃ5)5v3x>$+DZlLa|qF9 ZeY4Nv{qi3<"JƬ%s.cCa# cIna3:02 j ;GD& Rs 8깏"jd؉BXZBi"5ӑ98o6ˋ 5[yYY&}=gѲ,%8"m?ӴQ@CUBS󴙀Cnz:G~>1 m9gWKJFb$Z0bW[ :xUŔ{hk150aNZ9yV'Tt,33ש/|2p^NbL9}]v[{Bm\\UWsUHq=c. чe$޻""$tB;E*i|/ncHއ>=(>sҤbuƑsК%fً/./{[oy~}I+zo7;yxipqq?O>O|{s=QDXkU71L\9;izXeɹa<8n'虱[LH̬!NDS@9f'v+Z1CI@Z~ =QA!zzG=q603;nm=>=~撄ZZkav;BGP!ؚ./.bp=|1e%B[6FD]Ry^Zk7q}{ `1J*ӧq0n yywOay Xxv1M!eT>Bwxvnw'jT"tk}iO`F Fχ .Ĕr7sH$4iC˲Zx&pGy}׾>%_Nn9ݖ@fj89:yh\1ibӲ' ԺRJwK^$E{wwNO^ۛig": |-9i܂-r}彷ǯϾo_y;f1]ͽ/ o#\;gӃ#0Tݾۯ޿?="r? ]^=w@UBr̉UcH/VF{eN~~tJ.@HlLۍ ff5P0!LCk88紴lPՒYD &Rg.k36HxDyä Wʃ{_{|g4i!7> 8NiK/3M1+@ve.bu%>z4-9cXCp՜R@D485"R*v(Mn5xh{!jPjSZU\b`2FWE!.Z9T+8zDseǏ>>l6-FAZJGe?K|{~{{C/^%J,ÔKm{=e^0[=(.goW,Kؤͪu*vKzxvK3yn81y.`'n;U5ɹTцQ5y4쓧~OM[|㫿O?~zC OG?N۳#aA,6Ӱ1@5f4\_iY4iEZ0Ü0p Z:/Bp#e]R˗/b۝~s&BPPEoW|_;=w<TuYǟ|Oi) lIٳGS@hg>xeFSR+Mq:9yĞ82w@̬@J@0 ]ZN x&UUh`' w(JI)Ϲ"8z)ۇCp֪tvd3uĔ}c $h55 qt{40g4TUZwqO|)W??8OȞϵjmFSiI9OӤkg;c`bD8!%]{ZNSsʩ984Q0Ls .aW~ES'xͦYDwNl8;!>=uvZVJ 2e916%k!>~ѯ7{޻_vS!:v /^^?(v|L5)$ABBv;m~Ow*y7}98oϷ`  u`"dQ27z ZaFivp@Eĕ7@;:hJˌE0sQ0 bU8mnnf;NPu6U;618jf} "zJ))EG~G?m_8< a;B|^^͇/_ζCKۭ ( >. ZK在(4T}%W՜TKUZ4nv֬b6 J +!0 7|0F7D:fұ}<}w#8zdO8ZKS`ieYr^!dUVJRzU U40B`5<Ϸok0q!oۢB~BvҚa6몕5ރ?KH.y0nFʸ䅽S-oXJN̘bOG4{^!%K_gwϟ=6{gWW/pw @7li\S ВIiB ZRs J;iΦz1d֏0X9=,!#J4MK@OHH({ݩwte\hVGH>N~ WeN{rGK3Q;U:G@Nt*F' ɠ+Lfa/iV  W)1#E[ih>v(]b=4 Wb\ؑYGN\bh}j!-5<;Ofk"pA{@ȶ^i)C*;qJV;6j֬7z勧wː~0͋wfb|ʣ˧~l9p." ޽{Cp-_{r.*zw')[C9έx0Ժ0-iM͠6S$?D`qZM;?ƨ77k?AĪƘ];BTZO&EIYxr;{"̻]R*sn\f3x|.Ev6.<\;kj|qv,JH1hr11(`@.Rۦ.<(qjK[3}HKd\RjKN ⴝfZ ]0\]n3CAE6n> % *Ҫ{X510"@h ,9$lFlQG-`l@eֺ6]*BكRrN޿L海N9,Nr$vR˔ډRR3Z8V<2jS,{Bʭz\U{SUшxk?DKpbr%atp6ck̆NVy/^=:틙s)NM{EL)k PPq{Uڷǔ岷VVg*pIBE""H+l R)kL$W9٫@mjfq"~:]3ͦ1(„(%2sm\r~l[i?W7>ݓg/ YP29L2D:9E?K@ȍUGݯonwhQ2=p҄-%RʚVƺ@ (VJm.X·BPzՀX15@T*̜/\+t:޼^.%)" \"p)s)RJ&% Hؽi (B{.mV9G:%PDB8i8 踳`NyֺmIy9pt|vxAG U)5Wk!s{Dhs:%],դS9jwe)E ާ|5!xuru\D5"jT @ȉs&J9kGV*%mK)8KJ>G,UsSL=¥k[μc*BM XD3uHf$[j"dcʗr3f!)e+GM&0nKcGZYTdnKLI@p5m״;NTTWb q !KLP\3 d.T !<=l*&༜͖WOBϟ٤1f>88Z ,r)Դ)\a,Y8'Lm [hL{} o@@"RտO+uZ77)b~gMHX.!׹.guV𐱶Jw"6\4q)ůV nK{E*R,%0kTD"RjHT:Hk%]3I H, Z),)[kK. {P giBBjSJYϧ:Rǧ,=z@+0ArE8\CK)V;D_vO\<~b)l eD`TX4AQiR4O]c@b1$ӣCocPJNvIOԪf5|ǫM4,dzD "Z;CA8i*¹ Kk$!BRg(0~IZ6vͪm;cBRR)%sNEe.%+ F%MQ!rLL) bASll[o,km&Ȉ{J.X-@%pz.\]^Cdج ͧ5~Ysszy -H|2  .pM?~oWj'nr.t0ܼx~yrz>|{o޻a_qv1mBHdf\70#Pj6kU^SI)BރDk䒉T8q bQ' !L&1TjvSDkJ)MkK*Zw a>&֒@\I+RJ;,fC?bۺPؐwֶ}9SʭS!XX!"g,FnV_ڣs%߿w|jGMg7 EO>mza)Y}MܻwODa RJ̙r*^QN~vG=9ctۯ?ڎ72'+"(1~!R&ŘKJik*jVRbY۔R1՚`QZ PmmR۵Y$u]_0HrR!ĔrUXk)'km ].//s]mzVf]K=c8*u<uc ,ʾqJ |Ҭpl6P[D0TLݶJfSs@ibu,SN9?ܝO]a]콵b|]Q0 au~ŗشwڐD̵Ύ(IarIAbx.YJ.)&Ɂ{֋O?i4F;xv2w?#{|`9?n?zuCM\*Y:h,@ ӺT p t b`l {xF#Jt6"H9f  HE'"&Ai%3gqo7;vZ1u96 .CRl6'11![mwn.f6kOx||8L&dYd2YL'Ӽq.nJICb>Bi#0Vk[%GGb<.@8[Rmw,Q)E02s{^m%g`f3!s]v8Cl&Pi5zh’$F!(CJg|L^YE$TLzVJ@9$98Ęr|֤ZˆvwC3hKfuVc.כ~7 cI4Mf y C?FIӶ ((GA'h t5zu TڮNONNO^.G(l6mHʧKk~ߴ1>X,If>:>|q>Ri T1ikmH ns.^Ygi^ٺp0e,~XyMDH{ZfWZ{t.GJms%Aٗu\EGu/zU=)UUW,J$cJN̬hC`K+Fi$)5m_h N)J#R^xRǰn=|d0X8 t T^*B, 4)t,}:wj9^˿X&3e[xvtH1`ERaCp)¨jX:ð|gOKQhb RDDJ("! HkPtɱq|?zj/GoڽZgrYo泟W/8m8)D:Z%j,4M#qXJ"U7Xw%#p)PJ.̙J)eE! JRUHʁ9ސQX)P mK}*X)1Zk@a_dA"Bk|>m;tFiTBRcFm1ۆs6\_]ji;h$ʝ;g~/^<><.lM`zqqoa?zkd2iZCk$vWOWkL4˃eL2_DaeL@ʶi xOASJ!Ҧi'T(c(g.$5Ҡ,"PlބbɮkQʐc.}QZ]^][#X\3Q%1(E5+>ҴlY1hR~}Ksc1R&KGfh;*3 EVy\mVaRL&>Deu3@.zli>Tnf;ib2FHe|U^kn[o663ermO^n6ӣbWgtVŔ5h~}ֺNܥRJ=(9r9D j "J9֯]) Aq<] = vJ5!!1I)&0dDZ1 k|ѻ~˗Ox7?zw߼{zOg0 -S/~~\t1*%(60fn(kR}# )l% mZcflZULZ! RRJ)Gcpaq=;"29(M$)1ZO-ãtZb}!ė/_RDP59XAJM>CrvMfutxzD B{ ?3u|qnO]W>h2W:t)^B6N@Z\v TZmTSL[kr[{`nfS-B })Ӯ@Ib]NKB d43ٯW)%e (2$PR!ıRJ]7m6R}NJB]bR J )҈ξ u!1ǢȀ$skl7JS)z֛'_\^_\ kmP9IJsNSLȥOxKIRJu5g9@%AfIv]_&?ƻ'wܻon]<{d^vI>onKCߴn2<;`~뗿yGΟ<4wOOO׻ӧ/fgٔ86rY84-\ ĵ O)}"a]Z=^SaDh"]R7w)cLF T|t@u+s.ƨH5~w(UDwj%=pMuWv %$GIR;jTr; DVHUOV3ֺqPJir ŒZ9Gu6̀JYk6q 1 f(˃݇gGǟ>l8%~="EҬ 8S&< )P$&JwN>_ͯ=zݯl61Xdۮn~ǿ=նGJaX+P3"d,Kr^krcDniJQ)*EdO5ca*1mX8H c,9gSʱ%Z`@0 uC@$CΩ G#kU` Q(qahlsp.ˮid1+):)?1P -/?YB7>ӳ{wt__^<9]Ώp;.={3oO IDAT6tB>Xirɿ[k~ߎG˃f}IgGǧNt_=y*&kuȨSsɱUnSbJ`fHUX~o7|x|<1T]$ioֳ?'/7IfKi'sU #hcΐ"bػ!$QkŒYEn &ȄRTיdc O7"i kD0fSQD$@fDJ+arz췛q"T D 2(6;884v}FBPJyslMr7Kb>;?O<vLJ[gٳ7^trtpuuY]-gm j>W""Ҷmb!kV*Nq 2wYMS >7˥J.)SIu]k;\.3Nufkb:jt){+JΜr8 )Rj\Nb:C"e R8:RZ@E!"gԪC!2> ]IDH"̩KdZ WWW77˫zuR|̥;'-luM7͎RwM_?|znq|䋯̿~ٓÕ_b >ooڦyjO# O_9E?1iGm9/..H^]]#|9S0gww͵?f7F?(RkU֦iW1MT۶UAi J+9g̠IL5P` v]7 C wůx1GB.XA\b"V;.YĨ=i_D971'*̹n(^)86PXE+TE,}B::czEnRUF#֨˺*z\?%z0!6Qvl+FJ Rah@U_ 1% 4CJ9gaMX=?x /!&?̼9ZkXp$Y#R)A" fUf65Y,gswf1Űk Q~$~'?{q[nw~;f W@j:4h(g%,"V9kR SrM^OafR!e/8 q:ޫ)6@)}ODJc]&(^Ѭ$gsʪi6nUJҍqɉO{ۻuڰH 8ԝɉq ĘS%f{q7,]!җg?ٟtY''VjBXM&d:[,)ϟo[6c!c4iY0^vGoTx5^;Gmgrf: |savQRqSɶ!a$i`> n0ngu?WO.7)]D ]4أQ4IΞ>gmIBaΈyt mn@XXEJ2Ӽ(yK,t13`K  9Mr40 Qc\~05FkE) Bg/]k/h;\u)5,!X@{uxZ &˾A0J诺) wO_h:_WlmUsM;*fLJ="[6lv~8zn7@H"WNCQCc pgEVn]Su_nnbLO '`:[oͻ~:8~+D?\7G3E% 0'ZAo@5Ck>l-4j&ÐK=De|]RtXwpڶKaͦ˛G7~qu:rhwNmO|n;:c,=:rBw夛࿼i[MN?7UJwz/__OşG/4;{Abv nNO3wt?;K8_굥b?o?ڳWى6vHoyyY.6Ggw-餻ަ"MTavWrZ.Ӧl61buZ+ :ab%OmN9SLX sӶ9gRj9h.a֘*wtJQ@)P %89g\TѦ:R[n2ZigK RU=)q|nsVcz_]_+㣦ibs]6n%+Ȓ% Do )圣FeHHH>RdAҢ,B¥;<0TH.wtӨ$k߻Wf1;^]9slMF)\]3V,-uBDJ~k˗'t:|?_\^ieh1кfcR3VZC V+pkOP`1$APX@ƐшP&?&GRzyyW1朘9#3HV3}֏\(9ŘN3Zih۩RٮAd2@@vXn8|.l [._<ч޽{_;cyݡ~ѣM箛\f)5LocDh1`@dZk`40cF-zFТ-ʉx{ygO2?}P w#IgNNe_3mu,{"@Z;r}׍}"w~G?уD/v+mwrv||E9hZ˄LvrrvxyԢ8~L) C7;:wZ_sǛ!i\{ڜ?yv_3sO k|> !\]]Umn5X%i(c zXɶ,Mkt7gPfTIU PA J1(* 0JQ%dJDW/^ok촻gpz4IuDu Xm ~ձes΅YSC<odHlDJ"VZńbmƄ`@ʪ.е1N)PF@p^CRCN3w)ʘSJ)휧! =>9!x@YMee)\0*l< !EKYkEH:[|k:]/Fc &aJWL08➚p!BPS2DpI!dZ S!֩TWէ_<ӧuAhRQ#PBQ# e!QD8zP)Nm?}!x6pyyIP̽wKvY1^~`{ް۷Fi^'!1v8g Gc ﱒEo1c쪪SJ[ʰg( j2J'` h5Iwoc!b>G:NwF#`8\חLiZ8j/^H申raQA g" 5!m.2.Bm{Kp*Jy4-LW"o9h'5@RDʭvڔLШ3Zcf<|y85Ţz4A?_'qvIcdJ/5 fxx'fSeY{Z}TPJ5q1V,r^xl2[8ju;ƽZfޔJIDQn)fˋr\UUUEA B4¸n !Bm0ux wi}|6:{{7ҳ1oY2v8N': ʲ&O|{ua1r= ˆļ˼ZeniNh8>WUNQ4}GŀAG8AchiBΉ42i_|-'9΅Z" PV_ZuwQf3Va@cCaͦ_mLPg5 jTԊW] w=\[vu*Z/k[@Ƙ B;PGx6ˆsY !@=1 u5p1J7?BMRouqUVc 2$qRJՁu j.i \j5e\eҌ$JZq$`r Δ8b72yRB(%#w(Ye4/d|HeQTF$bVIr3B('^U`M 8oeJGOFy=2 spZkk\zʚߢ"׷ՃVۏ#l:.w"c\d{ܹt&yUUYkjhDvP kdy-E3 _Ӛap-40vx3m xϏ7G&|iGn_jC(Ap~uwrYrV4հ,uH J8# ܾqKId!pv>!P",MӠhlu;8n6lmwZ[VuqԌEU YV€rL;eYUqPtNU6 r''͛7nleZ^׌]Bʣ @ 7XT'B:ʍnc NÈP,Qe1)mqu~?~nR.^Iۃ;7ʗӧX|'dUāJS)Noo?`}DD^&G'NCU9TUrHgL^F7ݾk-zXB*MZVy^_,lch$Lsr>|"heQ19g1r"o-dNLEq l~[ߴ 4f;`g{wkw>N IDATJPj1J}e^*ITyJ!3EadYL;MUt ,W;{{YY(kyaxko?;otl6|o>([ϟ^s!gcۖMZ`#Xyހ1nԾ-g*Mzug _9+ T'$ѭE;8ߗVJc]˂jp}(„l<_5WhmUFP@86e546tr 8(f4'WRU;;[8#"k4(x[o!N~2Ti]{vN{kXZch'^9o*DG(/'?=;QSs*U=>J Y\jm ! փ$˜. !:n;s-gޖY;wownߺuv$RBj__=y: J9qn(T~\Z8Ő֟#el\z,K!@nZJ9ohE'3 O89_7A@b'ي0x㫳QQ&ӪeJ#LauYn[F??/:?K۫YA"{7;dU:1/>M~5IBU?/ipޭV$nlmmN"D4AS !i񬵻0o:f%i^/RWy.#4\$ic΃vnw6rΗe#UREYEEV囄zBH>tUcRQBet víaO VLO<0(_˕BCAy"$ktHi{n骵q OX;\:j9q)5 ;OųGzyu1ٌy{IuDPRV%wqY8W9T&w 접bv~|x~yn)oݺE{cj<7R[4DҬPU.EAqN"BRm2[j 2TUQa5ХozGՖsN)AkBj]G0ں{:f*5 cXµgzk?cirݜ:HMPu|"9`s4 \\3&͂yա8{ ,&!fSUiXjCQ" gO>͛7ݻ6v7J 0h%Sg߶}zb-UQ2ncwwՈ)F;Zʀ`kѥҠ ms%ڰ"_~eE=<*,/q^LiLs_s1z%"" ( 1">a„O1 g3FڭS-e6^y͝opwxc߈RAƨb~x|u|rZUlH9筥0FZ'ؚ1Qkl3N{m!D, &$sv-JY7D$DAww ˲5;x<rg;;{z\ICkMڝ.no"N¤G|dd:wGϞo1 ~~58r1[V3h壏>4w&ى3g/wuKFi$1gλ;wv'!/v3*U2j$ᠳҫanX"&!畬VN:L2mJWzYUgF$li$nD!avN8 Hr=g{G>/Ӳ;_ˌEl4޼%&Bx9,ow:l$*YPn$r-=+h4VAgzWOhZWQG?xxmy,wˏ?TeAE`0Y5Z~)?鴡,zt|8Ll aJAP>tԜ/HQ0]-|z1hbBb0I,e~EjeLccV`Jjg㨅cUmsuƖV48c91R.|Msago,VRժFs*)C)F5`0PiԳbʘZ)YӄjsΚxYcuO?bsRXp^Ss y uM7 @7ͥkV$-LEࢨ!FsX$m穒_?\<~h6_j.RFβ|Z~{?O/gZ*pf"R{鼵VUUX%uX-/_x~yz|ĪRONCAo?kMFϾj9/VReeW\7r1Cg>?l%b{i|mrNF/_vb*PVBYUJeJ;90JaXy&9GqT7vі71=`[caFww$N:5Rbo\pZWkB0=2`.ZkkZ]9dJW4B) Qι0 mLE0 ^obѦ: Wଳ2ƤRUYO^0"5"^ؼsuN$PRձ gPC )r' !DRP˫tU'_~rpл{kl9iZdB4/ziȲ*!zgG`3z((b18L.i$-SUSc B& #' !\k08 b!=!gW nJsR˙U@DmʍT/\-*pY,mR0%FF框$`,DW-rRJ!`9O4}q,GѸele^gWF"Ӳ a_<|yvK:2fsgFChT؃6Jp~^Uްӱ,<;ao@{|zJLQ-T鎦t.( xv/֗= NDٺ~oW;5ȹ#braus:r6^2zck%F"8 h ϐn^΋9O 7)XptF2SzVi7R!xzOZ taA~z>BaHfWXvnF'GA+@f;V"xc ѭ/>56ZkgY=UA7jT1[ӣYQ/3O u5;|6-RfWGda(OVDb](NOS>qc1;Yj}:Y'vl?kj,Hw.lrYuێx=Y/z:[& T+ yVJJ|$qb)Ҭ,3@[ yR gJʒ =T F2xzvrJ߸w_4|wkyE0.WxfZl\?c;7]-fr$B+@a(akhA벰U%WKѲUb(ArrO)آ(#QUeӫi-GMdf:M;@ 䧋Yӵ.+gv"ğ|I:a6VY:Ia#-*M;oRM2BڮeexFQk]}ͯy(emG&t#+@)FXme" _„` h!VƸn |$Ԥ6ր €; kDyE8k[ uut8gC&cruY)%!os缳,{ܻqcn`LEڐɯXg9!&#&„Xxvzzuz::=;?98:>=l9F38މgx!0f "8c<(mQ Aƒ{XJYExVicRrFe4㒭P/w(&9 9M^1^ޮ<]TٚsMFС`]/:G?y~!aI#Bn:<8S@PT,>R6׊lVvgEq~qyy1_NX,˫JY.8`EViEL%1(HՖ1{ ٕ+*U)'W4_g΂G(n)Ջd,;4]O.sY3?JVE0nlu{=mFYX;q7B׋$[xw8 Y朋8BLGP 8k7,vxk2[5TUQRmtVF?IO>}AMZY>?{8:>*/^/춟\FfnGGqw z`T@HQkbJ3831RVȣRA[f)rIBk5h5Tf;V磳*)eY& ,sΎ˳JeW;~aཫSfrq!$ F(0Ib2_W*-ӬpyFqQb0ˆsQf: c8#aL8Ziu[x1wsks+eZ[c!g֯{GRT3Y198#d]LJ1rmAkQ P*7gAQfR֚VekvkgzsSnP7^,mAR;I"2Z^Zq;h"hݸ "M''i5( t6:=q A$)f\Rλ0uY} qdo=<"lqGe!wF*%jr92Ӡ2b6a4{ۃ!b^_].n??^/O7Znuw^-dz ȧ~;?J"zML&SlHT2YSf2ʲ p{V{(TJei^T>rmD-el%ngXWP{w5U:]/z47o&Iŧ,c4O-^9cͷGqhvUQ^JUa6ƪVȌTxN@Y9YUH(i5$v9\8@;|, RKY;{IܖIvB +xMNI6-b,&jiV{mp/WEI !ֲJEQUUuW:RaDwzwq)@ %ou!e_t26"j*YkC N+Upj'Ƙ3f1VZדX+VaX lO)%ZPJ1z#a;eQ~B1yw:!/Xo],/&,8iJcV 1:-$XD=5*diTsZi1('Ǐ5guJQUJ1((A$D)eYy\xdtNk/ \PVqLE!jΗZ+r&xH|3ޛ:jL!b,#Sd +*L@I'ASGVA)V_c(k9`Z\?ꉀ`@am<xHYژ5͋-|^V9F3d!MqNvĠ߿qr7<^f<'TRf6_c IDATE%!y;zZi.(yo>y} 1x}Go=;qqp _0w''ϤrdXA;LLR Xu:t4Wtr:'NĚE=}/_nZ}ae.?hQQ3w5l'_V J.T{grc eS\O`Jca|.eiaNA}v= |<-\3 "^_~HY<G/' glh\i/ PEw;]V!gȸ"+AoS=cͰ2Z/q A$h5F,:-`w6I, QԠ;,wߊ;7zH.a̺q;2ˤ =+J]-̛3<418jtJ dBFyקּBaEwqf3cLe"L0_b޻R91`xzGTPֺup4ƀVeAuJuaʯ`cY Y˲"cznA0㼞Ժڵ !9k} I$ e~?: kLVGէ,$VƜzDQFo:S\ԕw"V!k-e1Ɔ!Qe0˜a83 eqxN/ǗG"YJQZ* &LiE1a `[o=x"8% !d(p3*Spo0 2׺R8mt1糵|r'ONΧ51[L(iM=OxƘ1`F0im^ܛ[ͻ!etH=r,]wC,vzV:oxUw!ZfZoP+c 7 V$9sN1P:&BÈ@ڡF~\?x{<:?1~)1 $5EJOoD*dд "R7w%t]gtu]/h16}>Lxpyy6vYGFkD{VuuWH/_ ‡[~85Alvo߼^W?c?WΑsZ_>|h~w  }ɓ_^7^/_1ǜ~_N''q`c|1;:Z]i}񼨺(}^N4R##Ճemgv^~.]^"\UUT$Ϟ'ՑC4b)cC>y՛Y5'u? qL1Ůia/ϞF *eLqBJ)g좚?<8^.?y{ ''rfUyr|؞^O1Y6Z)"aq~w9@S=~@ff.^4=Zv:L6D)}0ҏȓEdL4]0 MX[ Rqd2!rN!$o=X|ry4 M/!;C:44JgH$甬un!"PlÐq\FXjǣ>=$$"6u931y6{io r>6cV^QĔhDW&gD9aC]֜50IZ fo(|NAPa/l9 7/?|ç:&ad!`a2c*("I8&m2rR16 hЩ_<4gVag9q7{#sLjç"Ͽp}mߦֻv-* H9smO<;f۷l~g<bRhaا}o7JҬx,}in֊ /?\-F۔ͮY6V8EAʢa?4Fu1jMRWġ:?/W_CꚛO[#!6mlym3s#nw7 z{e :>9ZTe٬}ʻVuNvUv_?˯}tL9٭كo~xOJSVv_G 7%/={xJ pe3N9H6G8_>|.J(,EΦ 'mZ͎\H49J!d+멬l^J Z뢒Z{r1a_rn)9qlŮ\[R/F3e15\5\}YR NJqF(cJ1@J9NJk !嬭AEHHJM2Z[T(@)V)`  V "]tO#,Yd(M;ӈO09L!s1sS8 }Yc!eYaQx>Lcu89;^ޚ4pE)qʓ|`IJ%V*0 cQ30 9iga*EPFt{3{i:e1!R 1VJ)ƘLR aM@J ! vloca 842;au0۾~cHc/f5pB~bc]-gIwD<@Ya1F"@!B4E*eu"ge5YW3enw|w|)RY?ɳ$fF$,(IR̜SWQ_:2aF -Z!dhL'''FըYJmf/__Te''*\-nˣ/r6_xOl9xssvvfW?NkV) %ﶯ^_` z_l^קeu0ƴiC@DkmUήauF!`93c8v _.t}m_gϞNC^g2 4k?Z-''j^/<:c,)beXTNw_|U'c WT 󇧷KLYiM9GkԖr<;(כ+Bg_XŊvH)uNVvC EjàgZw.aFaQU(kRxN(lȩ6JkWjib:>|4MHaDpZkg AHΗ{횦0>qZ}oJ_UYa󬤾޼yӌ]{~hvaJ凘-Vnam5go6S(~aꊐNX1)9Hy*d,QLq'@DHRDpqoםdZ5Xiwqgro =uYbblqփ8] vj9d$ 㴖 )&H !”3R>Ĝ#*bQ1Iov޸~d&2wP@e$DҤ)'TJ, xu0ނRj.ˏY.?=(;îiJk=ťy*k b-M[ʪb:;?y٣'k"kӳ_6E\>Q+uz! JcM֛v!䄙]g-ǡI=d&P3-W|ftߜV=<]5WSZ,wccQC_]YA\b1[Nų/.\i9FN4@ʨIt NNSr|a>>X-Ou7FVn}چQg@,I҇)C7SZ!ƀ ^B " `[ oDaLqZ-bUsN њH8LSV4sɣ& =FHQǫm7:ö1KQ n۾o94yk0Q4X ]lKk;/5ۤ[$d<5眳N)5-EJ!ccL&&!M5{LpWI+֤B(!=p"J+˲rGl@%b4[o>_}&tfF[8}R"&ă+2IpP++}H]w  'nHrL!+EHʀk`W8E6+:ea5*5B(ϟ=o~>?}}>- O']{/gfsV 6;C!7?|W_ln_k/߾)KcjfxQ|2v{Ҧ-Ts1%PaiN1 ĦG#ʟ.x)pMyZ\h"Hn\vskn']Lt*T  8mהs2L1h[!$@2#XzN)Cy-6M''ӻ "˛MgLQbl{^b 0 !(e3k !$TIhQZY yifYC75Mi31]um{Dj䜕hr7A)93醐SY CQSquL1pAh&P5gUicYGٗfWXEC&NQ2gaQz{NwɩL2Fc"2RI_;ʝ4CSWt0隩O|ID4&a$Sm%ޜSQ!Fl%D !H wfv΍! "Fie:ιif{QtdI=1~%i~zFY&n9Ma @$N)!%mHi l8àZ0M6M㔛}<w_o^|O_֠Ө@ k:?_~/?u:2^+-24,Z[[9<*9eIld[:(2!r7t!&muhTvX20ĜƱ" ,LSjs=Ze]Ca:@O=}byzZ69u/^qUfc> 0C 珞>b~bהcw!X{_/?vyOG |6Z[gU؁sT99k _Օ C@Tu΀!ĜSDV# bEJ-QD`I)۾(3}Idv6,KLlT${)*Kb^V 7&,9@ʡ*,ٳa\70*MM9 ;G!BI}9֕H<"L$OVs9rS0 ,,f2 dӝO7L)8aXap@)Ԏ&d4Α6f S1 sNXq20!ERJ)N+9gdSNwA&9<ABwDQ7=* CQz"X _B?t|FYdDRRQԄߡQˆ IDBH(2 !+dH@rbcPk3(y7/^m~x}쳧NO9ۧO޼Ym gS0 ]7H{W5awؘH9 S$+F &uJ_l֚t!>>{r\oFEȚ81sL9t?QB< 3B+J)%("az " q&b0 ]h0u}wr0UrF'jr8t)I?M?::1F+,"s)pg5:[[7 ],E#kwpbJ2)qVӫ4m/O!25F"myqͧ2h!Bt yZf`fHB4hb'CD9W~ʅ+J_WH?nGU7nǧ'.ϾٳOOW鋋_k61CQ.))a " c1-sF!8R ira1@6{,Ǧiݶ/,}a5Q)es`Z $DH,3'8sJIg` PnooRUhom%7v{>dR}ιrʱAИAb֙僲*=0"ҬP)&8s}ZOqܷ}V!e;H(F !jbn7!"rsKgNovlE$4Nuj)sHBǮw$mVuYb9z R||v훽h59HXZ+ D(D&XE!$F6ڀ#hƒR$$<]_mzq䚦_ӇOg?_].\̾V1Ɣ /@R3ccZs"REEyf{w#郱jF%kkM; o֋űXuyԿ?\ӄ0:xlh 8uׇFqr 8dX:lnf˺JKe.f,7ͮZ1ڻ;6߽ _o7뇏-lY͋zzl^]oǦ۬wggm?9?v8ygϞmڭ?khҜ^]]5Mlu)نHh!0~^뫛St ಞfc>"$@^~ cG'Ml6hhy| >Z!KF~CmoH9)1g DB/y?溬<"c1pc竡O"o>.0-TuUCfm]Q1%4UqCQAд⬽--s (0016{M*4ʹVF+j`фZ,I{\1s_p`@.O3yCߺ@0,h6_̜s]qE 5N9lB@8Ǩ-ZBMSL( ;̽sLǔqD@æ1f0r?sTa8T{byRR4}_Mn'`•AqmlS,).O I2tTR!pwPPJ a01NGTŠ'Y'ܭf{Wd:-MT7MJi>UB9O :1Yp1‹_}(!E<Ekm1h8;q mNs$ 32J -"f$L \Wb_>mjURhaql?~z߾~gMjic1s L8eAb4A(M]uoUs@ҥ/7Aqe]VswnK~߼LDg)Bw,ct DZ+ڀAQ;(mWD =, 뛏mS lu*VA㇏?|@Ǘ>|x4_կbdMl۔߽{7 6уӓPZ UQ(gǏNq~qCJv}Q{50e**g?=9RΘ8Ay?;^6X u 3(sH8E\. ,VGbn\Z}=!jrjlU^'΢9ЄBb43CQ~[%dPXz?^!+HB 0fQv͆oo5*^_{-b>r%Qg-1*g庬g-5Dja:u8EU/YkͤP,[1]EG+[V^ =_UfUKcvzqYW1FmjH. f%͐g%scϔʨǗ=Wo͖R}Wfmty7[u1BM&csnCL-:&|8yoNm #gS2r]NN1 ,H]Dt}?}'cR6DBch=9dc'q("|6)FksƢ(+k{tt"3[k0712dȒ&iaVDžͬI4͍@1Ĵe@2QB!@1RuYiE1&A^m˺ԟn/zQ(R1bNھV7Z0нUUiF|֤"ԥއ qeQz!3r}Y?z@逢*(v9}t}71@jEPD8V]jbsN1`]_:B]T0vBȣU s42zӧOGV;}Ĥ&&$1ZAy@:hֶA;A۲bA[87}Q+Ed /ruYufvmZy0Paq.qNCB5eγg77W1,˙ef׏sttԶfI9Br֊HH)Yҕ^@IvIFN9s2/KVY( k (cʄ8A8p/ܧ QLzCCtHŒ'7mLrC !Lτ$Ci~IXRJQ,yJƽa0Utz0 Vc Zm٤4m#OߧRj5O(pȰDΐ!g!Jov8'@ZYYJ$ 5xQ 09GD2 fI yRAsVC߾k$8Gd,`4h[z@$~\2@(پSLtBTj10ÕBTL(>1 mMOW>~mOh>Ǐn?]nf$"1ĜsFDSbSN25&wcg,0 fp\ 40~ٌli0fN Q!EgŘݧF4'ԉ^! ]5D9MY8Ycbx>3֚~H)ӕsQm۷C8F2ske@bQϬFDLP$`r:* hW!'of:8Sl[7a)wo{Rd7ۉ邑@*0J6cfhZ p~zv|$55ueCPYRJaV1͎/__CYsr| lQΗژx^ՈbINjsst aכfkA#H$"%#HP !1vJʕ c6ώRJƹslq9q(˲(~ f[r!F U۵\X/>~mg!VJqdfCJ(!9-1D PJHA0t@b')|ORLH8ѭSݟ::$"]=|?}`1Z5ݧVC!{.AkMqQҨ1;:KDQNj^([gZ7.iv.%Go陛Z!/ʕfnX:?D$̚ju~z4Xg:8ꛛ'O>#ka$qqsh*YHcNNN`ŲLSuans]zYowy9 fCTż(b4^>~KFTFBK8;?_`;ENKP)rJ@!5a|pdV/?كbF1%tgc[v%q$TVիzd~$HCpdL0 j!|Y@@Zk3zJJiR,RnB_~|NiF5}J/Kf@ %K$bLRJ9qaq ~H 2rUXfv;v_>==͡3cnܯbǜ "T-*$`bfK)S~]I, AW_J@$ 201D$b9"F=dJ~[OwWh83u"h+KHQbZB*?')D>Y (eEb$|s΂Jʌ}O"BDĤ< Q"%$");ŋEl F$!2T{O(Ju-,X BP!Oa8~w8Ev(g@w]U8Fv@DBH"$J)D !P%MF ,D٬9ul}lgkibsf!WRqiիD>WWWa9q*PJMŌ%NMpھow_Ձ{zK ABT%V-ZM+lqpr `",Ct1Me1o&ӛ/ޔbJbbs.zuMv A5eާΆ8 aRm1@&>~˙YK,֏NufuW_}wˇ~|@<LC8;w77n.B[IɳO5,"('!8)@FIݶxxz]6U89b4}׭f|tgX4 9)0ya /B-yĘsI(" B6&0 0'9<Ϧ[^Ii//zchK9gfJ&PbJG6RNGU?\{RNz;SK85s`nv,-\~.;wm ĞuD1* 1af}V34.}^ܭTJnEuZOT%*a瘁pP/n=hm[yW)Xw*P gƂ@(C,9jTnƦ[V$HDf쬍u^fB(j6e*.$_I! `N) &wO? /BJv , Δ)@KB@v>+Db$$9d$F,D  !rG0s6PbrlKacq{_E hj֛o{RJ!lwb9"B(%g-YX`כV2蜷3FvBDnw@M;̑?}zfYK>i}<ܘMEd;яӬU+D~||ɴ`ڦFJy\7R5Bu Q ZKl[_Ѿxqw X^Q>Dg~?X`&H9ɍ}ZVUwC %WgLn/\tWgWrSz*Y?ۻq!\J(K2mfB!a&;=jt=+)M #Q=*#1h]'0ݷ7?_>;a]m6g_7v?moiͷκO1tB;ks&Y}}Hr%ktuuY/B5Jȅ\Q)nCZ(=lyvJ)$RQm $h4VjZc|I}AFiI&Ov*[)4MX I d "r*R%2BZg鶵(#<9q*SR+iwãUq9Kiy'j5 Rן0/?ƥ4RsP:KiLUI yBbBD#BӘ )\ORO);gcJ)p B& DJqw2" QLGqB 3RbN9TRIHT0dbJ !naO_O\f(G&sH1 fdd&$U㜕MSR2ː2 9BHB(A*RL!"J.dZ(db/ IDATRf$L8(ٺ"YlT8?a`n'?ꦀX,o޾sE]d!a)R9Ŋ$KB)B) <[9dBnfn4՞thy(cU/7^l_ֺ;cmX ð^ot0f!F09rs|:k~ѭ~|~p7f-r fHoD"m0`/o~VmTM?F;ZB7F.@za$ ĜV4/rPR YI8,1pxzw;Ow7gզ)Zk il͌9}۶Vgis*YC$a2D1SrRwRj!eYJB*@үV~?M3?J`6YGu΄51)Di 9|r'"Rڦ=9C6p~~h(Trɥ(!\=NA]=`Ԕ֧zk5KƧHT#QŵN]t`~ bw&гMDr1"fRJTJDZODY9A@R*TXDt)(BLa? [ݶ|Ip)2@ '2RI!.sq'TݰK),t<LFfy>~QmXR*)b]19RJ( ܺbvg(BZctX6 b1 @9,$D,8qn#))!Ʌ0rsR?\_]}ׯۦ)xGD0ưƘΐ#3J)Bpt4s]]hhCR*A@@Jiv1d#P會>S,;<@z74O>}yd&a~7g??o~m'7 m?ۉpbx:ptIHX|Y1BYrF6I%RUWcH 0spu]wbNv9|3W#}q,LӔs.P,z 1N.j^ȥ9rBV'"֋#`ZPcN4r- ]R38 ;'(RDDsm2gHk n\pxAJ%I"]}vq</~0۟%6oH*SPr1VJKLD61o$2R(H@$B%X7XR  'p쎸SΉF4.M їR D`~Nq >F~qQXo\h3΁iaOÇOߟ_`dJ1 (P F̥yZiBbO?Co_&,ww>Pp T3*bb @x㧟~=  ƆR̻|swx{xc4&ŪI az-/WK#X`2ū,+Ma%a)RIڰ9UJ,ܚ#/agBhm䤅"B/dpӹnd("}; ~o?}jo.W_E|׏5d.Bq5GYgu8)g6fVRU-P@j>ITտ`!&zFcNžjP v|<':is){T4xn>Ovt9'fbTJ2SΉP9b<۵&#͙bJ)R |(91z$Z`c@.BsJJFs 3:V8L5;笵̄xtgAI֖̥d2+(UBB mD%wwonJ9TL c b4)%RiLCUQ1"*6U :4 !XRTEJN)1!p) QUq夔Bp.%𸵓UJ_iq [ka44 B ̨i&X }C&>?~uvfS(o_vWXt}!MnƵϖ]5Z"%b܄PlFBO9/H7O1ƳYi^4~͛o퇏.f땔k߼y>[˶}/.W(`-G;%e-q?$!aл`0e$UyzW9& sg7X2LnKJBzm B"@,c,2 դ>- Dݢ_ֻ0E*d4- Ӄ^߼ͻwv㇏I@Z R4(/?9蝝Ipx zۿvi4(#D|ߦa`KRCJ)׭0J~|zf'v]i$EK@DeD(Q@8YTO$B3rzAvʙ`Zꊁ}λCFsVsC򗒊ycƘSi.4M\qUmHW*gR 1"z !FTkc aMq<NmcVX1J1g:%j"9ϝ2INOid3K)!j!Rg.+N4٨5H)R[UMU#z/v! хPaǟzD1.!cJ)JKJd"|vz]2 4N!d5_`AR\<@ը<|93D X h$$HCiFHy8wEL%q$JJX3 ȹ"DAs lZX,xf$o;ӌ[~q0ad`cd!H2X7#V)nfJHV 9̤Ţ~?D!/:VqdӐJhHwwwcY-o^,1b<޿=;[m罛 $v/~ztqq!7|v;xb1a%:$( DZ^bWk ~F9;Mh㸻{q< 篿xB\@)!BQbU 2 $gT4ejsv~/(8t91h#3ҙ*&J>I DH,Nt"P;JP _JI18BDMyֺK~\78gc>'ʹ䜥ҘHԎϫwJ\"`HH "PJ…x{O^΀Ah+4s6ʁTl:!XkiN)%)A)-hck;ݶ)Ɏ;XS|n$TP.9#"d& f$(LJ!$3bǘrNB19;IJ0J6l()@9;ȳ隋sc~&ZLpbǂ >TٍiP83$7?_.낷XH?L4?OZ&%3J!DE;?==.wmW3n;nzz (A" 4 cN!뫫WevCtB eat*$!@Ъ.pxxx$Vg*SFR %A9q5Y0xrJ %#⒕NDyvcH'}Tri}y!Lk] >XR(B<;arVzecъRltcc}LJy\4]5͇ar^28 _}^ɻ)z_Y71E%?{I9,JYl.bw;#`(K)ax8撏Ҹ:ן+F_50]gjyA{KVyQR?LCRJ)$g'~!g\Ճ<~\ED0 47Ʉ>!"ѧZK.Bbp~79 f6J[A3$qꥧ[C*!S{c$b"9M!fId9xy;0AΩ3$LYPnP42$&.ujVR疎kr\~)Gr8|< xP("bL9%YK3uQ]'H0C XrĈiTBK pfdm%TtN00)Zl: C̼nݮzk֎pBik33$]wǛ7כ6ōyC~vHr"_i1d`~lx5RiRJ T4>-+e0gU؏?C̫/SiCqs^/WJ#0.ǧG}"THqc۵ ;8[[u߉%իzy}- h ( .Bl-5Ҡ@p]x˗_E K1Aa! XEn%@LMBBqÀfu~շwӻY/WPAԄ .ؒ"3^Q R] :EBH>Ry+@` X CxίWu\L7 ç Q S(mcn//^zqsk!5M+ J̈tsaڥ^tU;,RJUt8[;n W"C J2uЮ-L|§#|z:ײk9??D 'if B1p*X͂Ls)Ց{_)T*Ƙ3ThEJ )%N{s̐s*K˥܉X`,,&HT2Đbp9" !.P cRJ`B@7h 1#db!(#F4;1m;#vvm'ܣuyd3 14O`.g3Pݢ!G勫K+wn.c9[)w6xvJ輵9SIY)9σB1qrvqo`w}uggg (H1Nv%b7Xbǧw/^X,ݸ?޴1HkEsd)vn8vqs"!r)%J̾Q˂ %F0qnl$R )  FbrmӀ >>hmh%fC@P@)Y;m,:;h&iJZ(itO\1*1L(@D+⏈Uy*U򫼇~~z[ n=+TD4g;Se8LFRJR !q4JVC2eq!RV@?R*YD}%AVG^H9gO,B=T6?D]DvΖ] @8iKs #ٕ2Ԑt*\QȜRaPXڱ !jZ)%p 9W<';?)c*Y0 )% gD9PF"!?sqŶ9Gd(P"1%d(BC]1 <RRJ %KHXh R""hm|4\ELqRͺiG)9aY'_ ̬`h#@ʩmۦٍv0  &Ԓ``w/qNi~6-CJ#%p$ }ט C6!xnw|}W_nJ!)8}zxYq5 ڜ)dX?C,lT/ťZm 뀀Z$h}H1s FhCfTnf8;[.B,A"Œ PJR   \JB "0d.=_ۿ?h̫_t9f P%حaO~wDZ*-U\\>|y]*9Xf[R"Q 8`hrJ*YͦY۶~}|||jﻒaZk9rmi_\Vk@X">*eXɢͧpJ))Ŕ)Zj:RB6 鹥T7cNͫyaAĜZʡR59Cc>9 \r ؇$J6Mב5Mw ӘR #趣A 9ìЈ,1*L1Cg2f=?n^&T;t߼b5l9nbݲ~}ݯf -}pFE׷ >Zp~v!W 0 ?|x|SF5]cTjvۧvggX.WUɹ5mp~}j/_~6jo۫/YIcZ]^x7Wor9o%6tJRL(c^,VehnlH4<`o~;ߥK.>y.&`!CJ)21"'r΂ dݷ:f.X7?>=-S,.\j5IQ A Ă 6TPt#2(Ӛ1Quw7B ƒ9}bL^=YC8Z3 Q}QR4:`)$Ŀ k^Di@"(WsUbI1#dzL6?=msL1m;hK<ϐ|Ѻ !n>}z}NnoncvHiu|s<[-gr'"1Ni/ZF*J%94Zf AP cEҐ)%+\bP Xհ}ZFo_ެY,9,]Z{h` 0A (3>h1dzLE#`lf0KtWuu-ẅ8DfVu+yo6T9.48%r@n@ىAmDSYE=;l`<_ |sw{~uyݼz~?lue*S@0BUPFd(&ղ轏J ]19dψ]7 r)< gmyJo߼'WכJD_$q3<{fV@. Mq5WT ΣA0fٯhuζ0!HQ) n۪^:'<4c( &Qԥc|-/sTh)-pLm JhfAŮJ= 1 /uI8 D`>T\X@]u۳d2sm6(!# MMJuM6PTl4iT1<ϭ\}?L~o8ѬbOUevԶZ{5aF34E}TQ$>xLs2{DaP";?V"R#:U(*PڔR].y9AkjV-jRV`zZ| =nNu =#T&F Tup#bsJ|0G'c?㳳C߹yǒrBdX qxvBwq=t>:+Xے(m$}NLWEs!wPf9KWVaeݫWŋιsU/C2woߍ]O~?Nj)m.ί"㔋VSc=8ꯟ"() P#2)PPSA;"VG7`O -C7ٲsgHȑ)!REK=J14 f%_<.eln߾MۋBp@T|ΡVBBuD΅K*#;\i}<``wqno...6invӸ^Bt7oyf՚y՜ S9> igd!G"gǗ# 媦!.vu.Ew8:cwvWqWwK2N`ARɵ#sEC4}3{J-̘Cx_3;זb|~L ǜH)]Uk"3",,KZD<Ϧ?N9=!. Rév[}D͹!!>ʇ;I-Rv\(R:i DDU|QjYʹR4;@bDҲxG Zs.cp"ugf<9om[R[ӂsfeZuqȹÁXTQͦ_OWA&45ruXjU~2g"GMZ1s1s)G#qjsNFT?t h\p=oK}8??%?n3ŗs{w{{|DєSyخֱV<0N"oKIMy,]wC Wf"U/t}= =9] AOͿy_}dj{q Cg`J$bkUvM y@XEđ*U$"<{o_~vX}%Oqb߅n}0,<}`<!`4MaJ%;0"r1y,ZWC`r ؓǏW]_JRZw)TKUDQI͐gﰔfV|9ꡊ W?yyjιSIu&J_3uY %o6Cc]`RI, vm'kEiuOx1VEH9#a q;J伇cq{ bﶲޚ%X? Q-)bcz|]'zso.w?wfvUjk) YS΍V<oE=*DV ]f"~|VSNWWW߼~ŗ!<~DK}mSt ޿?~D]\ls_Ϟџ?Z WO?[^vq`* :"Ҫ9gCZU 1sJ4͚zB&vZeGv?>"1Wi7w??/ûǗ<g 퓧waUs}Hj@531 =4<lU`V#v?{s3ŹGx8{db(t@B]Fu@]Yd`$"]QXPDx}=/gOgf˗y@K h>8oZ)s*<*c0RkM<1___7^LFCfqq s^WI%sG׭M |aѣt{Hu!8*vRLfu@K^!B/5@R:;v'8A85ۙ,~ A2fͅQZqX'n{&r~P#Qe"$jүа \ QNE4S!iQ"*%o7g.~4=|1䜘;nժj(Blj"Dp 6V/ӊLs*@|{IhGԥ * .L "jj ZED<;v "[k!wu)!!ZoLb0۶K>,j3lGDj#՜$4kEJY;s"8@i3`)j"X L= œ A2@buiBn7jJi3 _\\z{'O/.]J'x?ӡs!Sݾ?۲wf$&{sAcZz1@});..w)͇w.圧i:jpq㏟O|sŋ(lSTӋg:WgϞ>~i!L^?B|AfDS){U *s\Q9 H1gGoča)) }g\K & mqUN@ PM@IAMT <*1_}O~qOO !~͛o)6Ćb{nzOjDBjf%1,ެcwwwo߾4=z6uAC$:N3 s&rvy5ĥj!bpœg?zˬ6SFZ\B0)hUCWRYC㠄 ً ZF[w>4w >/ٹv}?9&' އ-ж։vDک1_3h_x&~|1 ?/ TL.8!`[Y?V]Kfv7̊,Xqr΋9`EaǢbHa3رsiaC>_\\p6ٟ9$"1]Z ىv \#/Ԫ.EOxBl͹0scr*Mf5+Z-rLa" ^ZNqNX2bq5ck1<瓵Hk}Z{O>䣏Dœ^w_`|fXw.ʼz 3\js>fZEma_`Wj^4N_ލfVO<_ze}=rVݞmXU.(.KK2MLdI#4%#F8<,ZMys}y?xܼS+ R͌'}|{_}ZcZ~sTA53!Qd&2P.:d6iڿyf]c0;V1DBSVYlNB]gh"ŻЯZ~d-:/j$}R\;wRLcfGU4S !Q˥NiNUYQEŐ9cC\y *z1FJ\J=D^74O% ѠVOرmPWg+~K7wq IDATO<9VXp?χFqG-즔`JЪ8كûo#ӄFιwRnϟ<}pٟ᭓1:x=zC}\Ǹ5 ƁX )u6f(&GA$?hZ/?~ כ?w_?8p! C[vwBQM9 4=h R\Sj 4rQz}W_m7ϷE:{ouon:\)CmתjR٨7sΈ"H[[=NGv6tij|$5f8 C ~q-Hu]ً6IbСj))KȒwqZ5sZR\u1}ZKC Zlt8}U0 nNsXGuEvffggۦn P 뾔͟|.Yc>oyݑބ 1xwEt8=SIiֲA}aš}_U}tnbmZK)3*}_Ji*ի~w{Wl\g?>>zyXw .] k-"jZ L5:pة|o*ҳgϮ73TCɵLR T"D E6Y-$ :ZLIEjެV#{p/^翘/hWϞuG󭚂|*V@ 2'tZ fJTn)_A'O&~# 6ޙ۬áʐC3"kdB`ǔA\$z{{{wqw)u1}F{8{#bOURap؁}S4O}n p #6Y/u쏧&1TEV]qa!ޣVjq:=yJl)%'K/xAU;~4Oa׀!noֶvvޯbeMuצqwR裏oߍZ4]@)UP mWg=Kѣٶj-"`VTYAUu7Os3"4]vv.@E|c3Enol6돞?oq E.}sj,P!`Py,<Caݮ}Ot_ꗒ* pyqL)gu"D yL@wZ; 2Cb`HAQ$3Plh_ۿ~}Ϟ=n<萦y:jB߃s9 @U]zȇi5*tnz mTTU;4|Z}_Ifv* naH-N5V4͖Ģb *N|(ЈR%܌ւj4r"x5";qXZ;T!X5lhicB*KsHN݋u2ιYK`U$Djf)%mBy绮3 !1.8Jb.Hm$RRiw(ǂAXDx@ĵT^\O|^0RڥYK?&"YkIrZ)H .vj23V+tHPj*sͷWgû7wu>lمҭ!@Uc7>JncClWQTU4+":i x\4o)D n8|sJhn1ߥڑߎE?e!75eCf| S4{HN]Ek4MAӜ$yY>'Dʞ|&_sNbwʵ-}*jE(ZT]t'ZsN`,R/Y4 { ÐR%,6%^L1TB= PVhL3#P)UJPf=]*ȑ{4DZ5iFA=h%"eJB@" 526*D*޷rM!6B!4g*]VD:b P4C ~ƔuAk11CrηP;)4qݭ}0"tc @|m/J4et]׃C) a:4<8Vs>f ,Ub/..o<>^k :K7O?ޯ6y !{@sǻy:4k. _=}| 髯_}/ˮ O/}۳?}'< hj5`15:upZEPUTbpgǛ?7wn<_?ZvEpc αU"\kF5%YU2QAT몪`CHuF:2c-ًsﺾrR!LJ }GĔK) nwZxk̓jWUCq\D"#$*MsRNܓ)C;4ͬZ3]s#E%fǣ;XTSJf -;Ljvvʫ}RJ˽NA4ă!20J"E5w-(])S{J]v!У\VR] jb#w|ϠvADScwADZbP$Ig^c$NG4?WC]"˛cΗ"hޟB>iݪjm4GpSwy޳w]#)̜s` C9-`h䘉ZQJ w]WkM)b7"s:fP);_ u1}Jf~o/VA9ojEUUSU˸zܭ_V΀A E D~>Vc">n5h !*.zƒ2VOTq^ZüoBP/fn?zٗ_~7߾~ZRJS>zskī˯:ncFH.Բs}X_<^y5 9~^z7/_ܾ>·//w,Հ*""#Eb2XHpPŤ}5Z:j ѻy` `{X?sޯS>?lusk 5[\L;R5@k.}w}}@Z[܌z˞H5]لfLT۳Uj1F ֔wRjc5gRɱ$rQ ^o.d`598ȼ8]8kd%Qs\QHIEUd^oBZ%"?"O~?l{}^^~ukt^ /W{R\`uNݫ}S;Cx/G󾨢'Daw`R]O. 6f!T }%~PrVUE%cτ D9g6]c7'WOoݔ41P0GHĬ2|gB|g˺oS5xB Nhi(`:g#F0Rl*!Bjq&׺{x<qh?믧x7ߌۑ|,x:9@3qD) UU3(CHqٜN8fm'Jm}Onۺ6-VH@DzXNaHa)kv┾W/Ha"+jFaGDwCL~4&Lt^!djz&wK)Z4#1ZKڏk"r3@9/r<kEfıSRէ'3u };#nB)~bIŮ4MDGqW8،9E;*nn]3RO7Hy>/vg̥!w W  ["@"! v]Yk:12(E|eyۃBkLJz : [@뉾 "ރ}QEVpᭁX#hM @ *ү^<ӿ_~ï7K7e=\ӴյHqS!PBW74b9>|>c7t7<NjW--qKR3)mgkYs*-0 CLlnRZ^N#QLZ4 {4̜c? DSi6͎C4RΥPJ%"ֶ8[mKYR?³_=l7:iL*q0&g9 i4qL@jkZBԠ6enQ|m+ D2O1wzw.epaͮ4"$ ͸ivݮBw"6RJJ Iva)# D d@m3Og؟;f"pC-!"w]톚>")qfn̗8D!^8jOxQ%k臍G۩G!jTn_~W_U}E\Ԁ:d2z Eu]qHĘʹ=jm,"X]J9y0MSChmxC1RJ``#emH!P9* !3"EUE$W-ԧә 6Ojkms`] ɁĈۿ4Ģofךe!IKq tTk[˚4٬&E:+XVQ&T,t\4wCv_/H0sBqq1N/o68wiy͌J9qb7aFd !RRǞR)aCV#wPv #ò,՗/^?ūs#!`Q TMt~Gv<(!s hvgy W3% 2k^JW>CIEͭ.'"p I/;UL(t.UB<_}7oۆ ^ LgW37+tnEc2خ|,xyװxk^/E- *WB&Z'mx<aAEL;j}9L岘z2B :̼832ww@$ֻ[rc%Wmf!\mD&N \ :Pxe ]npb-)uU #  ͤ?u`MW]Ae7_|~oTˊ&C6A `0pQL82޼!DH_n~{8>iik W.8f |>wqi]R󐍘u.e7SnkUCbHS&fcD@1ʴ뼖ZV",nhfcWsyiZ5mnǴۭТLC,hn1)g9+1$ : 7RRz3%WiIԅR87ޒ?i3 V;+9#oPb4iU|3Q"̛ayC@U7 !ߊCc@L|w.;ywՍCތ {_lpmT/9ϳKl5VM:mFV(Љt^Kz=WMjm5AZeun˓l˺7^޳Kwy Xn_CgJI/ftnUjmZbtCS'l'">E/ [3`BBGt|10'b#|^j)%'3c請Ў|́Cef3X3^9HVa][?~8ER͠:7&/GYF[wo6CdѲW H)4&`(nC ZS!TN|xx *SNf,7~b܎W_<ohCw]-e=?~OwoTUB a+)#J1LEuZc2Gww$)K`u^|m\0b@y"2G<44miNO󃶖ADS*]< %Q!_r .fJ f*uuPu4@fX&,:Kj1C]z8'?煒xmNԀ2Ϫc^EM)TT`]b0`1C8f*=KCjk">.nc9q2@0+J)~?W1{|կx:E!Y{$S)Nuke.ȍn )$썳{q)2Qgiup )_Tvuac<}ӍIiW)w7OM7WKνohk)D1N%aө;KR58a>A.aap-\ctojrq^CD3 /.b$DZ`eJ7fFѻ|EsJH{WFS;iBz뎻_dю#ZO<Ѐ#ƘDU\{|Z8DDMB蓔"dQ5 k! w4ֻguFۍ~_}LV-*0kCe] O!EE#׶.'FuJSPnC|:s ˂4cYלbRa UilCLnݙ#pk,iimmMbNcVϏ3o_~eۏ!vڑ)1'bVJ)(8r)EN;IT\Ĵh6sCTf;K>&"9 mm֪si)TBõhNQAыYHRڲua&E׵Ub g1E`rnles未Ǹ s y&C Z\Ϗڼ XqmJ)y<Ʒo姷7_~y+* 0P[3iCO !]q+(kYG&nՊca_.n=b̽Wv+Ϲ'eȄDLbPfnD@9B!W\ua2~n`9gbHbzA|o[O z1kNN 03smfS ʧS590Dwtk2" :#\:ߋg~;fV-!j"3x!uF݄l7)%w,"m&HZ"څ-ri?E.u1*\ܵʌ^JML/kJ*5ƈv`vSTkGHon) Zw/^/nepÑ5W *2KWu#U)%t8H+im_~:a)k>>9DJ;!ea8=<<:BݗBލqamBؚܽji}nd!F$3!m1ƜG b.:lwt,<庘8<>k/>k(ƘLUSZ(I'38]cB#`'fWwY|40۵)f^I/o~sD&0@s0!$\5Yڢa aQcTU/XaN0t"3 (R0~O.洿g Jm>K7ry>KZ]FH2λmÑ1giD n!qH|%uB͆i nk!q[><<T?{1$ք"4ֵ}?ڟx$`@D1*aLuYxLR*H..9Su^v >TZ) *rE}YCU~4afT/z:޿{_Z㔶ߦqJxϱĈS) Lv{a4)ECr#*KY}7CJtrwoZUN:Q9J饇Zb,bϟi8qVQ[&zw9RH1di$UrΛ͝|:绑~=\fv ׄKm+7[M[}+vp3D4L͕"-|״/*@Dꎧ=R 83teAiڸl  n~ޜ*J7vG@r8/Z~'Z Qv;,!JYy.zˆ wVO @ .iU4&&f !!(:br6/y.^B1f"&14E5QDZ+K1t>{x<M!šS[f aڄ@S2D Rpu "!p&&@sE94b4Q!DJ؆(Y`Ū[Z>VVʧtWw.qNdBܱuEhs G͞gU+nގf _ ܠ<p1Ssoo޽6_|ff>gk=݋{b͗_!"V -E n;]S&%)p)bnqy{km1Z=y]BbJۉCb8T2'/.ǷZj;T4qi<W.y]w8Ր#DtPVm))Fl q,IW,m9~?Z]V ^* DPj9<`S+u-ԑU`[{Ê1[ڛey~/^`kxi8mf$ԙ) )S yQ3DSIAK0NCҳZk S$DUYkkMw^4Mil8C7ӏꛖ,/JqRrW/_Z{>p>791T@Z9DfF(eۭri~cX5( Mֲy[\ʅ}f/bqeYz#{Ju]Rٰlj5Mּ}{ALbcN1r@s!w^hm v5]9JVe~TMSp!Ob)%1R.#6Bg1ܤZDŽ)rl3y%f-QDiߘ!#(MUZDÅ]sZfpLM`]#b9N"aUJ!U=N}9z,K:}x<8_#ܳ{ֺB)3S y}{U(@֤6Vx<#X")D$ן}ݷ_ @Ĉ)1emDOp9AHOR5u]Cevw8)'!nw>|8-%:ޏ)Wo>SpFJ)ikIGʁ ]=քYn.xFD>R1i! OsY3fӓS[W;>|xRk?s02Ҥ,x<0/_ ROnk}# nio=.[D@|ڎ沔u^84s@?7Oo~`弜?}Te3qԧm Jk^LJDjR*2l& Y9Ɛ5.UO#@XԷp0)2뺊vww(Ԧ0NSNߡOvM[7iq7FU'p8#K醪+*( )t3e(D;Efg5cԼ4 Ssڌ!o(M%wxR•˺d~ǜD;ZoQ75^_CBLkU& :G T1s,`J# )ƈF!< Bai!FuZ0 SQP.R][c\@LNgϞ5)rAUOCm102B&WlGݡB=n/{n@777gI{N@-RZEA.ɠ0 n^2a̯^mo_~i- yb kZkeUve@B 9-sS`fmJ|<eApY$: Ѝ:4k*Dw_Si*0ĐCZ[ΰ2u=/9v>WgR f Jm,gj;USj>.J뚴wkIcJ_g@mӱsDm)T́;X0`,!yG'iNy.똰H !# JW!ܼOf*k+Vt8<-EgϰmDT\"Kibz8w7Y40McH1յ7sf&GB@1w#qպ8C˞ ~y.ɀUF IDAT_m.a12 |SZN)9pvsR0 !A\|>z>[k D/&ADA&`tB#Aq@@Ob޽%ݰ&<<|໇_xV!Gm9zth"ֵ3!-]rLSzuҾZUu#{kmDq k=D1DL= 3ӕH". X/Wx #sD@* [mZWMÇG6fN)☦__|qU,7UVFU3bN)y:/CDU^VTBxR.fL8ef0lqUi=?cUhjZZ%6neF J)"qW PYi TbRk$4q/"6nw|*O#׹??z^*| W_fo_?8;SS+w4?Uތ1AJX j-솪vQG ]EEZf0q3CC=\?-ùTpr;?d@$Dž틉H}8pG˲Q*'B\k+ձo>쥹|tЂLcQkϮ?A_k>_eTmȩ5m4 ̥4/7i Rm*svQB ^]Nz8vB00ځ(X9'/hoU=Cjq4MSɿ"Z9yZ61( f*1yjm뺂"Řko&Ёc0ivL^v}?|!f#ݱ#`Z[Ej';MBdfզMݤZqZvY9ͧw~KC2U?19͔;"1nF,0N_}_vt}.5;{rZkǵEws?qN@{5jW&.ս$ }7p:ڭ@Y"ԢK]q_o3{).c>mGJ7G""z%>5J׸88O.F}j$*tR" pvLNB+'3J0Z!cP)DvJ]MZYՈcdڏ(R׊Pu](ZCRJDw(DMDbkl/V=mFCC7wv돟}ٛ7.rw;"arNĸLZ3(efFmn`Sʵ)t7)1 TMj3H̜cs)nx_X3pee )F*H%@"1>UsќpI/2,RT LØOW?y_|>Nbs9O?p:(4v!O#57() 1^øqզ^E3!Vʵ 1@8n6&! B><nyӺ8ȏ?|g8˗ۍj[su-[izrb1Niֵy2C)Z@8ޝb1o\hq9g*U~W?@tqxo~1fx%* .jnCʑXAU\Ue]J0n 55uPK:v E)" s"_o1-ǣsf͎\Yޞ9\1FAfz(YПIfzڪJU$cg{(1,aO""vخ iw߄< pύlfiӴr^k݅)>7gk=[(>eYj7w:z $?uլŌ~VeYyٜ~v d2tPD`eYTOq.k4^4bzt>LY2|s^ϲhv;|h("D׷Wߌ#q"70s[Qm2Sk5k6*)@)!:DPZ!03 c45p`Eck2q]*LgD<@9֤#Zq"헉an}|ȌlZa-ë˨fXk ADd]2s7UQը!f:W$===!m6a?Ό0 ŋzozճiM Dь V[N6ϋ::STpHa?nlyћa"0mu!h}Y0^߬/OwWw7Iכ4\jAy}z̜%Æ dhFU}:;@vW_Z9_4mue9AÜX<0ﴘ3x>=q|]dp!BTy5D$.l%KYų7_ۯbD2̍N tW7R  %/Pu"D K.cTf&[2jݗ#Z0ilJ?{ų/^CV1]^0㓘 at|/.kZv l$@k1]_n/|~yv0$:/r7_xs$0WhnYv#xh絩CU3Www߼a}9bih JsAoZ}üu5}!q1pnR TjFiڨ5]tUЇPJͭeb3絹m=2Qn^>ū}v7mǿ=!^va8&+7c Ny9xVͽTݑUJmNH8EN]Y1vNчz>qӹF$Nk7M5 GBBFDnD˲g!:: 8꺬!H/?Mik$M{2ӳpd6 rzBUϙuՓ: U8YU)53GoVZĘ$DKB4rs5}BP3w53`Q RzYc&,,m3cY粝p?ZfN)4RyL@{ZkZj#! %k"̬=ia`rS'iyH7WoxW/yW|ysI7#Eycڬ!Z!ń=`s~Gh9|8ܾz9.VkFr{8 w}KAxvl~g/nooa{"v~:01T5D$fSh0`uB`,E)&FKɥA~ 8bZsy^Z|_}ͯ08\Ô懌RuICnpCwSi;qZ-eanoo/\^^WuuA#"{n7_@W׻ ]M0D,!b{bbZբd)w_ayH浄ui@>"jKi!a|`.B@DGpB 1 fAK)E0{͵L4\˓x!2r":ym q ڸ.E>NI8-U侂Y!xtp$'3z "bnqqQj_;O*FvUԍs.=DDN;OKZՌO5ֱqZʱW~LjkD{fҩӈhg5hEmf_~dj17^ :χi3B|>}C=9#\ @(A53oZ}V#؝zO|7d:;)ͳWQu]s]}܎) MۺΈc;RH~* !V<0qLYkU3FBZt1o7w7; tR6T8N%UbD q}B C`6/O;4(HǏ%iCf?~|xx={\|8ڽyϴ"ۊϷ^{:McJCizÆ 5(Mq;^<{x?|8 l^ P8@Xh8@)dZyS`4&  GfW_$@lmQ ɁN iX!Zl](4y44lR*1ǕWTBb> EN~ !'8~@D:X+}Zˆ3mzyk.o =9bucNcvc9KL0mnͽo<̌??8;~/?-{zP'qI;joCS'-):MUXMDAёLՖZY=W_�Kj}CF$LBAWum)2(fqlUdvtLi.9TuK^5׵fan[j qJi DUki`CC^vB||biD:h e[5k^D$,fE"pFZ ~_N2{ի/^z}q;ZqR @#p a[!Kv@aqͫm~)~$4.v6~quJ! Tv6vKmkB"p\ EYnՇ??Jc 麮ёZ` ҺXCh1sebSpQhM #cm, IZ2@2 Ujk0 `m&)FɥeJYͥJi?5kffiiCa Aj BYV'F:YVNN㧥DZ;He>èYYqD0?O!6Xx;S_Cw;iSߙhf IDATϟ̻t@ߍ =ևNr{14|o^oۿ{231 !o&Кe-0d\ڴu@[k]D3 R~*m8x#|{:G4oB1 Rk .0 ðyYV!j-PkX@(TsB)ͼ5m"l B -x\]>K, B\]A V4p s7@&r5p cwksS(0t@b!LDj; μ]*5j˵,&M‰nis,{5ڀD$DB$R) JOfҸn%<"e j-jnnPk]seN~!R@!@7ҴZ~?>~voqqUDTPZepusC$FMkiܘفB`7"77>`> 4/vB\ó˫˛ : bm &IVBB~yW/_<<-دUji u-152Z[*p̠umiY h~:bDciCi\~vb wK)9>/. fi%k674W`5Dbz8<`ܝ-[k&W\׵}>_79I:~Bb`">/YN,8CO#̄ дuD t%QO?VI;)rjpө߬r̎='6x,K:8 0웹ayO==*q$mJvݷ?v9<55`ZkȲ0b7[>><@vhb Z\'Nϵ!FfjUQF餀Ykz3ZRD ߷̾at4;{D\k!;68{X[0OMoнt ̛*6&rb*ovzϞ ɭ6pojfE!ks-ڐ,XkVAO;KVGB8 CXY|C_yA$֏tuYKZ$L*[qRJDhHLiDB)Z2sRJ)UT NCkCYֲV؄Rʲ,ýxw/y> cJ)ZC  748sER)e}|9=<ChՖ5χe-ka^lFRvvcs)Ì,!LiN*"%) o^Ӳ]K LT|o䒧&9axx[Jb5wݙK )ZQKSVWu$R%Rxe^]a;Y㥪!7S4m Pf P;tz;nm]֚03Z*NF>J>;ԭtrmljL) wkkPkXzS} a6Q;ck [" SdP#>ދ[)fg]@S9ѫBYtHjX6vpz?/9qXͮB}5t?!j0_ 3Va b'6&1`WvN be[apRK#Nׂ.k1sqUvh0 ث>AEVUsi Lb[no篾⋻J9)~jfm+#SiZhC\kU Q;3K4 ԭEf\lF 6cûwnǟicL)m65[C׵(Qp\Z˼̥<.W49f!u],ZqYUHivVTu [kjV9Kd7k~zx_O@B/_7 ÀF z?-h7ij8m7y-}Y\ в,k7"aSqa?/^][f$Uq`׵| Z C )VÊ,nnz#繮&537_k͇%RJq Zk-7s9R2K֢-ť@8MJ^"#%f-[ A|uha≼{͛j@6F1Mq5[`,K^[m֯A$婇;wS- v[iTvhbE;rX$)bo{S7LLLnN3[ }h]Ե.4(E8:_sm]+t)BRz%N?SW?̴{RюS]c';^_7a୭kYpV`|ln?2wL1]l!c)OB@1Ў [A1B$H^[#q.f1"E1(47v *$f3:TUuk-ŢXbkjjL i;!Ut`hZs[yj- ޏhV|ݕp@/_KPWג2O@BNf^8@1 ttU72j{jW% Cq:T0-K0'7[Ym0XXraOUq2 8\ 6 .M! آpxx|[+u6 .GU@@m^Z]A"m-Z3kd._ME$a z(?ýz7χk屮zs9PQj8GP CY*@!HZʀlM}Oj{1wGsy]oo7ϟnC3iDe,`OUqZ UJ&[sUXѪl6{CV~շeA43ML-/z]:χ厙jӒUEnLiuV=,(5(-8bR2ky]:C/vakc݌ N$pp)TUM48\Ji7z KLHDH Ȥn]jKm`8|UbbGo0@'iL g,7HC)pA :#39\L3$D:Nߠ,DAb$V͹ )= B)Ķ.Z[dU[kk"\(is=dob0#'i)5Vm&m~`b0?<<8,3B]i 3w>+Q~7)m.3/^JTh~:|n)~?ܼx4?<˿" щ% f{JL' >)1̆Hq-f' z L\{G#"xV6GpDF L0Wkq0f4V oR,x}s7~]\MCXDBjz\OK<HͪVRaSCj+6!FP]ZkDāAAJKɄ4Jr.OOOח45mmc$Vr[60N.Nh"0p@U^DORK)V[і r·áv޿nn}ś7<qa +2t Z3{ 鸉8iBDӤJbHnf^(+amۧ젵XSoacaU"CL)!0D"f I.u~d]8ncH޽Ӕ6~MH8a뺮!i)y-yג'Uqq;N4)fM#rȵ8˫%?0!1uRqQ5V4#昐zHDǢjplS@vֻT"nV[U3& "sy'oY9whR:Ne)1&D=iOQoǩF0 ݏgѩl== z. b*ZvKDiܭ&2Q/b^Av: Hdj˼8cbĪFpY͐{ f+Ⱦ!Fhq2ܚ~=;¤U[y'16۴?͌EXt E-A25?nɏNp'?p"fMN.}i>mN TnZn\Vlf%M b|UW8هdMV6]@5l.}?s{y^L|ı!;2'@ A|HiY(Xi/%8~"bpuDA? ZfEmb3={oWnnn[3)##2 ɈyoDBrb`źn8 ܢk- [mNaZ8kkZK`Nhޚ0圧: 09c"@Ŗǃ*S:;gzֺib%3pJ)M(1ymjqp8J\JW_JinhC bGd750`M% ,!A5`SBu1,Vr5fL΁yB `qq6 8p_eBCa !EK![˯_ ޾{dZ/,:1Ftf6NcҺpYIb6L4BHjNnHbq 8ִ6A$$χl.oi-!8jqk@ȑ?Aki'~j6RJk֊Ch 9}/r u4vgM{CWENg OSJz٫RUHN: 2{rtSV}+}i;HcE/Vه U#(ctjڎR>Jpff&Պ4 )2n˿_yv÷߽뻯_l"U.y7q){-uPw5BHA ԛ5@>F޾Kꦪz88HdF.|DytJK捥[(2DDf"Uu=ZBhXCϞozs}}ayM0h";Xadd{,n$8UHQjm]Wl\ tNNF)Zy* <޿bڥ(hKYUk 蘇/Z-$yeקeq?ā8(RJak$˒+s fn<˾kqfZC I!gSdCY{i;s.m{u jV<.4D!@dRsͥ8>B!zxxyL+PS_}" b2,Y?~5գ,$' x{~KYBqo۾ө)z.!u9$s1 !s#(]NSVzvmfM_~n9SxVkK)D32 .tdzVb/[kMpŻpI|y^,On<曗_\е/?=y:EE05W@ր ݭKR Ax6p `BUOd5j`Gn'd>"Hn| ΢b/SQ$'"y+D$d=eE LBL&4A/իWPx,$'Y7~|bB:3Y{׏yM]CH8 yBq ~"-rK.%KV~)NT˰̐Z DTQB?o~ӏ?pJXő%";"T3m-c"NEa$#qZH^ԪЌ06D'!GPqLBC!4L 8FCh5O{w`M5@ ˫ 79Wme&ŒeN#º浖`ZK3ta,^`Tk_3KQS"Ei].a"Ŕ{2WmpTNU+FC BªhhUkȎU IDAT1kvm|;N0Y]B5/q&)IGr/_r}q%q.HAaQ0>Ch2P6%B/XN9RVs@! 2\jIz 9?."̕[N9|` 0\D>(u]w:T޽۾}g_FADj 魵 hMqRDAUQGj-:bBwOS~ 777_>Xw`UJ6dZGFY*2*+4(:@Te0EkztiU`JC`fr F$G#Oр) $⽱>Lӈ mD{_ fCf_ q>cc-q%lИ"du~Z2%V6"R3!8`>닋i<O_aɣYלKcY2" }+<K~jJ0γOQbUV)[3tZ%O;JDGM'wc `UJ}?إq Mwz{kL9'U%2nf݆u*6篇lj9N6!1ZaO8Ţz GcGlnLFXv5*{/qѯ(q<~vwpwrRZʹD2g4`+gi6HEWHekkmB䈌0䜄 idTI}_ `mAZ#UU  6.5&"4Z&fFd)h5W:.{Y]]^|ǫB-@ uW%PfcAĢD0V7|PWJZ\+{_xCEid R-Ev'Wwr{8/7ZKw_߾~oVeh vdC 7hOnwЪS=ӛRX\nb}בrUY@v`4bY|^U(|+&P,ӊ3yJeUfPBCJ9/Amۑ%[51ͪrBE2BlιPU%Ksc_{Pfn`P!g쬷afr:jjcJ\9kAH6G}˯?8wzfȚ|r.UxZE2 Ƴ0("EDl)c nSMsu18x~Ts}v@ @[D@3YBDSR2ρ( ֹz&LiFy32f9?{lRJι.t%) %4OG؊ pۑ~;^E sɹu^3:+I' !Y4ŏaa^ZrJ%iE68{Gi_ę Bb0?T+uDK(Y謠D Z+jPazVWT\Ao, h ئEgK)9vddvh՚{Jbљdj *D.xkATź6ULԊ7G7O?z Ρ& Š(j"YT> "4.@̂f󜫱XT1: Czt:G&7Ted!8D#9z}oa.~_s_s'7/xuaۯ7@_ow�)?^>_|r8ye ԪtwZعBUeM(sv H//5t$ QWɃ.u*)4Ξ MFQ  b(B" *y^J 48HbaZZu*޻Zˋ GFc3֛cED@۝О>~]g?{ᖧ߯ˡr f˥qV kH9u @6Ni?zqvB.(,P6|_OӉD\e#I" (? nl}#ydfV̙fkMp< 35vR ?+3TE/@@`kŵ1?^&P=䞇2w Zk9kjQM2; Gr,%ڧ)HC`9!D0\9"`SӔ1ZWb{ck!G8 dA0$$(*圭}\-))e'WHH ̄H@[RUQ:Puݺ]qpH<uq՜K)j4v@)UH0{otJ)\7DfYwMs",{vȔb3AI!SSRl~j%))3jMqNaZjb)ֻ0QmS%U*d" j#ͳ섀Y j,f\^\y@TDlĂPeabPPTV >ND89 AcRr446̹"F$kW5$ "JJ'FZ.\pВy,v2.H\KB@3h#H7FD$!A4*HdX9#p#!Ӑm!檢ذ&X,ۓ/AJp<}xӷٓ_~UkmݱΆeS~ß~z~]UO0|1x;`7Ʃ4Oc\e:'q\m.IYׯTѣoO?T={* Gr † 60qw`2s.=ݴ;1lsX t*b rDax&Nxө|c.w~Nӫ7.V+)zww?1zÔ_^^Fz8g>캐Rq4ֆ"WU22s5M Q2Y!?r׬[eAlQ~aq*⼷eG܉_OV~s!ГqΒE$iN.`u</9s)YȧZćn°cRR.d0.hqK9x*54Nq)ݏ4fN@-Q:*䒑ID\:g-ܢY*otE8 Ű2T~S2֎$qe|W_sqvzkVзoDvfsLuZ(e9{PC'c_RkAiMԊ3;8Ms(%c~sd@h.(\8A$c!{V=T^~6h<5 (t^11~5 >;{}_qC}4N{n;}8cga{3޻ZTﶩq]iZuӘq;槕'c9}8/MȜFfHUv0MgI'졢R͡sf''L,Ck IDAT^e{yq=&1xM}ߣRyfŰ`qR^w竻;$)jݴZXvVgx<9OC@n"}b6F$>l-%7(i>Մj([oL㾗27WS$}dq6kp=yZdqиܓ)=eLGD9j-`{ﭳ*g.A 05TXQ:s),bA!:0D%T#j[V@ i޼2t4N/wzM_T׷G?{n}gF~zu(%/7tv1 l(wqztsw̏/n.v?]:=氰io }w<AyZ92~ Q?xjz2x[_c֔JS&mVjyt K@DղJBB]ɥdղz2 gmRţ"fsח`hpWTVRSn ?x7-s./8?{_1Ǜby vOwծBwu{k]ظ .h6 x$~ 53M#t]H D @h];t(⨢ƏA4< eiux>缄LԄA ċ|*<2)N!%ڱQA ?ֺZKJi.t>4@dLVY,;f H,8\~"N5*xD4 CeFU8ezm9snFN\CĔRe hUKf 1gɩ* *tߧ @u՚j*9gԌfQ$krt<}~=l6yEp=M߽w{<Řc߇6n91.}F&6tE-c6Oj83n eG+SV੹:]ؔ1 ,}(9il5P@O4OZ7""gc@B;rJrzdH#p?|8=>}`6NӸzݶsJ$9K=N3ߍ>/./޾~w~lTOU$xppFizրvg ۶)e`͘EU}<*qW^y'v79Fm>`A%L.f-Z Jap8PpTu~=ꉫvmnOǟwa)1cL4M4i:6xjTs+j|!γqF0}-%U aއzv{2WDfi@>Z17[4$Z`lm4M>l~D@zۉU5hAC0Uc4uU":"޻1M 5ki%%:f} *`^W +`)Zk5c\!mYM%zYMZG3^J:!h* Hq_.ƙ:Mi<񠾖T,;c2jxnTAdv9 g4*O*!RVuZ=L•| CER<K] A6woo?jVDt\/?3Yn NݸݧU3MvޯEGJ5)XE\%q2ƁjBBWS1r:ŋE?yjC x:Mjj!raL1BX"X yf~=犆hF8xZ.wލt8moQa:KvysjԲѐk,fαTf2@b?TN_ ƠWR]Ustq}xz?ݽ[_^?yh`C~Zmit;4~H%cReQ%\sdlf!#Ŧl%W~Wo1=Z͵Ĕn !]qsy Y VG@ZTi?ʥP=kq>CH!O3~= ò̓1@3dlZa" m.Ȑ>ֹ1fv pejsq,i̵;MQ<3^Rl.:cMGD3Vh 9J)}wϟ=n%KcTny+LqJqgW"pX,;597woNSٓj)`[]%8MoZ+ 3k!",b"F.ژRU%ki񨕋Tp:18v\`A+їBsKo_BRC- w524Z4"5W$՘8+`TyPKHm02{f|~h~]_]i > fl뻋|{WWW?3r-XEcVι#v'9)5(F%y~[Oon /旿EDT߉Ga ,!Z pʭT Ї>x<cNɵ VRҩԪ昚Bg]UPF:|:1Fy"*Rx0 ?ؗbyO LM9O2?{bX|?{[7:[֩0,rݶ:"`J)jX"MymX,:g\]7~dX] °ܾ?~_u1 j0GUe/ oa:sڂuc%2Kku76v)w+W- \e¤s!A'gj?τj?rcZar)Zk˂m휋1Vy=Q<'ǹ~L1)%" k`< $և)0sQg a0iJM׿wlbQ*A5 T#g""K"e|{;z{{=N ;"XkP"DBk9ODZA~˹8ԗUM[FD&hV^GcL0,l&, "u{Uk;kq@#KXX&n3UUlQD`UlqHSkٌ\2(w!Z:c!G3Љ m; VPRK2w/xq2ƉMRy|_폇G޾}_Օ 7 ӛױ @ՄQKT~|O7k\^.|fs?`=c#c yKm:oARaߧjss}yuuYV>|\,-VcMSu:~Lp5Vb buP%O8Mi~w,U:ciXVŰ1"%H9:" Cn}9 \t.*%qN`4Qs=Bˍ nT8|y8ӧ1?!)9k՘%lNDB\6DSU13+a4MB@c./pq醅3?ߟ^Eb%aW^|WIS(o=u `nU9z5 άжj1yqgde'Fffvo+&CP"ja=U:@Jj@n Xؘ`%n6^ R=zQVFQPmW^hZckmwERʲ{̠v{r:ŋחar,//ʻwc[DKX֝=ZjbM)yKX}gţ?9ݛ#7Yd#;ʘbpi,qZVֻR!QG<Q8(\i8]\l?~ QEwnNqKx*d|茱\%Dd*׆8C))Flz˒dI^f{#2!&>` 鞙1f*3J 򞻙GYt}ׯ7j^V4O햵w/>߼~0<>>1.PR}O/1aZ ]PAinVB1E,<;w9ww_l1?=W/Pؼݞ=ƩdAU)_u2Nhxq6햿[=1[и1i\uf(քVB B+}Es+1Ù~Y=3Zpe.yN?+[iWv "jeh]k !4PmWE8g+4woM g< ] -D,A{n~x \T h!JA}Mm\rkƵVƇOzցR.Msy bX*'N°I]n&-Bh Z@vX 3(,̵ZݰZŒ,&)Y:Ti.(R+U9 4>DBz]nyK$2dLP"Zkv^s @iw͎@:`67ߝKInYs= p՛m>4)%PYԸn 4*ƜN'.,`Chua!@Uynoo7nZϞ3%WAN8݇SrQ|^s,< )䆮;ʮnçMo_n XkN ! Kѹmj1gy0 ¸ZLG5εyWqnwfOS~ӗOYk!4U j4o[[sm'"!bp{?6˗9GC ê5Wn6fYl677ژ qZuvX;sִjYscܾ\yeǏiz nt,߬7Ţ뺮qt眉hw'[κq1u~8OñcGѨ|`ݻw OqXo޼I"=읳W^{l!ZJ/_v j?` ոO'$Pg<v]g:Z/Eu\-˷^z=i&; gwrf@s*\;?f?˕x6Kc(Yb ڦ،+ފ}V5g߯ȗڿśMGsΥֶ(ke!x9W;j+fr)DrnDlHM!sn gG^.6ATԅji|X8N֕KULJI'%Zkl-\aE Z%)ނ!Dcq}?ްr곛/_vtY=|R $ {A" ; j\\-ؖxk2ϓUՁ64oHY@[o IDATK#rS+5MWYjͥj*cTeDCd(,cgv. ThQh!"@$D#gMHj>y* k)s}0SQZkq>YPEޒ]矿<}̖Z1Jg㮆V8ODDⴀegGk;@$  ۠t'e΀uDFTEQ`ݫ57bzm)qc}}w`n8"n6Ku΍c@-k:::kMJ G캬8zSkkYTه}߯VzX,10EAm8 R x]fsyQ 9|sl9>lKn.U(Y0JF !:15KZLSy+"9EidDC REZlpDNN)%kfﺱ{wz1X Ah\ǘ8 nnV*gT-lK >%G +tַ9Fo]]cc֊1F1f\-sCP5攞9E%1m5 }׋i>=oQ-=)ChCx\rՁ<c203X1d.hFAĔscs_srį&xkܞb5#ۦ HB k 4tЫ|FG21sIʦq)IED+N>Z6K)Prݵ`tB*9rq g|'wA{M|k”*x[/: CcK ] ݫ7^oӞ|׍]9 ZAkm3 ;;.rcNY0L5!ElږJE c s /Mbl0 .z; *5U*MfP1 G! z0(Y_P2窥x}JjTsAaQ֣R[a9Y&ZX=!*I煶[ "R [BP 4aZZs~6E}t)D$ui)RcNbKΐ1ΛY c(1f>3iw:MS)j]mVv ~:v}?ǚj-Gdp-%3)Nޫb]Db )4vaEf0-{USbi>)>~뜫V88gzs)E2 $<dZ CPUrܰ\,B"|s,7X,ހH %)cYdN)%Z*ͥ9YS%W"3$d`1,԰:E kaU?= s 6  :RB1*@\Vӯ[ *zQ_o]^5FX`aO{Wt~H. \&6†P%+I+ܸ׈)*穵7Zkf4À^\4u*3_bd"X뾆vC? z,dV)g5U cAXzT,eR](sÿ_?}׿z^B|Ot7uꔣ yo’s.. ~9f٬`m ·p:4 hl#VO6t3^"ZAŔL!"ۘ ZVddqZ `PUP1g Y"oy缟sP'(g?TaCV@YQX@mUUI@Uno$jjGD/e ܉ +RK9:64֚-H41kF.i< 4OWLU $i&nܔPΩdj8TŚ UE5MߏР NDV*,W|x߈Q?M9v%jdb âkXua}scpٰ(ԔTjBRt8KRK6X6>K/n 9@)\HhE:1O4,>}`uX4bgU+9!?+, uS򺖷4MFnڜ#,N\ -+t]מ;7~w\PkU ZJ"2!a\\Ɛx=rk4BŜAsMq躾VAή2ryǵZk%\2`:Z[ 25vPÎ,Q[;o,_2 sJ65A\#8vx>|zy߽f3_,\W/7_?m[2IKj:q9z뗫*%f>lwsWKJ;r)6N Uz&uIiN|4QT }gRa d a#HS`-uAb,XX$,Ra&!P H] c*+C;|f vZDUY}BYdՍ!9ߔ Rf%J)G.5Į:*x=1O xT*cPg!T Gd\F @Uɲ/QTS<0\RNJi>R%^ g@8  fw/_ᑥln7HHaѮDJ5@s;Ns")̩d9tjIesN1s+Z2^E١d "Rb)1bC50TD$@klΛ>D~qNU!twd]?9vΨ,!5 !bk {dB.i8RPׇB s1aY>}܆pj,`\ȴVe"W.m7/ߊΚ?yABlWua[mz! z"_axl.̮60D:2Tjm[xF1E4riGNP1dJoUs69fh kR/YSlmf<?sX@)E2qֆmxf6a)}p׿~O??~/ C@|?qa5-Xy/^~X.cq'm 읧b6&yCgi4U*WZHk^͚38 ZKV(75 HjM=/@4̂ Z蜳(Wc/& em"70hmlz$"!*B[\i; sPX׈XyY$c)E:gT.zZf98aotJū̊|o+W̠|hrx1FF|΀qUr:c7n{AkJUxi͍s"ŋͳ[a1}wO_R8}9Ź3 YX"dP6sh+3eu9gBڝ30]9SRe"EQ:8iJɇqG[i Zk)OGdT)|>)׹+4vJAn.if|9hmh\ TA*9owiNt]gsֶP̕7Ix7ݸ~|:j4U!#Rh:U 5>yBζ /Jf_p6AGqZ.Kc{/-RTfU94f1LEykވ""V*sNɇpT1m@s.kiGh|mv}Z~*E+b*ka$@b8`/coi[ju_~osw UrfU6Keb\7ˮ&\,?߁B-ɋ;Uf-ªg_ofADZC9"l_G7gnCDh:4M'!H4'P!,n y炪ZfB\Ce:~T^.cVFȢ- 9+)8fc TFi"W³ )札XZ+ZߘvCWJAc0sm-- XUg*v/؏y|xXߜR>9g"]X<Z,ߌZVJd(眫٢g1"Jͭxwg_6jϟ놡Cw1NtIjXb҄h (1ι0dMBj4OtHS#g:XfRjbhQ{6,8۔~гXn."@J ͳy\f7oi͋_>?3:InԿXr*H9uYZ "Wa9UUfCM"AvR)є=#h|eWDkgJYGZ1>CVBޘU߯#8hG{5\5; 虅`=38UqcS*iRF:*&mN{fc>C¸H؅pqKS1$uMX KOu~=0Oe_i5ė7:8Zw#"TDɂ1byXTԧc: #).3܎7T7ߕ,S*zp3.cm7}>oK̜˱J=_>} cݫYj^wjXQWO~جόsOT0~ d,x[@ê6+q!`h.[;t>IjqS%M{.]qHXe>QsHx1&I23920Ӕ'F Y.o7n3aZ|W d6]o}np\E㧿?|q8b 4JL +`,?y>ݿW!ñ~ C?̵|ð* Q nVYMWI[~:4t!$ zi%H*MAEHUCjqYK9眓Y,Yh +y_Lz1a,)041i4U1sAZhA hsSZ-C\֐\y Qj/ZTr\Iw_ bj43-K~cs8hڂFj-rk58/ 1^ ֛p09\v0 Epebmns8?7۷RE*Z#-, 4;4d!(V3i躱keT•q1[h4ڹ 0<ϵpC c\"y4_kXT%RiNr%B%̥Bi ^7JHk"tDUdֶqV[* J]87AC@5ƊEԒm )E:2R@KhuhP[YU@*"|ǏyOSzesqͬ\Sa]׏wv0+ _~5tf=T,$:MN &f ǘӗϟcNd{-f2 ƊHӝWU=N*jK)rCkTUesL* 1&9k+tƧOcUN4%0 9Xb)*P8v%\rS~vQReQB"rds0<{u}g&)l]y_zU ƒT.;g Wh*YW1n{f.OOq1t:>>>}zK\ r))W>mws) ٬ocoSE]X$.EK9#qﻔS)HV&ñX#VP3+3VAH`V`o]Ftd*i_QTʜxJLh0: Y' (T :(k! 2H&眫)ňXcyOJg?Z 0 2,`ΡAV:?޹߽~O?Ͽa.%>E헧j;Owvφv >@\f{k8Nqb,aTiܰjb$%Zka0ڹ,c*WcɵZ3*rigm9攋1>)io nV,R B5GT19gsd\=2f)5b8mDߏκ\2"R+H-) Tʖp8=<=fw<`p o?c#js9pSFRf EҔy,\Tq\: Z IDAThZ|bZup8qOn$O_n/MA2U f2H*YA?sϪֺXPYAg]F4 %cUi:/mTF7c*zj*UtN%ړ.-d-g`So9}wr-ωI匾9g.#ep͝hl_'WRL-YI1 j9֏?/9&m+X1_Y?O1|" a:HE@Zrh8a?~?^nq1cZ`[ɐ*!\T[.׋7_?{ľwaXq]o4Blj#8#\ *@3pZ?yUy$g'+ ڜbM"Ձk{%V}gxx!_umF4txD-eNJcW/kVzG0ك^mXø2>lϩ\Z+ vZSYc ax<d)󇏟]+\"m?|oWø|-ٳ盍ǘ&,XƘ8]+{ <5뺒9sADC)9b5&'1lxkjQ`\ZS^/2\+ZK\`!Oy@#@b)(tu2x֞S\{3{*W`&lC1\k%bΝ "7)Z똒1,  }pCrMǿ^6=D?>_zOwۙ[-C#ʯnoROi><_ey؟703.X).΀hXbk#REXjbJIYn7uRjyZ-s.(* ^Uqa@6nysI}?Xkc%-"6Au^TDusQf1nG1jP)dø|ݟv曷7ϟi{AN'}gU<h ZE XĘbI,n9̵7D:w]7AUCNL_~Kq9Bl0Bfw\7k#I03?L$Mp aEr죪̌L}DqTe᪦~#R F jڟ˲[v^RLX%Bna"k14MHbsG ͝oB%XjmSU6u2%^a+Wʢ'bHġBM֌[ܚ'~fޕ6X[YYVt>=VEvx,n*ǥڰ- `1`wrD@i_g7[q)1Fw#B,甒u*laXm>/?p?ԗ3s/L_Jc\?u˦cyk1qZ+iKY#e$te跀ંH@`N~;C$ Pۤ`XOOq^H‘*50hÀa /sȅJl/9sڃ1 BL~cPj t:c-t< &`W?dE^ٛǞw(Yi9y:ZPUC//N2%s+2:rh0s) sH)!T'۬e` #~"fx< UYxY،j:nV??NM?9g)ӄkӒpV/).yJ4 \<GZ$;2}׍(~ Ym xܼ4L!:R1L)¶̙ /dZo. nj ń)@`jJA`GTE[ϖ3|ڃRPVH\S3c kBS75 zO=Ƭ 8~@j <\~Y/;ŬMEr<=~ۼ( Nsv?Ob:楬MϩeV.jݸZӴ3b cw8߿DZnV/^~^izy ]S])whP|c`UKE$ƮGp!0t71LF3y(QA π#3*37Q4vĄK.:*ň9EGr0sl#@R͙!rr "Ff tfD(i4N]LmѺ 31QD"7b΍l&*lK1CX rn2|}C?ovj0*Ehq/zY'ߖn4d @֥8Χi͆ JYhݝK.RqLs2!EKfRT@tR <~Cy,|@ߙh.ݥJUjcL"TJ!1$QHWUJYUj^d)vpssx{{#Q^߿͏>ֱH-S־nnBi:ץMp 1NGSp|+H>M.87YMh %Kͪ/Js.b[,h) !eQW5NVpDdكYˑ6pu!0Lltݘ7/9LM10\JA))L~>ϵ Bu|&_sDdDfқĘ >hniX uU VP7%cVde)Y/*|IOǼg8!*|ji,bwq\b KYLD]~xK]|! :C?~xxZB7~\NoùgmnD$\M|}w]X利j kDDenbx*L4Ȍ=c0[MJw,)ƪZE͍]QMkIzGQ̜B.>QlnG5-0GEw14_;s#mSG+ eMGC؂/*t)rLr>* #u:6$#j Lv,ehc.2}=_ٷ_}ja^Nl)%/, uLjkB A5=OORUD>u]'y)t}|}qԏSu)!@dbB뇮ơCe0.mv^oƮG<̀Lx<.8b ܬ~vR3۬7j,eABߧHHC?tղ0bQsj2?"f^a9Ry_8IEwq^8t]WlV^BLJwR7 ðY3GQN/jvi3Ƙbzo6M9n_~z x:x*VF>Λ|_RWՎb_͉ÜgScL!aIͫhlZO!?ȶTfwwwXn&Uܽuff[ԶP[R<j.4۸FA7^Lؘ+YeSʉvwso^RԬHU%C"!D$@Ԩj;mEqdhYg7pն#i?tun3{œAOhQM(b LkVWqZ@\e:OɼX0xs{OaW~Otn^74:.WUbRŎCJ5bL)@>OC_o/o{q>RsUtӚO?<ݫLJۛa|2<Ϗï~+3CKh1xs{aqk_||>AKBDSfk"*bTw׫.f4ji>Ǔ5a RO!j !p9R3|M13+^L.zZSSUo|jjJLh9H,9h|afH䜛G5ZD1A&L_xo:.phG@WuOP}sf+vxE{yF2 C QT.CS 5oZ%<_RJn8e}ӻ~݇EHX\! @!ths~ѬZc)f[`H!nZwx?/ů¸8/EkQ;]םg3o{ѧ#W-Wmp B 4uCw&D0 C ɛm)BZtܿ{;~>wQXJi`;.`^7m/B5/$ޜJjaJu :fQ3@Br QjLK 0 7Z-S6 =vw=q3g=.K8=t'q+E=H$֖]j]C w`Ya`VNcҕ;6Oә ZOw5ԍE;i!"be9!tSJiiXj)}<n4cjC ٹ;s/ lMc9zAs۶sYUt:Jw}a?q_mwy* /0 _|ͻw-wRͲd@M}.tiX)1ϋ:.v:ubL]?nn%Wӥ1A`н웰~;N,E̜0Df`3#w@Cr@  ` k w t*5h`>sITt9cuq_BWk,.}DMd\m}vtF&k՜[m(Iqf?&r"3k帵hЎD$RU$o #b M[Ji3!&KfynKgf-Bج8JS34ܩ}aQxhx $FZ3@ F&S LTڌ]7 Q܍QUǣQBC =`iO' #%+BX{"x6ZjmC!!li1QJADf(YtHL-02aRkhV׈tv؏#Y 0AgK*z yl)*8N"4X%tpQeY/ xEV}H2R\Z@uAun9?9+Ԕ0[Z>?W/,۷_\J(Z>]Vx{wt|k\ ಈ#Ru[v?;`>R1 }ݭ֒E,RUԅ( JTȝlnKZR+CJƑݧQRXх֢f JeS-Z|8F i C ]H]Y ء5|FiE*?vC7n?w;3GJ1Ĕ@Ca΋aa8)Ze}qd&~UteX 67oDz^#VQb$P`Ҳ"~ٝL$p `J-լ)]R"3\Ji&b1**]e!o(H퇱4_nnWr'?0FfҠ" !5W%m5\TscLzudsmQF+MBSUR*W\DQQuB5w)L0^ثsDassRs (ɼ,fbFC8 50F us)oRDSi*ޅ>b<-OQL/^teMaXT IDAT&y .֘MyqJ)Ԕf \=" :kp n |D\1䮦v987~q,cAU١)N޽뺮s.:"Fk;1S"kܤ҆ǫE\ & .j)KawDL-M NU<1AdI]rBUvpKMP>tTV qCӹNW_{7Gk4SR۷1Ǐnnk6K.)# f!tZfA-bEjm{SRDU,u]8y0h"R4k\JХY;.ի2Fo#30.f8PRhKLK)*(@x~1qDZD&$B<:n=l[@LK&U]"cw0gD`fd&@ 8?VwdQ ƔR"&":`fH0(@FaSLЪ,RhU7bC˲hjHE慐+߭,mU\ ~Z?||!Ae Ījlq^___~O7o)ww/bdWwLLj5WDDHwwwH3ֺ,"Rr^ ]SRȎn#ԟ޿937ů"6n6\[f$<?Zk1ѮZ ׁFAG R%[BRUFFuj>+۬Џ]:;02!2#oBeBRMLJm)TbDh\82F 2`$W &bnd(0%.s&`Ȣ.)r<ø 6t) HU1+0PLaZ͋a:?mwj,>/Ӽ, ǰnoۿ/gW Ny7jկ1vx6"}ݏ?]G0HAͶN8n_~|n.H!Ze ] 演_ 1Yv᧷S.1uۻRw>1M@1|2q׿?߾}o5ïn,y?o6/^?xws!ƕ/VqѢ)\7SGDhD"%,r8B61pYʼˏq*~sܽJ1\ E;a\|je,Gj:%kEDعZ"Gfފ~J ";>"+6O+Kɭ\Vݛ]TXD.ka"`(ˢ)ݫD9 2f&nO!TnBq<")bv _ ]?VW/#E9ip ]݇0aBk!*!}ߖp=bsH?&"oB.bYW S}Ry"BޗT!mq+"RrDsVhZ$1F'Tjj1; ͮ:BeTw2OH="iRBDWFC@" 7, %,sr[Z]]^x6 =ܭ.91u1@R0  Rțqbێc!u1vc_r؃@ 5 K*a$-9Blð'%\BK@XA}z:o Б{Sv3&eJ!norOӣM6o϶>W?.GJW~I]fPt ԥ8P#R.acSdBw5'BlM g @RJ1jao(}#S1_Ӟ\܄䚅chGPTRݿx6-F&ph."R\sZSH$f͜Rc@YV"ZkhWccm*uwJ>Q]+snFF=y,Fy~K^ #1 j5/kq ]>t>m[D1F"J?=vz= X cnWR#>߿!>T0lmsYcJk"  F46`<C[̍&;.㱘Q۪(G8(\JmY AÆ;г' Yjm1 u6j7[gO okg 3je4r3gq.@5<̴m ߮%®lOߞi%g \<2VLDF %6<"Bovjs.H_)}C"3Aiana/*y@d J\ Zs(b- =e͢y%rǏЧbTϱV("C\Qq̀GU'@ :"WT$DA%$F f9Z4ƾ.yȐ"}?g`q7dft!`H]!c E`t&D7JVnnOjTQ ek}Z˿/_~|chj`Ex.IK}R aVRw:K> 0 ~Տ㸦.eYw7f\w2OS%tO)T9wCOeI)'scfuSEjlpj)n^мz>D$ d0a9Nȩpq^tN!s>=|L c~- z+safXMWiSk ' "ҳ͎_4ݩnB3Wb(=.K%&ЅgbS+$˶%K. \6u]|=_5"!yj)5Y+<-5<(-{bǟ))D5ȠiqߘyUW}FxyZ  }L<4rP5sNDbKLw^$n(LZ7 {K'!: !fqK3[6[G&C'hC)T @K}d\+XR0( T R 8R"edn j4PJ):1UC.d" ] R k|Z5dx9|.)VG.jz٬޿4fC "v}|:x<"2M4/_oV-ɽ]H87l҃V6d}MJllclFxbT0?s$~#@`a!Uv KƮjwdsN{Yk1xwDfU*9;"vg=M<;;yww_2lcwqt9iav\)yQUyO>QUT5\kY\ r|޷QksIs|LϧsC-R*89Nzݯ?2tnX =rLw~εQ_tۘfqsE-B7w<˻d#jhQff.斃|#eǢZrq-jn=ViF5Q%YypͺR$AW>jp7@;\jK[Z@3}pa_Uat-чHRV<"^rNz\J)!nMl׈`n`"'= W;:nAi7O 3Kfom`5Pʍl9G ơk8u>׭63F*漦]}.ۂª#"1v1#;E\svN)rN'+VOnU 3 @L3!85$^:#ʃsތXIQЙ\JBf5R@InP8 ,7f|f{к! dG@2Ȭ%J9$AA AUj*꽗$)/ccsje99 %\]Zk͹euoIMd_ ^*"Dc\}}Z.)/}n_?cB' SpmkTxJ~MJ|!Χv+yN23S-4]Y>}7o~ʻ[zl6ȼ̩qjJNA(vw׸P !6u"RU&6^QZT9Ma`3]=j7t|В3'/?qѿp>[AM%i̴J5W,޹>t~sn_|c,?񯞦qٔ㜏 #{=@*j 9-j0 d1.z"9z6p+rMYiv\|јo$WUZ.3k 1-y9WKi,A*RJxf~8ζu~+Z@17sk`hX6=TRȋ_z|̸ryDm[?!8׋13ߞf q U \I0񣗭*DlWQ!(Vk ԦySoAgkȳuCT f`JIsET1 X jT60 jj3ǟz| 譎M f`LDJhjD3K[Z dj"EDLJ||z?<4䒽Ie?I5B*,iYI$W }7n<Χq7YjÈin/27|?9>y *Kis}7dC{%"%tqyg_0 ]|GɵG:2_&Dvw@ L5qw"Ts\U6q!DB"TktrvwwN24_x"RBs IDATvf<7wnY"~]kEO>}<i/Η=\yZbtab+LqHOSȼ؁Χ<<>V1́q&6ҒҒ'Ǯk&lKɍGWTUֽ1Z$\UipžSέb^Q'mH9;v:{=Y|l6FMJRj{SOJ|Ab^+9̎Қ }[PU0&8k~4  f*z!G;"3+&ڇι&Hj,j xyiVr5CMW L1v]- 2@炪^.'Z`3% iJ*l@HLd@`RrH/?1G!j `|sDȤͿU<*WuW}4ttѹ0-x9;0LûmuݫW۷9/e0JLyxBЗR aj7|s!)#"]Eıft:!QDs 9ȠCz"ҵoι,iZR>TҢ5qU;PUЀ۝=+{.ő6As .K:_ J7^~s*`R!)(m Vح ?D15ұ۪vlзm`m><ړj֘-qt@I +ldY# mqr9痔W/*mQAԄcz )isfh4Omb֏ 9RRVk5[V nzi 0M9\W tj"t}\UwlV&p] 9GfWpbyW(]ERmCm2Fa_:ǰ22u<uuU13]Υ"R T*y>pY嗚KɍLgU5@$v95a-jce ؒl.YZ0k>pZ*8sJeCdtZX?/y;tSa2tLOOޅ8~w|>e3:Ng?4i{|;?~W/iyU/`|>w}TQ1G@H x UEluvClvb1)9qWOP+7{v\r_-64Tj:zv82538TL*bNUC!T]AV(O&XJaTkYWak\ Jð61ƾT51%ͪ6i"k:ĮeSK]aX͗U7xgd\K)R+^瀖 :YqlnKXP`f̤,PLL9'/v7Yι3Xe{П VbAL޵d^.GP9yBgJDHHwlR@T$Z~@#H%כhNeA$!87GXh^Wu85NmeQi>)-{>̦uNj"9B0UQ*:9iza nP`Q0je.eɂ+EI5YJ-bhL-unX71b:hZRJ<PJmhȫWVyx|z>Ep$P߼1]yYfbT2oƮ`O:fVgw&+!}L+{A y0Ncq KJth~?t{7f7!t."(04M<-bbG\DUlF(|S4CG@ ZDTRkTZnDQI0;=h&~da:]f3w*u7kmhkj>eQBhWsmxM Z7-҉RJ* kA. FjK斖+gCۤߦ֓k9ZanR%9hҲO8RyD 1#29XU & [ 1vf "J.0ZZ!"tuNj4y.THmVͪ)*ҦFj"D@!oQ9"\QbS ֜k.j?D :$e>괇q V@oD&L5|z-Rb>a{;]NBRKi`U54 Uz %{>u@{No&Ir O~qt>"8n$┚U!h%s\iRJlz~~no7^8Xpb-<͗9ǡ{xDi)i8x1C@.yY:G(㙹!v}uVl:D%-0g3)"ՠ^0 )-"`F%U0#f>.9W`YG"Ą%gOn)2:x||$os%s݆MG_/O#-s7|O1#.+ [iສm`Kk]ͅc7#"Ȏo<2~+@Wo"bRA(7:Piۂ+~xBs@l:Úl VEFj惛#lDDKi!x*cn+SDQafv|]3BOx BR9gbݰ֢ }\j 7Io{;#7#tmC& m)< sF0{@R"w0 DTrrfpc+B)90!T3D`KZ2ҁh;} e A0P40$*R!#0)c5<^5bsR-P>0wդJ!ơs#1#Jb׵ KNN○; 4UJtQR\ۋཏ] М||i'$d_ 3;ǢܶXȶ,s )"ݧiowK?Ǎ?ȆC ޹y˥SDUxci6yZBMj{3kߍv3윚zf`m'fpD]ə6~ZyI 6 8bh09/iLӼoeI|xz)!-t8j)0yJ4չ5``Jd8x.J5":RjJI ~2y'JO9$fۻ;8]"fHߝ.iˇW8aB*Wߜ<~q,紤LTb\jU%VkSțFh_*RKi5͝+OfӠ*V(W@P>=5W_me`6_kZc{vn}ߞxfR+ӴߔY1U+_{ yYZ^`Ԡ p-[- v&ewjsIڵ_mPSuajf*-#@ujjfv1:RʢL)d. ؞DvN`hSGD.!TfU@֚U좏AJ!0I`B1*@1jf/kUPrYb Z*p/hUb"S_eY>}΢۾~ѹOaњb F Lh @)Z4&;`1c8x3( Uj`IYD{S(Ek+CL@L~Y*wW6EJ)cRWjf8kg?So /ƿ1~G?s_ql6?sr:Ծ1vN~mRJ>nh޿oox8n eLCE߽|xxYs> Qm Ď[Z%Cf^-הFJY[B+<|wkV&k(#2SàZi5% 'Ht;Gv܌rJ6<#b-~VVD@ W$jWCyYVCG:CX#M;eMpy^m2kHZi!"zbE#!oȺ^wMV6lm (sΈˢ-a'!)E@CVj!#ZwױT PDb8$-yЪD 6R;sD߳ `aQA fr\jR#@ [`xu%@6t{J9לaJVsǓ{ߝK;i DYQhm^ZDEP ,VHEM~(hӼ$39^ +q8;"22R.rb'4Ow1FOLLB0v @sXo$Wo/_=~%6ڇo!-ZfsNtcgQKvՙY- s)bTeCWké%WQl=w~iyFfO\v1qI0Ŭ T.Җt8ؑsԔs6W0 |vG΅ZZeT1UJyy]x:Nݦ.0-ᓻ>S"%״.E'7M2R٪C#ت3C?O6g^]R-aט|qYѮV1gɈPDTj)CM>&߾~|])֚5yv&C "H1TR6,JTcqhpDk+Z)i Ӳ,H3G)%)S #kyG}Tm}ߛT"~wk҇/ܯI1( x9ͯe״v3CwYOO朘=H2yjKUP% h0 {UV 6%e2}u޻CY8c.v5b6~Nīt<%1|eo6sJRM^r͒a%WCi8lK) T< ”bZS\i|{& i)A 4՚;}s/^ <ݾLKpyxxo׿]\$%y*goaLs質c2"бcR"6}(5ԻyQnsV2b!y5ZRkJe\U!3;kM m L<Ϊm'PjmlT<˾lJb l%-BWv¶[lDZV_{eM5쭽!6JBDҢVsĮn0+{Oӱ?D0`5͉ǾK^Ŗ,RMY)vw[oo?~aMǥ@0svry:<-p0kQf[qUt8,/^.-9vH9W}_ IDAT8Uv7n8\dc? |YvL8ْUF5^T޽?T>;v>~ZŤ j-˴,Kucn_Z+eL! 0;K-)e.\@/yQ?wwߋB5(Up:?ہ|Upw}|o_?'輙eD EV:AUt^.d r>sK^-2k"g1QAia®!r5xNWj(th>wQ~_?+)ۊ>yKT3RnRk qۢ9$je !4Sy.o8IA[1S]cy9UrnHެD;jtj\a6Wwzw`ג.ƒ_9=u!+6jHYkY룙ZJ҂U3$n,70bthՠ*;Ll )8֔"z;Ni䩜Ax.\8ͦH*;yҟ76ӳ]D D`&1JwEbDj,u%t}%e3 v4*x$tT^Hl lVӼq|{zm :<jB6Ӛ2]Ëۇn3g'sD2 ݸr|V%2nɄ F]yC `=1\nm=Oݛ M u! !;o^O?o??KYǗ= G9}[cyͰ}~O~},"y^.K)*;K2yS\B}yIG?tI^u])GǔΏ?~EN_o>.iam_FFb/`󡣒'%zvR`l{ZĴʡvm@\i"j-mSPgKfZCM~t]4pmjbjV\?{)fFKRK?][/ 2/VCB={LjtԺZ]uKgE特9ljh@UL4/c54/RRR.7߮P3 Wձޢ1xf&87bQ61"a="8YiRBZgZ̀(nlns2[ Wsu"^w tixgf U*XJ5WK-SN1"!1sQUnsyBڌc Z QSLnn&X'23]&gWs``&"ew-R  0^m׎+{,/ @R(pYi E* J*b撸=yg! ʚr LGm y=Zeó2_.|Z~~KK?ݧ}~Ek?˾8wYj.L#2m? Wϗ) Ƿό耒H9n","|.Љi14p7 ٻuD$ Pj)yZϯ^ϵ7_s؟?Ki2MttnܼzԾuDt"I,s\.㱊lasC/n+. {>/|@"K޹t^tَ/տn򲤔yT/\"Pj??;w_yy9N4Oi Z:, HFȤ.޶7?nJԪfjW\^yĎb3DLԪ*}p5!zsO5վ{_E.WW:SUCvДMmr|@ E's]3X$rDj+[̌Hy<"4voa 7I4Ӯ9XwiBjͦC1̌RQK mU jcxyf6hE,@銥f *u TM)PXC5:Ɔn J]YZRə`% )` v{~"ZHV;4"-۷3Br &"h-KVTuD Ҁ3G,U.PTJQbDk)s@-~Wp1W]7zzgbOcݫ_[C^Uϗ.!XY6tK)ee;v sɴ?߼}]u𳯾颢9)ą (Z7CLqYƱr9܅ZyqD̈3ϗo_U* XT޽{w:/|CׇoނCl&PTSdOtmҚu|YtYiN`dNR*|9Η<<-2wCU]}OtW]~,4%{xWPE 5 w) …b" mTE Hys[^G|;l^뜊$mwp4r96ɵ'Zm jHעb #w]Q|vi1EԶɝ2  ytH5.9PS30LU+1{ŸN̥.@!x54ezl&0w3:loR'C!!CW@jQM1GȀ5OK-ug.wwNJOicN/ve92.x2VK|\ +83 hMKbϝ<(CslφP?:1 ]j coj?4L"Ƴ=49p)Z}e" fvJ梢b]ۻ~ؚ}R!"RŎP0ɟ9zruM<)aE Tq / l&U#\_]7<WB%x~~r9Kױr1w}t"ٝD: yj`ǖ! NA-114y^Jɭy'"gm0٬ #'&nju]]«ߝRv`!3+DV5ہ l7D|Pl(WZXRچU&kýZFZ[>v~8Z>+ד 7o祙Rv,OUGB(g6ʜwmͫ'!:сR#q*kqg#3L`&%8Bˀ$gf)#E^4r@)_8@Q '?}wUJ>?{|9 4]g)\ی'o}"1`/Kw6~cL\u!:MI`)bp\$a7nqλm "H衲Li*Xpؿ|l 䓘C*eqۍMRHa=+bq.,qfP@J)lbwE)$g^˻Capf d97H[o}K_|ڤbSV!aRJ-u>$3lf2.gʋo|_g~wexyX.vZ99ޓs= * R)1 t0h"vHxoC(=]9esJ[^*S@cZ|t'Q+HT& "b+M>y@睡5jNd}W"\ڰJm9a`jּ^=Ռy !NZ}c݃Snߢ(4;1/ lv:wRzU%ðH R;UǪl-`[M l]?m錫7HJ\r"^j1S]YdUFG_1YM5crD Ayk^'Fsc YsZࠩSBWWp|Syze#PVf=+ \sH&Z9nG ȠIq5W5U(b9ryEMEjͬ!}Wr*B|żMJ1/a^DCJx<Sι,^z7^_ŇNscUźp/ tܳDgJ|hj߅maő\b~?PM;5}M}qssH*yr8u]J=3Gߍ3\Gu4NInGC#, s*Ei.6S""i~+Q#xSbKT4aJ,D~fu~<7yk/:ƓG9-l{v?.Ku%(礈.U TՋ/vr3t|YO/<}8/f{9qO< #BU3  ŀ6Yoh'tj:!p͹ՔVE_V!HnH"Am͚~y=%i6u΋RRPP0hLָGbԇ#k>nj&"]:1&3X̸fC'ўH@\K)8)%CfJrNSG:)ir䈂?Wk+r 40"baDh)hf6f!$3 ';:p^ PE j$_Qke4qd6;2rxtѪT㒘b! Z-N۝mHDlSD,` j؞> `(!B CZ-DAX~vM֪(Ya V-EĦ-؝hm%CuMXUY04Gg/^{?e^,Ibſ[oq-rg]vDD 5Uԅ0vw,'~ٟ7Ʌ~%rdp}/w\I|H۽s!K|Á|uqK9s}߃ÒBL%CW W6C0 @A?-S/勛wg)8kć㑁pCk8 sËg_x}|G"Ԝ2vty^{w1J9 4v|Ry$uO~3?aSi6:BƦs:\p 5"וٷ؊+6ս#T  Q2i5JwN|\+ z5Xu"!T[^IDxuknv+|5Ul GB+ RhOH^ڜ?My.|]k~ۋ_kAiK~g-5oUAK5liq`o,"ffhJ8y< FD4\kX0Rk:]L5;F^™ʎySY*8$Ur-%Bu|@0SVyƵFHL P$NpD޹ G:@1e:(yuY4bf3ͦbU8i:HZbeU=a_Ox9T'! `eSPpQQ@Zv5F1R[*"]`_DƭSOn] 7w~k$@^\|nm/.w?onxIc-wwW)eKbws{ŋ~AGyoC#z s4~R(SΩ$q]眦)r\|t͕E7>*\rn|ŋO s|g/_~43/s¸@<-\T}v8! RJsNLɓێ7PGwxn^ް+qCG_߃^<=~t*W{ _n7Ӓ&!x wݦ>78a7~˟<{?x}yM%/l#"5׎V w<Xvl;ƇzRo ؜+OQY-aYWl >_! 3 Ժ:, tQ x\@2#" #,5W.ozꂛ=S `c>>n^Ǒijt(jInv!(K̦*,,ab4x< ]p 7M`sNGr3ýS?4{t}ͅ9S*}-s){~%-sWø LXǃpc睓R Ի`]ωo1gbq{̇T 'uw|q>,9ef56Yjs)G^!C.XR9.?S t,s-T˻}iIts&w}~e%"\=n#w۟~'(\öC.iNkqϮS]7ē9薚sNPYu...>/֟o{uqɥlX|{uut0CJl[R=9WDP5ۻl IDATʖm6# V9 :|vĮT$ܽk9=w\i63Aw`80 ˲ R)#O9Uު"F~+L\|rxyP~"\ j0f><8 1]/JUc]NJ w])%gМtS3sʹ̖e@-P5|#.Df&'t3&GHH>"bpډ Ie" pvQBF1úm Q0r08st:AӀ1 ,ԢIٹYS*dFjMicB%0-npV,;7_p}vŗ[|vxw4Bw~'R.ٟG4Cr=)!r6{ݭV*RKscTi?Z2hc1ӢpPOLH $$$ØC_}/&"SVU}哏@F%dPsHwQq.gϓ)yaszwm,RKqDyFGJ10kps.c 4Dj)%f>^EH?=\-"@fϧܮc%/4 /6I\+bĆf H `J"&RCsf(|ѹGv&~Y\#6v䠔Z+!8P+5fhipNi"h"6Ս fju`0f3cH. "TwL9-wqA""r<޷dBs!3]IɻaƱr-L0t"u5lI!8lLII&f@m-e|wg?' ArwC1G2Wl٫on'KnȕȠIT@@QrbTRgo1|,q=dcuEl09p!l[>0cf})R0/{&J޳obRHZyȳÑh@R9J`/r8JO\2Cm>đ>l|7,=3I΍#m;oWGP0QGKm̌z׭ƨ!I,U ݰ `QI&R㦋}_|_j Ώ|vDcQ sL! P[zvSc+W^ʍJ90"q14"m֧cY0nӾxr.Ѐueϒ 8x 4km֬?kEιLI2}võn;2kN쯟4pZӶoa"m4DxDl)!~fv#i}Z> VF_*=;j&GAi*Z&N̤#bDC Q{,Dc!D1YDu.ɘH2ػ\B53Q*=7"xm,]&^tzUKuߎ6^'e]zy)^^]y~7~[_B/ajǵ>'/xp٧~y8(Zr<,F7!kn{‹x"9 k>D낉>x?jq OT4qbY X"EEkZ{E$*J"} 璘Au)Rټ 6m|O9eq.l4)ewCF·7\_*R (X*rnvۍ^/|G 粤ι`h DLm{QW=O_gɟ޾$:t^+)@{ HF&RO @Ch>!ɃlA+{ 1vfò+7>qMJZWmѫ+h+!轭u^@M9) Eke]smd5M"R%vm \ nz]r)UjN͍SO,lŘ껮v˲YΙcfkQ[6}xZѼwŒK[%>x"O C䕰伪 jۮ#%eB\Y0E÷6qXTdIX_t(Sc%x ݿ1r"mo@H.nC엔V{5C@pԴۣQ\yN)mz(ZHtAetL\Rk%آٜ͐(B"9<ݨ(ZB9qrtK/eI ⦚[ W\9iA//A)9ͽCTR1BԪkڝoM\f"]w}Kr{5Lc-m\rqqGm}NEJ)ӜR1ܞ‚A`)t=7uв(E@U!:kY3k(*;a߳89408n//<>FSQ2:n"O&zǻ1flqx;n';;91Kk̈U5clVC!Ķ4P!< %F&Tv#4o̹W"1,rğ!:Lt0D՝|^KamQLIEYUme'Eqenň6tњj~_x8q`WiE 9m\;MOttD!0|svSD} n6f>fa$ιWEv:->{OU]{B[3! D0"m:gCT@y6\߫ءlcu’Aךl/U V ;붛,9n;ncKMG? Cx53T[%`[yqU&iE""A*'dڬ(FGM^+f*C Z" BX4֒p ؏r~c6OeO??wHP<6eI~1pyNIJ'!ƱǾ]@PJy99JPe\hdPQTQ%K63fEĂ|W! 2K1R1Eafi ).):B_y0Dc@X[kM)KDoWaMC7`tM}AEٲ,< gw7'yr.v@)9baf8sֺ釥gW~oǿ[ɝZ5B w *"8]׹ץa/Y3?hh-z=UC$G aZɥ^V @T>v?8blwZ [<5xoCf-k)lP#5lͻylC4YYE%?(}n3c㺮k۝`'66mSJԪ*!n/j@2R{ yA uڐkW`Jkg󙐦0uU' Xδ'mF^(H 炒jmΨsi^\6-Qb¥KkGVJ(՜A j)*,KaA xpqhj1|hZ,ku @LIDDw`H{2Dķ6BmO "ػ W4,sG~gכީ4W~_|#eT)wqdfo]\]7 uCpNl/ϏcI=xp2dC#J⽿8x?48ގ]7c{O Hgv5@UœYỶuT!mfdBU¥*5w "&ꁶFcw!8|q/Զifzt"ZL(ªG-PX@.nv \a IM78^9 KNy؅("f]"eI 2AVHǮ, ̺#%9R2T*9r\k:6X 69QR4b ج\в# @"lqD%\[jNm SZe>ӫ)rܸˏnnzB<]loG&(ٙZ([w(yq)-mfk)Y+]*q3>i唋=>S·:f<|Wй$S].ޗ͸y(K)e\ `TmH9X׆$p>*-c?2Dh%g@¶UwwZbhl~b7]b)/e؎S]=,YxwDtvvsW;'o>>ji2rl9WUyYJi)n{'O}o~_w˫|pc90eiI %vTg#3ZæcGE9qv އtc\QM=8 iӵeqbY1%jf软pҭDaaoA]͑q ާr > N'gt)Tr!phx*MVJi*/wS΅}#xs&~xyQ.Kv '(xH qtaIQѼC34P"n8:` M褃Eع'lG913p䮺J$ s;ȌVe9q Rkre ꎈTT LMu񀻡gO/=Ro*DsaݫǏ^߽}a0 k(  [wȇ9@|^K4}P-|\q#o/#"HۗTÕvxīowgcY0D`QD ɓ Z滛mTǗ,m.7aQ7$~&e_+ ((b{4`Tժ<8Άiy8Xy): _ݝr=;O$𓫳컾tbkUEdE !@ Їhfqqep谓C:cRrL0Dqr%Bm""!NA v3J 3z]@]PD [rӇEG_,_7~/mfy-mŀVtࣻqER6qq׷SO7z'_믽c)D; =ͣ" ܀9q#q*͖ǻ}SVqۗ7HԇGG\ ԅ(Fc$D/5kYE@KI}`ᦔVgUqTM8r ZVj>5}Ehsna|68s!Db5!ɒ H)MӴl,\sZk3' FML:OfZ1/Ӳ,x<6ܩjM's&CL5{fM^ŋ$VD:sܜNY~ /7b< @4~/ H *YnW{Dй*0pp~X}Ou\~ :u^ԪGdÖrARRJθ9@Į?m?)Sv'y&٪:օjmRRqWԗ/_.){l68&+..6P 1YͶb$f #R6OTТZ@9x+hJhSt[Ʒ'nS rc_^ninC!c#9|oLeV|> ԅMsCi8,e(Eգn_VM"¬֦uNf")7>pkW??wozb*>4v{=|Hpq.vUS"`cY X3[l*<Ѝ%Ҳ$-9@9j !ω@?5Ԍx-nt][ D{u F5knkԬdiFDhe` [AB. HJM8s."Ο.T!y疖Jt7Zw~j.~fD{~`O֛5Kv]w~k wPAHq@S%YvmG#/P;Žmw_jʦBI(P2OfPQUu_&^حFIDZo!"Qʒ K"l္kƈX=%o }p T3D/7@Ύ\DϋxgIIh[w Zy8_zXZ IDATlND\Jƙ ԥskh7.DBLBQ P;3EF X(0 A F036\^pJwHw]}jJ=~A~O:??>)Ne,)iZVVSc{v.2׀{"3C_V$?Kmͳ^b"Ess }B6;wtf\qqqAD ӹCp8̇Cf)xR~tyPubVP &Rhpnx R'0.URq Ffl6۪d"6U$ SJ FI \\^qQAD~ƇE'CCbe*fm&|{{{zvWJw>ӛ}ߍi}O~o<_=Ž_9E A{N~hR&_J]0e3%#5y^Q"pf?,OH&fN"`ԚG|ָy?-݆c* `0ˆsx bռ4-8F["\>3la $B64mx*SJG=SPWo@accI׵&;"?Ghː0D[i<o/0iu @A,Tq$BnKI=T};K='Z@]R8cfx5*Yo%։Z#&Dܩ5N{DXMbyYmf")1oETJKZ.t uV PB2z_9"u';̦=f(Sq;ICØe9Ǜ;wǿ٠<4jXgnZתi w=~{?@PD66,u132"$%xz=n}fv[zڋݞ\~BM)ő]ڠYzI""᎑La`ff #E!y}ߩes'}?"²jC1֪\LDnkl|ͫe`O}/?/WO}fnn|oYup5Lskm("/^>wmmϷ8=O'owq6^l5!Ikfs,Dt7ѬU-C̠9 mJ<0MmG}lFR#`e4GYb~ qT| 7 YUCx +8(Vs;6ţ|N fbAZ1͠bY Mn'ڰ$!e.eeI]cS&;f&QmMkPQrETX)8 3XT8;t$Xv8wZ϶aNc-?]_?Ͼ BPpgɉܚ"ɴ]*Ti 3sE( ۋ;MW_Bk&Ӳl2VW-N AR/./a:L^$noyPO$qs"2_ L}BQ@ͭ81 zZ3 0w"";#nXk3N/..϶C%X$6ܲeK-(C꺪j3Q/Y"z׾j%h?N}g6]+ T H2SyZ9W @DVמ˗xna)v譛?~᱔2[՜ĚJn5R5n ,s17Wڀj~QM{X}'׎ZƯ^@MSAGNzZr1G1w~rFI|v}5,'p\nH]6S#,l᫗N+ ;Mם C]@Z[wZizgS]odi3s!&D2+s?.%am?\̈́N2"RʼaqJMw7 =`$pkvxɽ>8gyY&(;  Ĭa~z8 ZbR0[ XOIkAݑ\UovafD@>A03u7 W T$4CM9$F8"Z4}U2Tţx.$F QNѡ;@ Q@m?3."/^p3d X=A >gOL3#AnYrїhݎ٣'O> _%"(%J:-Ke0Y¢. Vn="nqf,LԠg9@FPx:TOj=cYDO @,jV$!&":EL!,$> "c'A }SN4S+Gh%`KY"}h}3[ݪibK9_ety5^o~O_l짹뺖8Pp"`uzh-ɪ%bqf}YpZ0_W;K/>Og;'~~|ȐIf͔sT E9UfTUjAD]7,.R\9K֚^7`O+] O  D U ^|m/L$oO 1|:9vn38k8vҘ5u贍9XG M=^ʗ7ֶ[OuH6-1ɺtl "J-KYLS\MBZ6Hui%Yk{h'WeR,pX8w׵j"&PZ˜stoG{ORH2~}!zbh"8D Es7ȡ{M$3 J%2ZT:&X=aVt3@DvER&!lRXDݹCKK u_N3n&Pج6PԌQ0TQ]Y;ux୻o]/~՟?<;tۋҍ/N\twu57W$A&Ak1ZK1#z!ɐ:b)t@yt~swvLB6yU-"w`2..Dggg}{٦oMeS)tL`nnzݖF}$RpKR7k#A.̬ţK'CL$$^BFyy'fVji)}!K23NdӼC,w/,0n3:?ٳ}>|E \D0qu]e[aNN> Νw>QC7l) 8t4 [㿂nx~ݻwy;_v{aWwhМSj }ob4kxuū!r%Q/Ex}G6C[#jq]OmiD9vg#zS'7ZyÉct[8@Mj 7;i9ehkALSA-205Mm%Lw\%QKM렪NDwF)n3W'v(S::ֲ,Eoon=~H0 mry~.r޽~7fk<Xuw6 ]51;w?>y;8 n0jH{fNLe>03BcG@s $. "AΙͼZitf'n?(P)ַcssO66ӜZi%N*-G(On+14/ZU7 a/!"ZShE KI&kl%jp?ϱ0{mk0}/-G{SvZxhU%"5"ץ_93`hNKﶇÄc.ζns}q9|o}[?//r7gt}h^/t`NCB5(w9ҴK)@I,j%04 3wt՜p@fSݜEyiAjpG@ʹT2p FTw$4M} # ]n;Z E24`a+@5rH}AAgTAͫ:D|Awqy:g'N)Oz?v*BIt 1!]k L\<DDal]^޽zԋR|1Rg|/)v7j׷Ļe ʲ,Hz-Kz:9ͶeS]{q)e>}R"cW:W;P&OLQ8pZKAf%dLBXvgXMQq?/w/ϻ}Nxiƾi#9i f; BZ2tYlY..TooosqNW7'~}?zyqG3s@3%gW)cGD}oZƑ߿w}}sysJ)qlۧeyUk%IIrJbIx{r/G~>߿譻w/5I&52] /_zur{u.(ፗ:^1RV?c.m ̎%f-p+:"n6ǪjRoM=mZku9heVSK=6ssJIm sI Z=A̪ͳ_~WS+$4p Zk73+ y堥4nh:"LPR $%}/Eݧi=KQ$DSn2`=#ץj2n|0q?/ Q.jXGxJ0RU)rPZU"KȈݔIL(1 M#}n.z e! eYnowc>nڰ Fn,ұy;Ͼ{6m5P'I9490#5?1sb[oM4@R+&YnVkYK٧n]vHKY=w?o?}.7vݸ!6hZnr͵%).@ٳH'IغU51{mJXo\b:h#4 C 1Sl z81-r Dzʉ1rĉTĸk=JY$CKᑥ0Wv)7"7uONk-:DɭW| "bX׆C[RD9*.Pua$ wo?y\2@B_Le0 D]g"8L~B|P="PIADd6If~S1"PQu%D#RFk^A 2S5ǖRp9wu7g.IJ yi{+k^+F7/> t8|'xKܧ_ݯ_-UzzqsX :^n=ԊEx,H  `@. @p&ģ6"`"$RJZ7|, 0D H-r1;X9peITL.-%9,eWe9K@dDx6nvh2zx j>u*Ts,$L]EzIœ,&@m68^2[I$EwԲRJu*aeQpr@0 z(9]팺{>|LguGB饛:X1P + Uri} v9gRa):*h[wu9%(:oǎ/~˟?~}~9[J6\e " Z"X cOdfSm^}Lm.fۙq­"juahYǣbV0tS IDATgOg Muj*O qDx;kgwT[U1Qs"f^4Y'kSfaw+H3Vjf=h_̭>pwzHC{4cL`:V/5%Zjs%]Cdqs~ЉkVgerЙ:޽D$}h΄hGz5AyYb=Fz 7wǣY*Ep77U:v l%ΙֶWȄ௲#ŁN6f|?|pi6Kh!JMB9#/ApOLĴ~YciR54us{cΏiZ@}W]fH@vĘW ) :nG;bNm=,9-˲̻7}wy񩚹0D@5F{UTSt`iN@'wuBO?7"^-8 W3 $)sbR7 Fͨ-pueLh! `P7}绛Cky./_ϾO>Ջ/g }d$DQťbSfb"ȝScVtp+R ׬!"Dp1'f L Tv3&$y.UDܱQ̩,insnl^eLeZp8[J 5¼zS4):,<3s@fM N k~^&a]1 Cifb"49 ,b `HH9 8 ) CY4T]’qDl0 %<6MUX6;IsC?Y pXU.!8osvA9$ˬI$E_<. 9ц B>#|cVaZ?8~Y~3VUPhZq`iþL^`qUwࠔllj??yۿw?|LiWd-U]=LSS'" Vݣs 5yOCSI@{܂]?h1h9k8enMo4aܬj=vynkg&e^֖R,z!T-t]א>˲jos h Rx̜RG oHU# =xc[t&^Z#b=?w{ɝrJddJsY-Vgr΋*$o2[~]QO&4+ Ե )cm7W?2hMڦD"4Kk7+ @ʂB`Ͼ=}.Kџ?,/_=},퇳]-\8j9H9Sh^^ @m#aHpS/̴"t&ϣZJ<1oYA 1 @g-4JPשZ]@~Zu}UVN"ុt736nCF$҉.!"cܻsr+؉ r($c ZIfZ Dϝ89KΙ@wwu%,D(,< 1Kwc?7\rPf4nϦ_=ѝeRT8U2 C|5n@vUf$zF"66=/ZU CDy 8D'"R넨w<^0/K~?'?7>۟[ラǏK \Rƣj /lY 6{Zekݛ$e;pɂbj&fffZnzBI+="-5^폧itryz7g$MMIByzMk?S$}"իU06~UAan*fBrpĶfoy:`'3q<~Ǐ=xAAvz6Tիzuw c{w.!_^YxpZ0 D&v fdTM[<""YTҐ )]DG'R jZ"* "i@U L%!-, nXt3U u咙E0 gAj񣯽2Og?{\~^.+AI  D‹^K2srgw ;q ;I*q%RMBfZh&-p=W/gY"4600w}SBe)u_T˫Rh.{jJ&us V7IhVmZ%c,Ob$|)D0Md!rbsA?0haJ7}.gaJ,]g-'V4ϥE:s6>zJIkLNƻg=x(\,͙W1Wk;<k(?֥ͬU|ں }+$5 u7CnwcnܥjGZxȩϹCճ~:L'~^TK)$ 8LUd=ݭSԍc㯤ۊSD4ͧ(6F}3ZGLXʧuceZ)9w׊!""\A̪m6pk{n藗p 602&S_UדdkNv""R,eN,ZTH/#{-H Lm 1᰹ˌe`+߾k7ӣ@B$12Ҹ_uYrVD\ݭ )J hrb\4E՜gD+ pm5q2l6|| vմжFYه_+pvnRwt5 /G+hO !!؟W%{/&Y_2 ֠aaO-1 øar~ggnu?n6o?K7۳xfE}89ë$y#Rg犁p-xzwg<^y޽Je=_)t؞7jpkpjWü3gQS({IpS7]|p.0}޻/@gozʔz?j~m;vA8~ZD(TRJ{ $2 Rf I2ͺSDM8eW~谈qw0Ĵَ)RϟR(9~|_M_}{?.>{;\Qi$\|IǛaC0tZY> VصԶ=bY`lj.E*hBD@3IMeQ1m ?&Ĕj874p*ז#i+#19>z8#"\<4TAi=W*_o=yx{3C@ @oS efF9w]g|[~q5wJ1 Kl&^lqf"3ez@y;殫fMeyؚ>ɪcl0e ~0Q#CNQaCv(dGp@Jh }oߩr8Z~'-H·̬<ַ ̬OTBplKB .f`g$]r4E` Dd{q>`s_|o׿_~:f3 1.Z.  mК޹7,ynC#ENMA|sSʇ[;"GĪYK^}i~0:Lk,?to4_i}B ,zm6}N7"F1ZUMӔU_l!83pPB"$5sbB ss5bbd@@H1q4$.MI1FAaBq7||#6W g!Ƨ|^m/$N.O?jJiኤKf}.Zr}"4qEb"6~QAxeJ]w;?}덏޻͉D\<(uQn] YL,ADd܌L !0BI՚mLfviGY6O'FjVVUJDZqk6yT B7`1oc2ۥiͮNݚ8]5bm)Tf^'!j/܁Y[./|v3 Zk;I4fuÇ_}""F@ͭz["э1={ 7)ܻə<יj5^&DB*0/w7u E҂ҹ@{!=ҝw+h12 0%$Y}^!a, mzw&}]x\GsP(H jCH][x;p)jEYcD%Վvۻ^omB4*Յk19V1EV{~,w]gT݀H5΍C|~vv84!u}:"jv̸6.DZl# RAkm=yA O x7 v3[ٜmE}fs-Q}uX]:Y)ugnEDn#"HVH |o|gjl=~zySTj3 #TAX[js@p0D&K7%]#/ L]j9v K4(Ccpk 5?TDnVTs_^DfnA; ns 1xGjt۬Cf^K W.XdD#4sZh-0E>^ܺH} q3_w@sW7Eff"y.Yq#yC5: #0`v@hTBBoJ;\82ډ՚S 2:բD$ r=`f-cBeq¤I9A#< xo]Oy[k󦋛!w<e0 fhS==vc jLdK'nx-»=Dq?Z['y{[ZTea?ݿe8N8́ݺ[aڜLyvM x<9_13ODZVM-@|`.`ғ4UIDNfVL+f?XugC +S(̮ZEBFƳr SZnDD`s|vv.|t}JM]uqq{?^.mm\_!lz7Nf_T#: >*U:E41B%ru;V?3bLSJ܅bN J)á:)>iv5!Ƙkʀ (ObjLZ<@s A&2!T.!phSG.̜ I<()uc5Kd$rfU K\pZk R@8^a?>?я>_OϾ,MX IDATҝ߂ Wa{+A 3Hx:K-RDt)#7{UXp R{Z-.$sUp8,UY<+NɁ [{̽>~/տx} _g>77?xcyC ("Nf YUk5`4y,wD0qs^\׏ڊv ``vZ*"` Ԃ6NL kYk !Kvoi+*)-H DJAU.b((~s{ZJ1KpŌ!$P ifhT͒JShePC)1d0v` BitKfu4DAHxuvCUT+ق/q3\':i-tUSZ}*I D#vbwؼ̍ !牄#; Udr:^r].<ijdBPާPj`Mt94Mro~)6vV-DcAhK&n]K)tRӘ #s03Cy {wn?bUU[s<?| 8sB4Ta$ZC%\fɌ]b23qe"76gt8dUꥨ-Ȉ,PL!pSf5oaQlW@҅V!4u3P[DAP)J,-փP 4XMj=n}7DSS/ѣGN"WI /f'8hݸon70oe$^a7T Zsui=;eA3EchjUyԒ s3)M՟ݷ{2} _WG>%2Me֣VjB]#ҝkC~Ns^ꭐ#"~ӷHU Zn q<7L2[$,KS F~JfA15yQBr v34Mf^]"5kfL]g.bạBdwZB*)K]uޝ۷n]D!*,g͔gwEp0CbpDbFj.%C&#*3c# x<ܹ5]lVLѮS͠lhYDλs4_?=8:[i!(hB82W-S-uD1 EHD-"1SXf욫sjΗt4H 'z}=Z\C 1Uak9O9zη~Bz6-jf,"^jk\㛅`iJ';FD, -bݿo:uy]PcuI8vaAd؞",)u]7 HV*զoRS?1geO|ZZMJi RJ!$qt7/-JkGZ'tD]ǣKviQhuDLk?0wh >bZj7}uZuRrw2~L3Ex,)a7Orn]z7fj f$"y<ԹxU!FǚԻ::2;\q9yaRj=A0yj,&y4Fn ږq漸q乤lcVSmTfwSlJhp1HN|Rp)Fa|`'@ײ+ǩA)%u׾bhH7 !r<ið==DY6 t@ Pؙ>v;>@`Z]K-ي9CTmnܬ\W Zḻx,jZ dދ^ LG]qC*Ul| 2;*"HpLF]jժV PEnX޼+moM`b`whE 4M7ϒڴewqM1Z>ƒ}ߏ<0 ~?Q볧~?y_Uv؅<(D:6Efur`PRy{g~_ħ>ƃjW%>`s57Q]4!z TέZU6HsmW7hb/pBxj3fNbmP`+Rzi1 ШD*5ks~"jE NeΧ=Z{je? l>|۩:Ԭ[ qw k+/rUet48匈`(i-b9lED*CۈT7 @zHT_ؿxZ\՚E$D7-0ɶH;8WԥCεRzzj:k|T-@V-`hF "!WKPRč V3kYHHFaxD7yPD" Fs71R~ӟ@ulRɥð^^]}~?5|puyًjIr. *"]u*~I[DGuFv6U JJtD3OSњ pcb[)Yta{9nφ;v0c{D $!`O #uU&: SBG!!Đ橱5[J0fVggg)u9R1Ŭ@tw ߽kY ĉĴ֬L0O%vݣ[p2ɵѾaeiyX_c}!\(ᱜ(ΠΡj68eD#F拨@jR9zΓFaӢNb{={?pv"qeETS?Z˜AUrHb6ͺ.5ȡ`0w0 mpX(Fm, zٰvYٯ'ΗӾieC CgW܀..niVJnVصͤՕ!,Eb&m =d˒B\ӔhqG82VX4<Ԋ‹EWЅH ^];; tw.~䭷?~^"14/KjXxλW'ſ;tn|xbS9yt)DdUmi4vK5h6 rn"~cWw\΋Z9C'LK1\ki0MӔtܺUKVn3t)R4π`4 ,f1Z0jrl\(`daf$ʀ%nNqaP1<}vۿ_5nS@nH(hg Rj^e۟<Ӝgfl6Dt}C]JnVvBxn6~z{b]B߃,Ur3ٯݛHjK T.&UW5!C2U͌9œfv(8pw~?+__ȯ_:_Љ P "X$HGn+D8=@EO x;!pZ1k!m*P:0AD 'w~i<;;okI=rLy_Y]#o3R4A)ֶODUUKfu]T#97WdrDzJ@LPM>2:0B67uFZ FvZU͘8 ) wS_r&B&f^jZtʐͱJ@#g䰛4^7WfF*0 Ꮩ;I c@#pTw$a5itcHtM[cA1nSCJφW/RCKFLfuԥXDZRf9#z*1aųjVcǜ'B6":x)RjXJ=0BiP}&6wFU]w)EqܽƷL>P:s]*8ĘRp܁?~|j@Db S-n`kC77:JGB[8=}s]yY -Q:T""6$;caw<E&ͥ:ZF _xyϾۯ=gҔ/s-δo[e_p$RMMքo>͑DqC3@<\WE c6d3hs)MtݗDdeYr3i\{Fqj܅DHY,\J6kt].ժDdK`B6tU6v VB%[g#Go_jḱ$p Z" 8+4ۦB$(/vAScaɸ;h)scl鎦,b2 =?}RH0:: 5C J2(rW@uٜCgnH$djnͩ!7\2 [F'vJ "6iɌDSQ䭕~[W²E$erE$"(ي~ {Z p5ߺ*HOWZ[NHXrΛͦsOb-yzϦ(ZY`Szby.Z\5ȇݓz{!)JGusbFsl}N [(6@Un4:@ÒŴ^I 0pτr1 $j2T$DEU'ݼWעf^Z ]qځ7\d] ~?bN\Pƽ8}'qj׷Ʃecc\8>ݾukJ1N|8Z+nԋ֠g@-KY \J)&S _f.TSJ).DڧYJR ZZJDRRzs\Z^ "(ZJFOjyw8p];l?g 4ZMR8pH)[7nnじHvI+b.(kzov\QzU=?gKuMF^vVo!] 4+6^ZKu]#z Coପx@"9yBhfJ~_kygf'S7R2"JyM[w!∿V"4MG>5/@udS'K.1!ru'FU-K.X.BBj)h5]=}-=N ƍQIHqK8c*ճ;yRpR&"j7 X*8/ck|o; &&nZ wp KGt 1VBSG"Dv1ͮt/uzkWAQ1 JX_- -e"\;9z$@z/U}$гgOK޿dO>=yS?Ә_xqV7c$EdՇnUeb&9u\T$]E"<SL)JM9c3̲t)4@,TR{w]]a5o&_{:wj B(up b⨪D8l9_W?oC#\ IDATZP4lUXB^{]xv;9עl("Rހ7\BodWa}uVQ97DHbTb",>)3J.D+hA806̏/y)%r5GEXY:X)gw ^?뭿~w^y~ySΜ8RKQvqT̀fP뒖smVm:i/Z 1L{Z q4VDy6D,=N]E1˺~.}$ǩlDT"#vagB eGLdðqZ'DEPb\,GTI؊ @@VJ$PE8)Ra#qŬZ'ݦBL] uU*h14Mewu,2·ʽ//~b?]y~$͠+jZje*PjgaƵR>Mx_>(%E@Ŋ>Ph`6pY\ Dv@?=YG oҼZKՊ1-EB ]HĪFf^JUA"L]܀ Y\Rj^0yDb&c/}{;Ekx<3zpыKWqu*y74۷.BMa|}o/nKRx؞ 3WsqaK^HeŎ^x&!hSa)tCJw~wr}*pQnG gngQbVmhKh]'!S˰x~}տ7q w/9Z)V"8Mgmwb"Ch.0֖H՚>s~BT[Z3A98 ]1EdZc6Sflz31l6}c-֡si9p5ƀgx& .>ٿKu]7-;êj {8̡IQliD1=@$3Q#0$N#   d HDDE"iyzΰ^kUU~nYs9{ZU_}}\ْGP`yCg{_ʋ/kѵz "Z~}G"aυKNMIH}O|,ʈ`c>xfRpcUD ]u5MSW/9 JYp盾 $#*z|ڧ^U/sSOISRʕSIk<0UEUAYo֮bc8;;=} j5YU?QGohɨUvw*bfLRhi̗maaƟе۶Tvq?M}ssi(McEÏ=~}{5L"bK6!e8ʌ*#p̹B7e(Q,)'Ns EGR%ԏ~P|lf7ID{E G?QMRq`:eTReQAAP@j/"lo`u'TUEUAFWQTP/[UzO.`( },Z_ӶG~uܭ722r^Ez@剼W UPrKޙ답0@cmŅM[dKayڲ6 VٙHQ˿gX4vZju&˞lߴia٦ub2mdÔu==qyt=,d.t}T_ج\./7i $٭=msF 30 ʅ@T\!V"B.ZIWgBѦpE 9q]׀r*yt cU]XêmI% ͆:qNAk_x㓓u ?9_uv|O,Yv@PY@EGq[0X3<&Ҁ9pD{}%`X&w|2y`{l؉b!&kIL;^.@PJ&E$9[7h&`đoslƆ.xW!0sEr*:(7m-Z\[}Óѕk{xŪ*g5q2+TMUUrۥ>'N!.P*e+rHsa!*@%2 @o>u1fT5JmIUUr 0 I:D a@6<$Sߏ aPBhFG)@ G#Չ՜U > ` | 6ŹxϕӷbλZew}}ŷOvȠh:fBR)*1/}Ka{^9sDUdp-s4&0{Dv<#Bq4n@U>T*O FQF, 5e;! 8=n>kn3s4j]1,jк[~߹~E}z8<:zyѶm;D`]]?{ut5/,GWOw7M"(#C jҖ8CbDbi#>"\S.ܐҢm-թ_hI*境H#1eB{XN05gYM98;( ""7IDRr"ԡ'"B(©Riurk7o<]oRS9|**.!3*Fpt ¥'ON=Z {E3k?Uwu`JzDĊq,@4fގ]5u`/e=濫oϡĺnp6u]%G* ZDEH ;@F{Jrđ zu-6.I@xߵkgcxǻ׿{w/.ԫ<\O= "   f8nKDp2sm۹65kʰ+㶔_{Dcu@Q[i9`*H)lYr)'mhy6=Nb8,ɸ<&6*K)D(R@9hڐ3܉RJe=Ӄڅo޼$/ok.>GVi<w?ۏOn؜/ .n}>?‰{MxwLwEAfڪ+}wx<.4)Bd!% y@Dbi(ka|{ [Eb| 2Y2 D S*d :PyPdZAEX5P{4>yw%;' AѠ(p)B s0Y:d_aA sk^^h1#*8G0lVAGۘ:ir(8;;5?\ftu'Tdc`tp|O@sPCh뚺bu~kn=uNUCm*ONjՕã׷wwއi8u *PFE:hz4Vym1suv( OC<ḎRy] 05f@'&Db>+|}8*cNGDb UKq.fu=A߻z}߇>rσ+.R틃AEڣS2yܘ"rJo^.Beu K?$UzV]5:eEgU8\V3gv\1,p2R@U{Į3 Aˮa@Y$9>><\ݹq~xںK^}ɸ<]D_eR V F<mPITAIi/Ͽnm 'DM854knG^2Qpfm87#c|%p)ӫ r1k>|U1g_v RXԬ3GBP%H!/nP9wȁJXB\ϥ7ܾ3zuO}w4`QD $E A8`}wdMf>aߐ>ݘ<ֵ' ֢Y=!mlʹ8@DOw+^q7'/Td٢'9UӺ}|}>目KOY,båi8fg< f*Pj,;g.*և˃4da^'y*+f&UMGӮwaH!3wSG*VL༫:65 9@fޞ]{G!\nMW_^~/w{νվާ!4<[bЌT`Dg: v]G4O|F4J3׵g.}ߋpjŧNι㓓4˵"7wZ;odh6 ,@AR΋ޱ@RJ9ƺiUUR@@T9xeR~.I7w@gn=G{ww_e0^b2p1sT>ٮI= Joލr>voNXș[(fCUu݀Du;%2D_5uӥ5Cp(2Qw&mZJ:\-}`pTMj(> % (nDBUDRpj~m>??_Axux \i!WM'(9yH X;ʲ㶵aNRQϠМ%Ʉ1BiA[y;jMBD#Σ# U(e.Z}3rl6ZUm"0 Dz.ڶ=8_.1D?3)r"8$$J 9)`]sǦݍ[7qȂ b8>\]<~k_w#RJapD2 ɡ'%\6˶YVn޻[5m^"J9[z$"ăXB9RJ<)޶~#62"AITաi3 %q#"}}sFBԥ&;@9:X>C=xo[W"\J6:XUHT0 Hޫ8r:vݩɕXO?~3:-nW7-PXB ((-jdQc܇!bp#~J(mAUi9,?!Ps5g4fX,K41ggm#A'Of.b}Tc{'{ٺV IDAT۞ :t9K/K/Pܯ*E\OsNAй9ӻ^VonsNG&j&?ܔRfKl&ƝN:bz̈B1D\.mЩjK4Us]>9! XAǺ]RTd1*!t@(0 B] $+/=~{镛1=6U=mKgwsORڡvfTJ3 g{Mwf̢~KUc=xqq~~9#%EPEXrM,G2uoTDU1( l霉hAafpxp*K]Xa^^+"tR\b/~cK|֍pڵ{zFE3מZ":}}=z=|drS׍s&̞F}K;0aaKaUhfN)ڶn7\{oVYMx3Ch!g|ZH[DK9K]')h (uQjv§?w5^o} ; )9єwS@C2X5*n7}yd)RmZDZ\gPPRֻ͂y JN$lvD)ֻ4W;G_uϽ}߿~xyz兆6($S55FT-*F`OګMlD ݃ނ̋jNPW Ԑ7ՈLsج*#?ޒ眳et]lEĺd`sjZU툨jΪR8)\RJ}7UcU3[ c)O!! u5yE*R SGR)iS<B.gFGʅ1ew R֍7^y_|SyGvCScCeP"Q^gB!8)G!L7bfPF 輛{KQ)&ι+#ZSŐMU7ebD?kp{sv??7S ƵlH9֭;_}7.П:jRBDga(杄4~9_͛dM1t܇!NPk%xK? 0zd"I{N5}J)0RBb2Ka'GVwttdsHєYSsX)x$R\h+1D@`"!"O侳)T[2sXu!'NMSyOʠ KA Cw|~?tC=uOyypty.zgra$&\,؋8$ =1Bm0Tjuxo,y`H.1>!'"B I-0DOM՚#$#pʩ]:7 <`Ml.{?_yҢ9v AuaDTU)yzLpj NI,69@XrF1/.:hP;$VFN钡:3Q3ṋy%KB7%a-";?  ܙiwk:{ 񄛭L)[,F.V,< zQWׁPDOg;q]V93D Bnv`_>rλƅmeI7KJiXmDLb_&rDsRNrzE0S]T#*#if>ƈB!T)Nf7o[K7ӓ>'Tz1mHaA޿k<̦<a!9ӑ?Y %ߟ_GDsmIϋv矟Fl2VUku圷_4DRgJr.}C l\a֣tmzsg͜g5JT &un:RaÐ'oE.x#HcU(s>qDV%<6Ufw~|^Ïcyx{WX up#?߮mb!"o1D>PHmB&\=}.vjvm; %sL`i+!>ǜew%*UչFqQAGj./OO仾??'>1dPPG;Ine:9>\hXx:\rESBPTrcD$墪A3D DTѡҽ&HiqqxI?GdIg>,Zdb-~K=~NmϗYp{-4w,ޡPtDgjfXvQ]C?#? W~jӓXآ9X-WK tD0EXciƍ}wTt]gm/pەfA 9tfWxs.DU %p>x@$]j>Ci8o}ٗn?>~]Xl7_!(*=fa$ F]`e:BJ"h_<\ `|t97ZE*NPkQvOY&9/)y[`S <Ւ532WuEXl1322|pu~;$ tާTy H#??ܳ߼v^u~ut}(\svBv̖9}ob4 @5mӶŹ0 !e0hY!כUxCv@ %'C0DxY[5TtHC"!V:IVU5䜹]MM?0;gQwGXlDx8`Y9NYy"rn>/!P-~U5?iμR"۽{1Ԗj檱b>)0Ҟ:4'!b!`xQUYsJ0`vSUрSU2Ca*5%jPJ1Y:Lr*{T!jC8A RD٪RZdD@MA@qnhιa# _ &>x~x.3R̪}q3ěnޭ7]t*b.*Dq{\4s"āTu)XU97X˔h`* dέlo}?s=Sbum'syd~ŷPȶL?D,{_0 zõxFhG^T^wfV:uhH.R0]sPKrvw~r7nbriZE][11 }.%~hv^^\*VnCHC꺮RxݶMUJ>8BCv]\޴ RJJgRۮ46fs:v΅E|o}%ȟ|_նY Mfjߞ\鶛qjtr@@Ei.rafe|W'?ɳ +6c:9ƹ*ph0w o{Lixosnh|vsawrDD+v d*V]חR ẮaҐFDbͦz- qKҶ "ytEĪiҘNg@&+,MdHU<R !>8BRɒmb=]%מ?wȵ+ifqC$4bI|1@Ι\ED7_}k/˳>bh=:?[bYRڦrTŴyUnDs}kWO`ۺ:(BTUݐrմC+_>X.6ݐ\ =Ft d$ay!P+8".ErALPeb-Er?BB抨 2(#98**P"Pse Zmu +"U>c !d?#3G"y̹\\ӴͰ0zvv$"Kc@mK3lϛ[ejq?hL|R`u@nX яoۭ/b)R|X U0:AKQ-γjrH1ǣxS|[ݕO6/Vh]EN/n7nǞ7UJnir|t#5u@=pɕ!g̲^mŘ7PSjJJnvrlR&|f @u69JRS%;o\]~{^_eXfCb"#l,PWݶofrԛ{=-녪,W s. 3([.lq-Й?yC@s% 1Bpރ!1"Qa6U*VUPe HcC ,UrVXU*Ī:;?дà0ԯvvX6feQd9^i\%KAIwO<6QF~3h\r*\TA0pyX-W1jUĻ:BJX6O-RB=rU+/}ןfvOEgx}Ni2IGH?ALD ' :!37(we;O?1g`O:< Y95滵mE%wU9| Bg"p PU3 |y矿^vZb= PXc6USݢjNlI̒ћ"oW#"fZ:jц*#UlbBcs.)]ץ4b{Ţ 9>>;_w7r|Z.cpUQ h_#"G6ey"_?}WNOSɻ,c;>CߛVNt_o'Cfyͅhi')̼Gf@ی.%3I> ߪb @$UH#œzsL38<!Ѱ<߀@;ẃ<ų|K3:4+VtunVfCiVn7 "lSUK~#H8eaC@ABgbs&QVOm4 _ֳ]ۦVw*RXP*=z+uRrR v*ݺn]::,07*>. ܴM4xttxB)9 iݝ]Z6WQq˃n#ZBTb.!o>gx?P-|U{DxAw۹D"X,^yݏvJ`u]Um>D$mꔮ4f9p^Som`-Nʸse9Asdc%uc댛k<4۫j]ՅNBF+viA1}ӵOH2 IէO&ӆb@O~)yo0M&%P'ozs`)3؏ ãfj%"JI 9?xOPT\iO<}k߼lov]wt~Szzڅ% r4 ]3;^z]m WG7nX,}mr<yPH.TRe핋JrB8휋u?]\nwnlכ݋ׇ'7׭+<|_s@h< QyP༢Ta !撫z7 -Zﰇ3|ԳВ$@ `!a'E%]N/CpPN%&8 `BH R;o>LA|¨' 4@SL2[snD&%/顋B8;tF]>nhʵɈ\{pmDIJ4)V::vg9 Y桔nه HkEV[Wo/3ɯ?wH߼ykޮiCӴdS"m,zn˰ .$ د|_._?:9:^.皖gg'S1+9׬7BRlhA"X[[sP0 v>$Tmuq[O~#o~[D}a7R* LȤH yRJ !Bd۶//ce!)OqA8܄8U=V7V$^a*s9Ͻ/|Rfy"_Q(4h^]jn6M]1"t5"h 2+I)B)>ANӨ?*. 1o3!3D|郞9,aP)23ui R*qJ7{n_{aūo]VM;G YhTrh\u>R)iIdV.ܸ{6 4A`h ~h3aDd !ALH+RPY$v7noo]ZmƘY|c?ptz"@l=(埔fј !DX (U%;  Rfw{UvƓ,)%E&NŃȔ|;2#k"gj+o*G#k"9fˮZ*'f\R u2kן~ -TJiEP)rJQ"U߷EQz@T-Kggڭkܚrte녭,HJ5އ0.^<81\{5f1޿{"+r`YW˪jW딀S"PF̖{+œCX_MVk7^}7֏lS5T 44l/h6?i'f8F!I ;ͭB@Lw=Oo-ZIF#R„ h7qJ@9om28)j!H᛾{n߸~fF]wnFT)΅֧uEЭ"ǡ_=xpMv]wʥCt&\娺BO!z@Mbwǿ{?顭٬iX "!2F(ֲBqR̳"#hEϳѱ<Ȩ=$1ztҢw;seX+ޯf[~_zUnºm6Ń.G|í8 K2P"ɩ̦*d2EfLfl|M)IYVJiww7H9OP6I+@A-2$c֞4S̙Uy|pZ8r'2~rkZE`H͔oJm5O?wu !jR( %Nܵqf !-ˮsZkmLھgn]z/ݻ'?+׮l͜jCX;SJݽnKiMӄLY\<(J9?ȣggf}vrCz8SNfc(M'PZkBPTѡ+gXQU9<:>=}~ ??MUۻX֑A3f;"*Q@EaED$8vYC t֭7n.\pu[-cz<2S +ESKNڹlr(?&uUTULFΑ]7yJ*W IQrQ2^&'ãr"RCYIH1'4)GL* 81YmfU}tX< QUZ|sjכ|Hɗ%ijju"1*Uٝܺy۾{֯1K!ݥX)c42y }o}o߽wK3ز@MMEY׫(Iv7"1t} w!z"T8FR4 {oyӣy=8v>m=үƅӳCmUZ:RJ2 rM׆b\@MvUc2B0sWrCȼ)yeMds)s_f͸WƦ rOF Hrm=T Y%zf]׵]+IS>iJ#_PQɊٌ5 (جkYl@ᱳCXMH!F)uzr|RDø9%cC˫o:뷖K(zϻG6(Ũ;;cu&!eёzX:NN1zmm6Mz'\~Y]}o})EyJQQmŃYjVY]Hq7?jo~U]T` 2EJ,²QR狛STZ+ݚi~wɱ1s ˪Zގk\=ymxl=rH1zXX4.D@b1GC%HQT*EqH-IOP;NNNP,5IݝUuZb]W69J9{(r)1R2&-JŒX1"<L鐇,= u%m׶'Q1(%MoNmk~>wwvΎ:{)R&LGI!mdmDwN1)c!X,Ш!`~6l摊M1,B*TQ1,<"cj .2(2AS"C|B Q  ~s@tIp;9],ιf- CXK|^)PKr'R1IS*J)):߰Ɯ1FXϊb* # X[EW'޽W(Eut%BJ])t!#PI_e$$$F뼱H%Ns/BzX!4TRo$`2 9^pG.,51ic'W~Ui I(*_8q4sڲxAki de 0JHa=%}y'N{S ([hɡ~{aè#V{q$Q<I71D+1{ˣN+5Ɩep{__ Ml]ۡ~IѹR-]3'S($E?GN2UDC?gH.ĭ<ȨNY9]mZBu]_^n]*fFH1m_Xw9(br^^,&QwVQ_x{#㬴U+PqXê,B$Ry6JOO?o 1ژI(.YԈeBaPΟ]ePJ}5 DAlL{a xS(8l 4[u椵!:GMeE)`ouʗBGar";sr4xY^?zNV&6'J l˔R #њWJ-HC۶}ׅsSZv>Z rFƶk:=]^8cY66TxʱO {PyWl:Λ2:&b4F?&!9cN iP ` ŽG 6~#/ @]ƌD?H#&NZA[g-T9Td'H#H V,"~6Y(%pf,HpV(نQ < &uO9EFWؖH4TR @AY%C\n߽nV"(EH)X!YD0Q {_=:9 \x_]n4Rٖv>_@-g!Sm *<k׮W|#>rLn'F,cHy-E6Fsbb* umE=CLP`}̓{SO=u`*[$IŔ4(@ZŸT@ɷO8~^~qk~Z΂~bǞyl7s"/>1*1ʥy5._!wݫW>|-cdsܥO;4 ,DH8 P!* IP4Y$/!Bc!8XȆQK{*V_'>rppAw)HmȎh$"ڭb1+dNA$i'ZW0D*#n>Oя~|iP^zU5cBţilN4!h d6f *Uz[lZ]۶,2IжhuN>eJE<ܸT%z1|>JS|$1L\Lg񖺇'ӿ/,l!5r}0};] TmLQWe߻?_Za 1ExHl!&L [,A?0(KG9*\`wˏv/ E]feRPkm]L;Dۻ˭mSXmW{1Ń{{Z#TUsݝ;wS l,-o~DS, z䡫{{{U۶ބ=N`#V42(yS!< x>H9^z_|!m˪RZ#6&ׇQXSOM,A;S|˟Hp>Z[D%I3:֖eYY["b 9yR>>qމYT܋tk =4_,2h&,9rfl;8,R1u]'п$6lb|9uUL6FiQZ!!2>BHUrLbk !X'm/gPSn_ZIiq|Zq9YShYu'x杣Ő[k='t}*00 s(Nk"\.tMFVˏpg?YC-UU'"y:!`45 -fӇ1H])7,9K >?B#~XH&EQl6LRƑ|4gןi&iM{0ˤG`L!ӧq?_0Nfv-^iB:>=O?ӟMqBG0!AtGXHdRT]PnU>⭗^³=|akQ^M{v_wwv1(m! IDATŬb9g-_tk[[/_n"6U4iҬ^.k<>~Vۮ}H7} [z7' q!ۧ!2 И@m$f@0Zfk/_'?4M]F |\qc,yq2VH2PCDtzrH)jWrY.RJɥk}B A>)9`8# pιQFSZ|>gf(TK[gG ,b{:g?G+hBIJ(b )B A+EYiʭuWO,Kʳ? cEiB%Pb.xhH)k y붤}9t '@GVes1spZxq4%3Vy@b@uo#Џz/Rɸ$eYBGt~bbd].eSpjL-qd1'r-!*}x@^4N8QW⣣#[TFk\.S%YUF߿^lu=;;;z!S89.SPdSQҐoP?;}_M3կq'p޵~ebiuaRͦi[mD\+ۗ˲<8衇nc/jH<克{˝=z/[/LӮu'޻?{΅u, Ħ34y9,!!"#t>O? YPAqyJ)cmJIr>繼N˟s͑_) 95y'(rl9Db] 1N)&WuAnAfP㹲ȸ?Η`YsH)u]oEl6,94d+Z Sd+Įfywa%RD(šI(ɤR"DɌoq9P ;H)=މoafm B!>ew[.gk`>XPi?ucv-"@Qk5ՏumEDc';_®mB M˂Bfz`g~zz*4Gg4Y0buvTDn]ri ޼}o¥o}Y΋23bC0*bʈ) !2E]Ϫa< 3c6"b2ŔbƦ]J]|@@:0ɑ9K, ҆ZV9yzr%?-s) ]`"' Ğ)'D_ZpdK県;L~#ӒCy?i4z ”;Jb5AJ{9q?[2o{|VD I#20)-#3 !*J ČBRaR@Z -e?y͏׿w=:BT+SȘBYzo ;P̫zk5}T7=靓O{W.6?g?}_|[@iil y?CP*@M @!8Fl|V(EoqW>vp xKQF-Bbޗ%b6(拙!qFǔB`J\< ' kN0FqقA ʶiZAw%''')5̂m1gi4ԴmQ1m}eYI7]RTu}Jkb;>9!5!|t|X.ʪF(yC0ֆ,*!Fl;gӋ1iI(ctLX8&vo(̦YB/fZs @00!&,Rv1xRT {Nŕ|NӞ5]W%c RRd@RJnDQQRDNxI@EUez1jҤEBJB%5BmϪyrkY|GNm ݘ&FLxs?kK͙jg'쎛f .T]גK #GE"6󸁚)w'?DBeצT_2&gELe&b<@ $ߒ0Y l?$@i>\I۶UUI!:1[;&Er?C&׉$lGTD [O<1@"Fr@pט_^v++Mk;഻Xܻswgg=BPm۝KN )OO5B]oYY^Σ?T3ӃGj;sWW8&SHC*4QFFY}>oqDmQJfV'G&7(2b\F%J*) ?ʉ|i0!.1sHDI "F9CY,'IsWxq {&l"jm2eUj9gc7FDM^Ö /lEa3<%kI8(/+ՄOED2̒Iq = qY)zxYRO! S:7Ŕ̬J"Q+>'F]T,+pJB M1{b`Hux+߿vI*Re1Kwu] Ԡ1J딒}J):$MI`cI]pZY>;&TwSYM%k2dF8#,S51ss@ى?0pw#MsLv,0-aaESMKXG?& 0NdW.Fdw|.!R=V9;CkNR7ҔU{wW•Sd eÞa2 I1e W?O}y-44gmn=?[mVu*1 k=;;+ґ]pafxw2WBi#ܻzk@H3(B`(VcGB4CUZiO?2Q.l9g`-wȘϝl`L#Z; f '{-9gM ĝ@cq\_#I_jĄlcK]D,] 90:cbrGu]I+ !Zk&A4Z.HYrj!v<ן{9q# !qxNrGf)*1R"^ӧ>owлlDE@4) Č)2$ՃGW]n;b^o;lٻ}a6Ĩ0r1!À1$)F"><>~{ŐuhηH-H=7NfS'KE2|&SENIfj2ʘs+'`Th7Hj&f,_O/'8 _=ρ'8L}lj )u)`Qsyfݶ]JA17Mϯ7~J4>NRJIJ"&@%Ν)زA \b_ox{9]l.^tvvv>EuxxZz<EU֐Ws\K~i뛷n __ f"" Ap)s> 8@iJ NNNϟNsUNh))LJwdh.-LŹlM#AIJT}Jٞ11"F}=KG$ҜN mac1Bn0 *4$@*]$I0N/#GnJS(EQ [驪=, 5` <9Ѷ(7N~ɯprVbzƑHiM 8@)b!-~;oڶݬ R"%"q7RI)9R xSr)dwq21 OFI~:,@ !N:eJF?;jB 1f6휺Zds/fz/[\1xeuseɩSXd $R9O%sr'?Jl6+wHǘPP8}R(n͛7nxǓoy24&Lo*cTM7cﺹZ[bwGٴj/"' O7VjV ]l>۞#1(CPlBti7z#+_nh|ԕBMV)[ N\.7?32k]H7M˯<ܟHZʄ4c> #:*Oc_+s>4<1ֿD)ΥPr5D&g"$4y.@`ۀ9sR~" w F"N)22Iy}ʛb(+riJcD2.h?@nytZmVgr9j92(Kmy׿Nu]6nB0 !'} "Gf@T91X[tyƭ{7.!wAk:H"b iBԆ"p}uAϿҍwA(\{o@E&;V*D !1TD8iM(ʢ(-eǢC-"Y2w!fUZe gL NI~HTCbP x҅{dխ7nuBU9Vk+)F zD1D aݴz|օJk6MFvT\FeBK6<ȩL9SںΪz뱷͛Bæ(7m6ZflwڶrY fmDdЫ[]en;& B"EQU&xH~tĤ)}>b9Ő5EYDI<&淙R>_jc|jlZ,ߗ2IsUc>J*rȅ4a1)JiY"\霓q:|YHE%qZćwY'A8I# ~z0+̝i) RD!Ǝ u0 J @y e*"Ұwxg׬B;*&m4@#xʆ]tH@)l>z_յ+d0۶3Fs( O8B]ت0Ҥ"m/%\.ׇR~5O)xèl9GJ*p#8+=wH̐w)5ֺiziF5|QraU僔mH4zhS@u7@ P8qT±#'4i~d I8~p/H( P&| R_VJm6@ &뜚ysb oܹsO:ښ; 2(l+-|ӝ3k7Ϳ}?~?{j~mXyGZ./彻W.?^N(pfs |"pR@k2R#hVսwٯ?h-UFAj\>SVg^~饗_޴E}> \eiS*°B2<cx0nu4NO2*&d4e5(2c.ds{ 9I)Ř!q0wZYTcu" 2p ߸ ɳjVꞜԪ*s3G݁iRj'!x:hFex#=ic lڶKM$iDCt1b>x^޻{o.ۨwϪC "8!h"¨Je/ll@4+\;tu6ƻ(3HJ!Y= Wv7M}?]z*!K_z (fP.ac0 m@Z6M*cYm6lBUz>[9 )ёMS}#SO=zN/<ϾO!%0F]5Wuno|0.CwoN OOO]VyG#$"Ȁ+:>}~w~~OΠpEL!ᒏ! b]i;ww͛7uӴ^` )]p5)7^{o|% EV㹦Ϳ9  "1/tn?k~ܥ.֛ގ8v.Y%K 8>~:vo\{o~_&я. zJ9D/7*MN-Kvޜs犐i|>SzXMUfTF6^}냪H&Vno6'zU̓ uL5l.p P}:(TPL?<۴d"Ҷf3"rP +iʄy5-@>(*ؚ0 rQS.yֵgSO #ym, ΡW9S$]!RhF< =rƗ񻯼p .#eQ'0*(-#BC$Y_lŞyI8O$#"˞+\M8ږhYJ,.fa?pNeZZs܄bN261Avl@7)Չք2TVt(pi[sUU=\lQ;um1t^f,OTeW欥A2jz< myz;=瞿Pja1p3W;UÀa`F UTD10o,זi7.n9K(A{z:99_~;w޽{Ї|ݣÏ?D[?6w^G'@_:֔V|#g ]{Sg?U1@XB-cWy\(RFvF2 5T6^ &YM9Dz<9gJR(ᬙ\Y2F툠P}f/ij8nl6󅈋ŢuZ1""՛c%p)PI 0捇 *=gY>㬝#PN 8 4 IWU"vLg? =7#~Sv)3sHZ`r!'7HrnHM9H@c`" YdQ6i8.&9tMڦ?|7ٷp?ܳpُqgϟ;''=K/3|399-] C˃7½C{Zr[_}|wyNkWpkPG?ѓ !I9[^Ẍ́S-|3 I<F#)J TZJIvmibU}隣2&&u:4>t-"//ךg*"gf/"qP}yq [:4 svna{ttM 4Q ."trT[ZVs.'+5Ϥ@l֍c1 *=@ ("Ko{g0!3sL)ҍ9CD\@O9qs7qr= 8mKsdB&Odnۖݻw~z]Ԯ6B1% VkXBrۺn3E:[)Y,(tucm {ԪK 30 .5**(:F\HOi,Q6T*LS2Td'diq}LD$Ǩ!ggW^}# M0t=gNuKVjb}0T޿9pS6ڦifxւJ5ED8x]FK h>"RpCha/~c?0SAyAwA$"au@S7DBǞ9u?X_mf b.@!")ezۧxZnpyDBw-0>3pttdPmRjtP.ʹj<7;v`TḱUS5*4,Kg8ˣ&W4AKs9ۄTtAV1sKRӫ!OU *-a0dY](tS,D(HaŽtBaܹ}ߟmۣcDp9s\PO2p:?xh~ڦ{Eܸ~xpk\\.SL)b~}>r:BDݾ} ׯ_w=<"Cc9k=@*ѲZB22.6Pv٤LH2da|'\2G͗5nO]օ6&F Mw)M: z^(В'8 .no?wjyt Kqv Y3_ޠf>^\i.:zy9'!!][EF=@oÃ;w^Z̖^;<{//|7~k_*vhG@AFaP+ۡS>nnwcJcNQrfIcJY ȑ()1KvY7ȡ4woB SФ@;ׄ#j01x ۶qȻ@D0'JN,9eRR q>9gڶiʻ0qq#̜۶麖9ǔpG<C: 1ˋЄb]&B>n6=3;w}Omw;@)tm$m;6Tm_h&LPXt@)d%@ wM#rkRqH)e jFۦYg08iaC-Rxz֯~4<SEpA!o37Т!K_8"wy1cBԏ= ;ƻQ91t.(9g>8 R΂Hy2~e6ʽXnm[mKZo[?f-,9kzAŴS&V|SVDҟ(@ZRUw#a\H,`6_%Ī'&D @Prڡެ??1"e׺@Pv}ːXۆ)+mL8X^ _K1LNꋇ*"jN9rVf/|Mx%4ԇTgm3M%+k #+^3;"t]v]Z-ChaǨ55 Yʜ>DtQ{BmPE)~Y˵ڷ ҳX4~ 3z3v7}Փ!Kpq;BCM$+#Nydo_Ǐ@DaLy Qmb)ZB'-Z7U+A;ZhU'ʉT ڍ՝MkZxi-yuhfٮ 9bYPMVysÒBJ7aN*-6\[P[H-YdC@bҟLK"vֶ_8q# M㉼wD 9)  t"qxGGwnݼZ^|E_`cIIYw?E%6FRJl).zrr""}?4)"r\:7A4vΉ8z-k̎|>.ryڵ=t-(`u/E:m)V )29[XjՇ&vVvQ Pq٭[+[_ַt] EG:?37~7>O L4+UhHKWrezH>LE$EmPR~(b4)eXD0XRQ5!uΘ.94Mok[$0qP>#>erƉHI[pShjQ 8em< ølv]u])W<`tglyc " לXe_|Yx'U'"( OW "<¤H Q!ϪպFj!&ݮU7™Ih}i 2TaکW&\`-T=}EUT'30y˯ $`U,j݌=)};vo&7vf_@nMMsξiL.5qH\)@y~_nudY_QBbiP.$ ZM9b>lwy{BϞۏ^Z,ݶZ+1‚9 ~)w7:xW I.ExĶmcm6-('''^ӵ͋/މ1ThȐyB }pD2dB}nD8I28DOL||ٌ[nWt<<}m#ν IDAT{A"ҏ|?<>rar@O0L æy4f[GuXJr cʘybkG$E ITP3Kڂcuj/aӉChSL#ڔRЫR2gfAY WS&h0u)qBJ0B~"T̸J6ΜL8El`*.lV_OcNpmqos ޡCt;Z$3/ Wy29bj"gg CAB@)2Q1Զ\i KG"@#[S.ljK)}{`3&]us!W.jlɕ)O(U:s3KUڵ{)%D Y2 JDYxK3T̆I+iskŔu;UׯqԊ*'L:e9Qw7nfX׆&,7S̙Q.EP ꤰMdf$7Ƴry0/vC׏ /pb>{<D@%XA>.r˔Shl6~znFgݜ޽{?+t۶mCX$'~gz{n\df5*\v/Pe)L3)ПG~eG1uЀEJ˱b !H0MWK)&5֊9St?$ޤ9gCDNDt}۶mDuh3I_?.ڶBk1 5wex,{-CDML:ݬ)14]T!ѱL(1θjJ~4nNrfF!1=8Oǁ(9O)9Tw.8?}Ɂz4VɰZήV8_·8d@y'CH(Y 4i t A${1J e3"V+Ꞙ2w] C푒}ߦD/R{Ecl/%49 bV'f#Uid,T ˪af.,'y1FN1ضmn9ZVڀ_׼/>8=};}g}py L30$9H?|SG7{s;|zҽW壯yιb! E}^˪8D W[]ۡ#/`._^^zyaՒ0~g?xOO'$Dp0 +_!D~6= ??YuzM v]u (:o/ R^ޛ*LRD4C Ҷ!爈[*'trr_ _|['NNܡ[Λ_/||>BncW%5~_Utغl>s~`a.*Te KNMJ[4IybED7 f4v҆2VLWl݆۝uڞvgEiG|rr吜sd,c{#ODmVahW]Fc# ̠J O)Ar:w_rNwq>Ged|K&#gu>9sffAsLԓ5ܴ>a r*3xU13T\ Kl؆~l/Q̵ I]0 R[riM H^rXN4Wi@M\`aq@c(}Rh;8鷦@U4ۮSΗ2sn5~^7xV˃bX-la`@tll>xpw?kusڜٴ4)ee)&>_AtuEXȑm̌H$ D{lėMh?Ϝ_˿e]v/o# ~jPRb) "@]:sQ 44D z5]RfX=0-"Z2\ X,r)o5̨958JAU2%ZƦi%8J nl_üNޞXEDREMhTBvf.q]a 9=DӦ֯؏9cis^aP8>ki7h`hsiH)"Eܸvt`/xeΎL%A As$B@f̊_6=+Pf!rN:]{~~eWXo=bZ\y jˍUlTBKߒ*~:WgxRÄ aK~%Oѻ !URJow5S?4⩀ xg}vXݻG~p2#a@N V?n]lC7c0#!@sr΅)QCcnWL\?XUٙUpDɇy"ĨE)*tt8HM4_>$9k BNWpJZx"$DҪ~Ԧlr*5seFNhW*\M5u~Dh~nS,aEV90O,4l 8i6aO'}g.Jw}LPAԁKrr1&'vRb 2\ʂG0)J>Ĕu oVplſV- ٦.XZGDgP=n~GuLT>c]5TiNK"[Ꚉt y0Ǚ]1ЋT(kOW?6z~e:e;VE#wmuދ]J-98sǯ{Wգ>: l>rD gbC?CyKw uyۭ aX u EP9!qr*l*p;Dזn^9{CEO8?K3]~x<8}׻ /pc@ضm?mۢy\zKN@D| •it@ !X9Jzkj P8}rΚ=|\ ?8 0a­7YEH5mRD$®딉Ag#6Qpyl!˸&qvԐ+cL]$smh2ڊ,wee)*u15$_je9Sڶ]ioDFn>Hӄ AP2,8> ҾuBhv\\ZU*^zԳ?TQ{+X{nA}{x5Tj;Quwv|b`U u^qW_oUf3ǔ =849>:y͛7CYwH34!W(M =i{j02AیOOOo ~ܹBHibl6_ D]I`WDKci2Tմ0CBCմCaGSq5J "PAPŊ)K4lmZȾ*#2ڶafmQH%b5.l6w]^^U df=.ˮkgȂڶ|>W?;-wͷ ZjU))T9Gt&Qkդ0䜻nio&gO`ۄ7>8 fv¸{ݗ?tыM7_ c MkeZ&r'zh#Y*\ @HcJ xL ЖD$J&tλ5$A1#@۶3wN}U"nl>ya1_l6کWTe4Gӳ1'j D, _l^A)ZHyտVXN>x,ğ9W*L~(hYkfX H4p;jf+ıi$A\̓zq\j^z'ft,`}|z߿zk_vn׮]?Qك>8, 8l7vJI!GRa1jv+l6}J[=pqڵ{]vxӳD٧~v:}'>ͥx_ovյn)!>HS|^f[= ) q͞sN&ι'{O49Jh 1wH00KQm+i]8&jz띣b1 ڜ:Zez&?LoIp*CJ)`q2OY߶*`Pk^41Fu q65M"//t`GIaΜwby?7|9X,.7[qH'GYrNYǸq8lpNi1^aziȡeAA qL9,cu?)1@i(j DwWu?Ɣ'1˜Sw9p Y֑TSǑJ™Ss&xq-5!KKh WM4à0M蕝sm8`$P@Leu<8X9G)Ŝ9D(4@|Ɯu '*Al>ĺNF3Yc8ť8U~y:lwi߸Э<̻Yʙ~ i};>3sjC9x ܆еdVOB@B #iH㺧 2d:la#*HYY`<>"v>億9%`^tm@aĔ#LJn .\.?O~ӟlۖx']g|ݑ$vH\BN9,ǔ{,sLq8e)o|P: +Ę?`€btlwNPфwn>@]4G6 umDD0 0C?@͔u1_5m;#3*Vg8Ɣris%0Mjqwl6faޮ| wmEk0)LK еl6 y&]K9 à6mʉ M O9Tow1nÐ%n[qg昒fhVmSM9OTlMRE@#$\:DHN2w΅i3Ǎ iys׾-3r L4r-vJ9+( BR KJpdY2:! \i.fʫBW}Bŏ`fV/ AyRˢWrvL9_\ |H]@j\M¥e,aTfPTaߢI%PǙ[gޗm;z-U"ˮ_a)SbO 9|֭Go=ozsJYrNOϯ-3 o/&a/..,6uԙfKVZg&"Eg˻wKK&izTaT~2s$@ hk=Q4xmbRݷ {h1˅غ-hpT`]Z#"ݚKJhZ"M5gˀAnJmG9WhVYSpMjL vH ]nNl9"jاbhBDAӱ]ז@4 uǔS҇ zM-B>g,4+MDlSiPB:*;ޜ-fvΫCI՜X.I9uLt%,¬PRG3xM_5mK$93Lس&M8wxx]3 IDATkrO ! GVPUo\. j׺gUg^qy­[nv-aѮ8\BIDZ7MΫR)$kF -AT"X:Tgx*o5Jsc*RR R6 R@}e?;;vfyG_3KB hUB1(‚333Kd8{R眥zT:"5|:cYrJI%)sHsq9UeHYij+Hާ2c.*|.(հX>XKvfkښKcqw++Ֆc;ope,3#c:9wvv&"8.Wrb('SO=HAvn"gX.S>\ߙdBն-4Cʃ9cSl\S輣Ss.qPm`}#)p0ͬurG}tBm3#"q.,d=WKoBWhRʶ/gk]1eo*wNwmXNxQpy1)!x %  8BDV+jvo眽9a9< U*^k10W})spM4*Vcu`!@r)u/؍J3Kֹή6X B^ V̅YGDNPE,iY~ JP$&U-q>Ux}յ-1e v̄hTzf7hJ޹r+ n :uYVز,"ŜA@f0zE#Exw~ozv<9-ܿ,K s0s.k \@RS4eNr;.c*# , {3k*&fMzR#j\9&xJ@3 ]+v&1qS۫`҄Azn:nς&°L`[%.{ƬBoځ`݌m]3}>nUs nՖ5xIT8Ryy8g>] o7 eVػN*V2 `IRL1q<.h$Do{ \W6j"C!RJ9ȅi'沺OgŇCDr|)]S9a7Oeyj/у}1+l:i.'w0 RҘ:{}~;6EZUP]6l5s.*Rr듩gvVŜfS}D -+ z6A=q}@e@^xd " '$>:.G@DKU6 XL,pt:eriy1N[Y\sIГll&o~zRDdDyiW+I407uSafpӑbQk'ڕg}sm! 3xr!x87~7DoͿ񧯟O!D5DQ/K:ƼwD !ij]*0 5~S;ep+UI_cq odޕMbtG+JMelW9"P 9| FG 7-( L+ZH UERGM*zOaI˂xɽէitp+'S~J];qD6C)ELyV=n1=BZ]բ"`9"˲U6@\*ɹ~OgIs1 R9…pA`.3Û7M@!dBsgaR-+u_jW2ƊHPr58eav,9ܨɪ&{33#}¤uD4og\G#jMDj*([2zdmòJ"`R?Qo=Db04ϻsg7/_ٟ @`*O-9+# H!zKFg@gRrqK7\L ̔Rhze )"A ֖T"r.輘 eVޡ; pmBiY={KD$"1l,)e XAr.aE?HB-ݕqz?As !2^!@LUqL)jG:!$d57 Jm1 CM-Cd=9g"ΖB=ժ\Jxi @#.GÖ9ݦP5lϘY" D|! N)gf)ަ=Z?`5/ue|зf)Gh,CӾMmC{jfWZzǹb(B{o~r:G/{?s@/K¹RJY! "0SR\xᇿ3q4^Gn\hrf$ᧆAЭˬ?;R;}u|) Q/Z6ŊTR{:AO~b(< F9aAK, UPKUjnU+iTO'Xme36{8m)j K̓64w{1Kow4󨨺aWW'OK)}BɐsnwOX *ιŔSJVoF0(v ~:X} UoT?ZLQ@>ȷ蜏K):cnSz%"n6CJٻR8pf\ܥ,KInooU0O0SD,Ck;r:j(ƨd޼mw]-i*Eב̲,딴y.Zd[,xl68"0l:7WvtWNJuU(:Oͦ 3@hFG/_>ygLOؗ޾ymA)(7gs.Y 8-x9z1{DQAj*uFk![yK+G[u4~fZgԄVCdikD񇚙1XU&qNd2Rgef`CXX &+{%"։0Mq*ỊےJxUmk!bF Zл0,"["/|}F_4r{~gAsy@l6:sP). Ψd@/YsB̥a!YъԣgUIq\s}I)Nh"R )܅9*zb!f J6L 05OmJ+bކY aTo#M@ ߾ !0fSJI+zS]M.,Buyv8www07K)/^`4hRB$eOPX {ʹ7qIE9vddeRrZqa8/"z̔dP4/^J19x{ _7:[3_,A VZv( P@\Qb^ 7znfi^\'} P]Ќ !6HRf.,{Waj*|]"vC-OuLJ)5zjZɄ *nGx;|&aKg+)r3]mFASc&jWY#^t waЅ ^Xr"s qwؿ}V zI)Q6WF4/1DI p(cF4I)/ pAe>8 :ru*9}{A'vhk{)9NG"&o)M1֬/"3OA\VyH \5°(9G5 ,v)UDSsD8ϋ:H47s4.r8 a ]q΅CޞɃ&"m5N+WZERۭvw]TSN)<.q=ϟ_t,, ̢ K)%\8'Ptn+iN4?|<.[Cח#غ4m~<"an}YtպJ o@g3OnUg ȴgillz ӧ `Mu05i+ }.Cwx}z|l*~k:7uܺP*!Gkqﻡ*}K?t7nBp!>pF,Dc»57TϜN'Mu-me=v̈́K*r=<V-K 82HQ>K.7p "zO-bLNӽsN{Skg3Thο*hLܩ[G9̘mm fCkc%^M[ܷ7ODbo޼̗01xR2$*VZ^}@zx]il%a)*s|fY9ET3+hLu)!:^NR?MJj%ia4BoYnS*T#!Ȳ,sym x;,b)FHk?s" J$QZ0U*Dk#_;LJ0]JE,1u{M&ԮZ\izZFy2/ǣ3WNj@mo^!>Yl*ҥ[`(5/f4tʢ=R&ZЭ)zKә4D)*v~X:y:הh^~3^1x<փTpPV;t7o9/Dw}w]4̀,`舔Go1s!]!62GAQއyiy~W߼x|>%P6%`GFrA4e¦c s1UGw"(鯢@(`Q9u"9J֤ 8&G^I;ڜstGq\d4>ƨ9twLBXSZjACp(u]wwwzv>}TD#y^Z{/ˢ _VFRX-w)t[Qk=fZkAKN|ATHyBGK. PrI~fӟo_}+eH) b/Ǒ+23sĕ2;RArxZ בTخgkۗm'X"̲ޭ\RJh`-uI5eZOIhdJr<@ A!8f>]*/3,iDOoǻal۾\wPg"cRr4ŀCv},O,,O~{O9w\aH(S>|aYMܼs%Yemƴ@i3++y-% TΈk2OJt:Kr.;DPpxn{?tFD-#,ʾ,]֥IuJ 9RMSc=-f(fk6|*@jHjb krz| rr\VJkxZ3`lSy j; #<ww/^xw$rUj7ԥCt}}M($ >,x` IDAT>m9@xPq .)R5z|0GlG}B?|w~oA_7?]4MH8r^y_Q@OOmgA|[s].vs9sP,\luֳt;IӧoM:DXj}el^L;-*~~W~]UŘ9ۍfN&lx[mzf5s2MiRKdDu|>[@g.zNbrdǣas5V!"^:_R0" ?Ω~aSuD8GSJ<{ɉϟi .T4MX弸aZ"XS|PjPCiZ+b H)S\hؓIzj3}T g&""T^ 'q'fDcM#NA^R,u K@;PޛVMҘN&aY*z2SF jsk[jimZ+QØ&b@۱abV4NmA!э ])jW?=vxX"v&SSvx`#;\J=YvpRp{)g1.{*HY aD9 @煑(MR5A!,0mn[~JH\/LOR;K>n7`.aخsE@ B@V3ncJ·n`@bצ:p\TURRCjx>K਼>cc5zt}jvq1^hq1C<~:#:"w<^?ޟUAw}Wrը0pOitiğ74"~C EkC R X2 @a^R,ɫׯ>go_ozcAc՛%-pKuۃ`UR n56Uh@̌f*̺p3EŜ'=P)Uۯ\).|vy ˤUes,j*{4<^ .E'c#H-ճps~oD]$k8A{qy^4k2oJS\.O: GRu0(93@!ΣEq "̥b\s !s):${OHZnH%n%s1䔗J)yUH0EWMΪy/^@SʥݰIt:=}G䏾ާSaI})B҅~?_RL~ӫW~t q2/A:Mj*)33ϹPq m-AI)4:y(T_fMX|gJCT&0^H(YhPZE.T6@1WnKEa ,yBҮ0r=9kv0F-c~ZO }TuŴ U/6j,PR6jƨW̢Y \J"״"cH@!23CašJ8\'A!#pT3P% z塽K=4פd~0xk=R. T|{e;cRKիY0|'M CA3\S\rm|p{ph(gA_ LJ"Ko} ktDrڕ%b*WN'l" '05#J'Ƙ3?H]k=c"H.c싛~1qwu=lv舁Jf.sBE/$B0vA@d\v |y7WW)-]\WRᔒ,P)EӌXSC,GUΓ;NG}*& ډ5ZYIUk4| }ehZb嚆ZET-d1&RZ/U7Yd}vyV =:*}P:;"20l/clJ[!e8NCO@Ku`fDC}EgW1͖j^E0뤰bkhМ'.+r^:OfڥACD^,Kׅ@R8"r,-%m}ÇˉEs.Q˜>΅ Rb/h ӼAzZ!VO˪Oe&{X =M*&Kyc3Q]94*NJgfAN|CM^tsN!7USf&53s6T[կQ,C:ZՉacpt:Ѱ?RJ}+OWp8jiMTJ 0r9q]XTW߾0l@_3'}?!/ _?/\]ׁ4T;zP SK@"(,Kba.)8RW>]m/_|G|RbP9#&=:Qf)*FHKN)iДpnf,WF:nS* Hͳ~pUULMԀy:OI.9YZivB˒q(4^zUGDfV9$xԃjeQZ$z>yCW.y|_hfu|֚J>W"CntJN`uZ0 ~6M>=ǣ{ݝ6kٲђ:§j= 36ݳ:= ؃kagIaMҀ稙k/]?s%K&?J`&M R1[5**.7]T44V`v 4nrK-Vξwnyh"fP8 "= Wy1a4͛kږNv7WwW[sU Tn>O8Jr0(44ߟ7^]ď黻;4Ol6$B@~H%T@DJ"^ms^ݝq%(4Z^ jJVwD}sycQcUX6vS*/$ԨYE4XD6aY2Flaι kGZ -WD@ؔkAЀ;Tv ڰdQ`>@]KpPKd񖶼Bkݮl/3z*9eE-7D@i2]\%w]'zըB+N\N`U ڱ@PJK H$B q. l\`:;#A&aò k{bMׇ:GϏ+VjQaam& [vNvYl)?*iMllԢ+vTmlk"H\TD)ogd~+dc3j<[Gn1Ʈ뺜yߍ㢴q!꺜Y}ӻ7_m~b@H g'@GBm dvzy:4p>bߩ/V@LF#HJ]iBc ;F:ޕyZ27&GҚ PR\qιglsEYi>M:=*չf+>]# vs3j\71 3OӢ7`RTKIR~Q;sm4#IDX::F*HAz 04P|p"caѮ6(D%fabrIavD>y{{YIB=3֒Ԥ :7JSm=64Ӏ͙2=/RVҰ2ww\C毙C)au:fu|it\~p>hMdk^nj#-Ȼg\+N Uӎ=-KjAΦ+Q*sD.0qJ)v[$)raȏjnGk> m@i甇a!;_uqL4Mooo)2y*ku4PR4}ion ks1E %(RdT9_DRZ(Ruq/"*VUH9iV R4]eas\Fz]p[Y6ZBmR"K5Tޞh}:rouԛhwwˢ,u:5  k3?4 T:^]# `ΎjKMN =Ac >1ff.koJ@ovf.~k8={| 0!+AJaf>O0N3C[l]ேV .1;wbk'9,f?$A%k69Uf-JG4Vl VTF1Pi,>fDXV`aӤJ)|UvX«M]M+,ijΊk,J5ZW`s_xfa3&CuWQrN^o޻|'DK#"O堂 )2 `^*ݫ.xoĔ/5}3*4@%USz0`GN6[ 7 M,ƍŦӑ̱')j3H p:L$ˤJDVż֗:Dh\iԚ4}#C{ IDAT}>*WB dny^diB0l6xG>wqbnf!& 'z \RJڋKHS4ž;Oo}tFGȯ۫VqR!btSq0 B\ #oHiK CMjnxg朅58`4+Yw'D[RUVW04V#bJ߰~=^;Pa:1ߐb"3y#;̬=';˥%6cf׮0=SYJ9:TgW-d䩶ա[ > "$ΈBx>߼y #%fd@[1OڄeJu%ik.K\T[l?AFi-Sic4U]ObkeBF 5`"q&C6xiڌM\T"mGaP؉jU'XT e^@]莝Sm&H}3su9lTTasJSz C@ZFGKd KJ)K)E}N~;+KK"Bd>RSZ4#".Ʋ@9TcbъX?J_(>yPJӐѺϥvuHPESEQιPY(")eD[Zf2SJ_ ̬p53]5i{*i`hF3`Ƕo}VI+z0M@ZIDuMI;NP#"- ۍ1Z9fd=泺ZmnnC!&6A0"TV7Dr/y1/K˒3d撥R@N 0E:څ+M74$Z k{7X+j:v+̣뢎(/4MۭgwqI)c)8_xOOu]ɓ}P) -lqrB 枝t/|_˿n8My +T9Q1iJ)q.fȵWC%hT զ$ERbN[?e}!`uQs*]Z6 4',4SKǼmPSYخie)vZU֊VD4ͨ!Tp9g9q^1l6ɺp~V W9M{Ԩnղ4MP\xYAo^szN<*szs̟׈C\) H)轋EDOfXΧ{!pr|{ѯ~9s織9 uڻy.P-:P@j YBnL,❋!VHZa?wNW)0 !p)\89bRJR z>v~8s,);ĥjhi J̉+ vJ)gz3}niN {uΧMcMIY"ٕVkhrQD9g4bs,էP{NuN{Bh*j:uyι)0t4~[x~~ϟ<v8<}pCH@PAPx% ".<%f(KCw; ~+Wxg>{ ށyY1N'Y'afő1X)WH#BiQD4ཛ;d'\1i=>UBfaNG2 9guFE %YP3 pa)H \d'tӘ`3;RJ9e:!s9RZO`02f7Zjdh&άsܵubָX*~6[\>0RV)ѯ_@s?mTG믎=LvW4$,3Qw },WWO_W>leYev"R4@Be9s.EQ使O~syWJ4+t5PQiWD~l)ȥC7k}{^/d4=F=%/""_%:2Fu̚!ZAtb],sӛ!&(h*9ɲjp~)ӶbUwG8\.Nemʷіt}J3IIg7Sjj sؼ,MK]5*5 |î-'S@*ZD.XBB<`[Ykd")e`9kdU0aWhϤ!a \[WbMJkH!P2il@Y6v=BڝӛT!Tr _ʮunΡn8Rkmq{_[>l19^*1TIL),  8@byH9J#?:B$B"?C( B q 4U墨vٌ͹>,]]j59GY/Tjz2U}-&2G8aʚu2ssZ-+"Hd3"@"]{ ٿ??w/H݃}YFe.d"uggۦWMo}kSվryVT1v>{(R!\5+9@p,"Bz:u4{õ WU{aVziM)E˼$W$ K?.f1ĔuHQȜ˒cJta^"#2CIM!%j[ȬA jAtBv0(gm-W%I5\h=Jˠeۙakٖ}iRrE>I!뽒Sĝe <{! VB )2ZFtJb)@iżea*;AX˟ _u1 OaԽ..tW첂OPn$ziwNwgm,J^UpkR$2s6}^j}1k3}߯JU PZRBD4b*ٕv'*p‱Q c% YCckq8D?w?#?YC7f#)Err_`. $)qCf ㍋gϞ?xK*UD{ D"ixRkwb٥fkf(6W!]o$5f왥ShZmE\8HLĘ ,Ǔ(N[(L-OgPИf6kŃ51 9ޯ6:j-,7#[RLQn*LpLc|K(V%́HǨ, Z1 "ĈdY,#\Dd"$ۦ"G5a73{ж,IP/_@m>//dB>b;]3Y?~[QN*f)c)1Fe7}5ک%>}p4j+3+^A2]Ly#EGE{L?ϟy4]LI*TQB)K>@v9\U_ynwDD0EPD L,kn/Ϸg>7wmc#k:ey(|)`1OHt"WtQwy5CֲQ(:O2D20s!3@DDe{?c'C,XQ"D@J5.rZ-{+I$SQD$N݊Lh%fnJT%Q!.ʞ/nX&kRz=CJji<5o_.FΠkAhQvmEUkz+EFaY|cwSJ%gf 03=Q!` da@ChpQđC];5L&}=VLqR,CYb͖ &ÜKe4߿Dd !#qmbu*OU)֧]mDMO*Zn>St+PpUY]gS_By28WӴsvUU/Z9P֦"Z%.NqI"%b"h1 4ۏ}7ų=vf7kTPe pv|Pل .HHD@a^#;|ٟ򗿲jWiky64 ck1%) ޫf 2|A9,{Q!}2eY̔UKZZ7^Xu(E.6ڕ&p)-#",JD&g Gc5*…~ gAx]NOQ$)̾nGj40s\)^nb# jȘR*,7S,Ș0tthOq YtܳvCy6d Xc7$ kHdȂw~_z0Uv_ͦ;*;7x9Q?}ˁc)ƔXD!هzŋ_zw8|?|u}= ӣGC*ZR"t)LQO+/ )jSJ:Ϻ@QYe$,v]8q5]DR3ĜRBxE^+AzV͹Kuz;!;"B~/ǗP}Vps;RḈ._ tf@)jϻ>Ab{ȱ8(75}P! jT9U:b++VKxˣNg$ ٖ. E(`Ȅ._HN"cHg6+V@& 8ӴmkYZ[l U+ƷNsʗTu[U[g^eeE4*6K>|1@ȥ\j#W(544gWED󋋋-\B긦,ݥ5g1q0_;?HyHoA%W5Wk u]ZshcV+W?Ҍe#ݹ@{]M0`Y5}BrqHc<}"Ϟ=mfZz{vvqޯ{>gE`G0 0@q?Yciڶ_V+:(6hiۮm[_)%kSI#"b 2e:^᱔9>'l6of1^^]iM;gi]5 aX5̼ǐ ARL3$D%@Ԋ2aXl(d XmRJңqSThLA2elWQEPoNSᘇPe^hUכe{g)XZ *vefGm [?2,1iCH@]g1&u (z!)F\-uJe*ZB*Jb` 9^:|ɓ?7.m2/lEJsNq6\=~/*ZHkB@ nuJ&VjFߙ,z !8@A-M)*VdU ij^"}=He`aCFi4Ƭ kB~Yb}*sL: JR"!x?OMjw]l/>6zqmD92_ .ֿ`1]_]knެ7ï=}4mY;MGKVdrS7J HlKb4J r٨P )7W7^[?~< Ʋ֪Esk۶;=!D9ěbaq#I@R yVbZ(ڬ+K,M߯BDZSg.3 yx}֕'3rIڶ|z^+-3m#Dh>ZZբTR:;;e|tV)sx8 xp&VzC쵏afkZ8"_m-0$"ĉY8iD̜U$b`HhXJ+b ->WmlQPv\.KyTܸ7 +g!rƘ;u}1ոg//]w][R+U9:]yY9%1^_]J$+jrvp8OBQ̍Z3Yd*f26덦ZԥV'ͧrCu]W9- yf^|O7~#?w}Ǜ λju]k, d=J$6-3v;gy//\_6a{k0"8"-q]1)C4v<-N 4f/L-+3|شBzR֤TQiU[s}O͋gϙ_7'?3 D}Ӵz46liҥ=^@ze$h)0x=!ѳg~~n?@0grˎwL;+ItHD_}WBٵ_nMqeIq5fٚEO-h7M,Ya jbQ ƛ_{!So41^{i[Yc2aGgrKAv1$}~o.pӧo~~W~_?xڣnw)%a\8]:(P`H8ETMӈiij.&?sMsBˆǩe5ս ۍ$jbԭk?M SZ]EYWZ^*41TPvU(]4 X䐒$Dt~QZ#-DrԙINվIRvf8NzU\7P8bgAݖ{^!ҟusM+;Xq%5,cR Zl16r?*E #NcdR,RpVIN^2zuW[+quU\)KՇ]+ErC;[.$lcN"tXiEꕭ0/͍ 0KLTҔS4RB\Tb/z[/"RcV,wU;u,Sd%clSsCU>p|^~'ϟo۾sՊMɸڦOɠR`F"SJ 48v!//j6u+f4D"msr!"u]hv<*6x.o} X>eNA4Bkkp%~Ei"Ӵ2N)h:iI/ȨĸН1dNzL(t)Pn~͢1KSI8 G+^^Nj]^^}ٮc ,PއH산yXR^l+ "ҩ$֝F0c< P ֗ PAeS"h #;b1&#5fLRbR:cJD3ڮ_\c8xj9RR4jf8Qr|;K`Zc l +Zp4MW>4Jo7/]fPl~]_X+/,8@qxg@ORmAB. _XJ|!4p7xmXfUXh_W5Ow?lVO>|p̮#t>؛.fn`K=Z,A,9V1ixPk69-8(u %=L7֚k="ȽKfgB" cr"!4M!xjJ)q9Ji5Ms~~>8NR%B"TBw(_a<n-5y[3y뭯MۮRυŵ!NP C:Lwz"p4Rr-6!VSvI1)Wz4W+_vf3)"VH%5.}ųp{Mm%.U"TzmȨ<:"6=svsmY(Y歷? 1>}_'wj!٬Ϛ †, *?Yq!x8v=Ev1~髿?)MrJ4Ax -2/=b?QUSGݹS+%#\,)wDg" @1)[+/==Uںޙ%Ӌ/8'{`ru㣻HD,ȃ"eҵ,"H`”8P=~~osixFzBuYc(cU:zZet@0~p(Oچ|v@DW}{1t,_|IQY'ĤJeUyGdu 78gODY_3DGsyqsjZ`\F] .C׼ CR˫LEœhŋ' ysNxURC^up~yI(w|i)Yc 6g,)n!_75`qzb9~iqnoo0۔sl6j,+4s0fUr4}-u!xcj;=I4\V5V[ĬuE i4k/Ĵ!!D)Bb6i !YS{$\c%6JR"vӁk<_9JAK֢#4ƠE p@brfcZ7]?ql]`}8Lcb ȅI(6]49۴f1h#cnM‘lyiڶn]s3υ)z "faͱGArn;/\cUD@Yȴm)4>F 0΃sZBBX"`֒^/Ke߻[W`|<[yO "V ]c4:㜀#ː#*Z{u HD`MF8qfa?q8ð/{AN@t%ΐeURL)58!15Ƹk'c!kcF嶮!ԡzqYx *H}{{6YkmffGنI.;%a1Y\dr$W:_juu4Z | ֽsNiH1%P: 0wV@w;#!08qJB(1.m1ΐOyA1ιP֩A' Cިd_B'XHÇ1FR$u_iZ%XFX8WVxw{'a\cY;,ҭ<}{-hY:DJ)h[ k[_7,@y6䚾3H L(F yU%B )aRާx~cgM7뷟>}JF֥TlNӔp^JIJEv9h1Pٓ!e܂:e IMDM( +-MDz5ы1-12s]18D>5Y 1S2Đ@0$1 0h !qJ1Eq!!@,[A$7svd%q15byAq\*Ǘ* R)9%Ғ}T!UCZbe3r*`muHj C&>YLߪdh]*M_}-o| /X'ˎ" 4xywߏu9ZkXW (g*(*BUqNۋ5,OKY wsl}ֶmaj6cs$$g8Lk[7wwy HMkow30Cc$3'HU,p8&.ڹ 3_M,SMr\ubtz†*2*HИy}I v ST#:5G͈UbPv.:gP^!R|n:Ir)fS`U`aCi8nwsssq8Mmӓu/^\o8m/BdLLTŨMS g>Ծ2p}M'BIJO,}[" '0(Bb cbaf2h3aGJ \xt"5G#-J`FfDHM׊&$$H"3qL"RC XcP 0〖fI 8Fv r@uriTbU3* 5%z%.J>r;}qMlm)J2w QqTH&HjЋTY^QIZ8. `%2bL:ag򋵒3"z%3KsOej}*Q?/>Nwu웿/>|qP <]v@08}e)$5l0_~ńP4Z?B'h QMJ^*LE9xucܤ !NC"b)N1ƪL&aAuW+U'"I9\M)Isp&X>''!D0P AD՚ʔoKԴ ?2T5IaIr:3J@)@ЇՃ WdJHwQK9u+Y+. 3'N"E=K1yӘJ9xcI4MW&jVFOFW!X@yzʘ?S?O}ʐnݺƦ׎˗/^<{ɓ'O>CykvN `cdf]iG2#)28K*qSJA4X4@i"Dkw"xaA"`7xNժeA$3|RV( 2иӛKz0!e8LӬ-6ٵ?PV."`g]o_wJӭwBׇsH1F@ɱ/HIR8y~> 0?ֵ#&$@ JOk)E>J)ҳ1JLe Dl[q4mB[̬]YkU䞘$@tOq+Za.&&}du{cL '?{0& j <:h2킌A" h  "瀈ݺWgQ!%#h>gRe?_(1u9bbr񲚎c`Z,@,Jq'CRo1 Ct~;)I_sC(5B,_^0真F" HD(G" N31V$CrVxRI03G "@'$ ^/!1Rf$ɽa ^cbC i!`Z`;`sL}j1݄JG RU)J%|T_9Wi]uYkٶuY+){3FQ5 קA׍] 4Ga)鸾>_5+^KvMK-&cjR|iQo˳3BcVϺX̜RPB$iHb[¥( ]q@F֭ʼtM~SN}c;)O(/L-XG4]^]{w'>]}:@L@,A$ #,)-1HbA40SY˿z`z yɱf鰵ֶm " QR^$L D(W&ʩL #5B2Hyx'`]gYDI,&wi1ya-?{ Y#U5#H@*͒QjKT/CR׸PNTE\۶]xqcxooo#Ĕb8%M;;/~鳟g~3m6M ZӤgW^ۮ.VU,Ȝ0 YnaPx0/^>}ɻ~ɓϞ=fžTyXJ⸄_]69O)u^p Kp(rDqE~xv~;8z1R4sDιk! kF7@׵d 5)yXn׮ZD Y_rXr&^jtڶ*hչ1FFs3N#H)|6B~0a6kv7NчUD" F)N9rƈJ“ڦiH| IDATdsc;24&iQ Df06[6aJX4HMӶ̇d@rRVMsrAf"˹QLr@D<)]!?C5td1K%T3B/ǭG54VL+Y`1zV+ibbZ2aޫy{5y>|(m8b`) " a!] ;aڿ"<[8Oqiļ[8p=k魯iq={.gۋ4Owa8iWvu^\\ ðZUbh.EDp?:}P*@\=ɓ'緷qFuqg~'k)aZV;?? ;! h2+Q f',}[H[^QH )ފ1t}UT iqn!ٹ1<}~|KdH Q1n6Òx0i4yϞ?D|q?v}s15'nf=!mۯqZvPbld)/vm]__8  i~6~yzs^;i[`U?;;c曛D|xhP*CdRJ9Sgkƪ1&x":cgtV>%n`svw{k^xm7X@m2s|9OY3&!y"y4( Dh8b~%]k^{ͯݽ<+w3lGcsi,3,NNd$xo8;c777f!~_@ XT5,vi5xl%SZ Xάr -d9Ng"ffϵMD ޮC22E6Vuvp6-0[[a%pVrԙA,֚*^~O֊+T9CҒSJo!o<|7}Ӻkζm13Y h$ɶm=\o[w_ӯ|Qpagc]tZ¦uinC8wM&!yﭵiёkm;,r.C|7xb;=x0M^[fnWxap~_;;S$S:\9- sL)8YBl\bHAlȹ)[׿Ong#4]RJ mN3ec/p%Cz*`5fRJ,&Xa.*[c9X6"mJv64]?+ %M/AڄB>Y+z $"S-u\4cQ#mRi hZw/.6R d][!%ؔF@uA>dG<.' Raۿc0o777_W/ Z(!cøڬ=~'bfali`F*t0Ϫ BwwG.hg}Cn8Ɲ֑1E1k#iێFmB0k>8R?o/ҟ|}o/4-[`]7@'*bMuo}"8*\FF%)^$VJRշ^m&qvns??O}_{xqL40AoEhjۢ@`p! KL1F?%ɏz~ɓbȌb }/C~ڇS s'fsۺi#1@Tf,uc Bޯ:W>1݌$)j3W'8'1du|Eo삄/zTrP QECh{œ^aBpnn0.102~1!%!4!c7!?荏~뷼c) y}4ANTC%,V"G+fbM:[@cLF\ A0WTn9 ,UҔ%"j'-|⅂Qbh M)TD$Kz :GD$kZZK&"1?L>R:4%@HRĪϫbh^p (\|ɒ ĬJV>CoX3f'cÇy@IXL ámA/~Stb ,'fxy݀aOBah6p@qQxsJJZv$ĘB075MӉ`9#iNgXV~GogsbY,]ٵpy Ϻr(XDr:cN

yc<O#K֛FYvUgwzc25,  ؔ*۫ݫUˮU]mv^E]ݦn!H%1NgOȥxý眽}dEQEE 0hG㦵VjZrF9SBXpDÇW>|܈xnUDN6D֩)uR:NCeZoQ&3o/eФ4I"mY{G*E߼|iw>K%8%2ǙR9e[mK:1iߊi)<z2e-KI0 \J+}G^y_?k@5,-Xrnqngw_* Ȕ'qp$0Fz:#]~΋BDʉ6 Թ@kMBh5w<Xd9TcFUwBg(C6MDYHr1"dHbRj[[C,3`a1b,$!D9M9֐$AhӌVb0`BzLp5=M㶂[s ~Ȧ>oz(X:O|Hh>`e`b,83}Uu@AYi W'QH|ԈRY>l5+J̩>d5:R09 \  B`!PL@L"BB4:?Dℇ! Q>Rj l9 RB+&1h 0ffxx}_ׯG"VNՋ/mV ITXMlM P TGշ,[I|7T)e<ɦ¤RJGVK/=d٘`B!6P$~ 󧘵`68v0.7 {A_"s9cCPәέm\4@^04VEAPJug~:"H8 C1!161Z+pMFZ>[R_.PJkw0&N&A]E s cD5`VEGʽ;YNǠm@&ƅ@Y"cjESjL "DY"s sQfp,:]c,83/r'}C!b LRf;ɦH@i#|֏T4}X}ÄT=u{ܔuW |~Z &WcSj5TM$U&#~!*$U#ږR rjIq؂ߓ8HkHUWM%TǺ):-Qjv;^wu E91mr0`>  ;o[[Hq !!Ll2DmԈGPq2Ƃvhkڪ0J8!'r"KsA1#$v*Tպ,K[k8e5~!h<QBY_2+걇tv7R!j#Z^U@kD^AR+1&ʲ4_|LYR Y15&XN]j2 U9~,Jc|g(s-/= /`9B)vo?˲luZe$܃QEGFί%:!'}skh,d5z`*O /;ꯡoAHzzzdLC!B0rF3d*f$D6!cEsBJAkPfDVh)QjIam@8j8qALa$2bHxtZ)x |_+kkVVr4#Bvv`~~Nh543֨<2R_p[ a<𵔔C@e)!tȤQ11q12`;tję*\ؤA)FPeh,\m0 qjVF ]XkA%!QiΘ6XR*.D攻 d*7 `cQJZ !YBKRZ!ʹdر֗/]Z]<g:ʰ :LP3"U!FĹbw&#F;[ͫ[[2qGP|(ifgvw:|~%PI0J08 v%RK(h0Ii(eB0 ѽBz08ʰPL0Y2%uV9a8Fe٘lƌQ!Jc87n5YQH5T5f!I"BB") Vq *Xäװ{'bře;}߭wSϑs[w541R6+A2]cmAAʥK kg[-M[ơֶ8[!B& {SsNntb_unT3'5 } ZAP5=:Ѷ  NR 2r_NDX&1+aZ桎La8BhQA)vA-KP'Z&B*jj~1~ƨ0ZeYNjVj:F}D]qWl:ֹ?bVswuGU][k9^vlU{]^YF<}sw}7*MK?O?˧>}e}!lre0i]YX^>rɓw~p>|ԩS4M9t%I[[[Y9oQim4 IV'ɋEQe$~ژ47 \߼[[[{W?}3#ASusQjNӴ4M=qMb1+F_Zk1[a~h$ C8oZs+,D5<~ʕu׏D@Tm ~ּ(qgHiZkL( 㙙CKK+7G=zoܹQI|RMy"x<>=\a \iƘǂ^.cM`[>-?9Pq7/vʊ.W O~$I_ܼfy5v(|yZ^=zheV3vׯei a0EqO~2//xnnfNk]šCzӯL)ec[I5N"BV_z:sHhݢw= cģ 6> 5c SJF$&\x⣏<5hZTaFkHU(~M1iZikoM54L" TI-%uYA4 ((4,AjZRi'E!r Q6ӥ5D}N+ק˝N{<&k (G2PdJWN 'M#Ρ%౜'cLuAX)S>v:9G12֒beQ$2MnM#5VzJ99 Tw6c1|ZOQd)<9h;YG?)]Yntg]c{{#ڀYreyнw++7*;}8SB2]oq0ˆ3ntH//+y+,+txp䦛Ϟx韼3(bAc?ܳ~~Go7+++zoY=vҥ77vvY嗿`$֏*VyNAWiQad]:9gZ(B0eě9p9W{!/i-c0rU6al.tbQA Hk29 e^HYj.EQ(Qy>F`'EI16VIKa[oY^)J`L1D&륕=Vyh-8~q*%co=#gp8l0O;HxQٙƝ~:;33mAk)Y]\},6 IM9A4((?߯hD)T%viJWt?]?IR,0nS S!b]E8h[!TI=%Ji2B Ƴ:l/ruōP[M fq2R5IKd՝T=1ul;Քb6Õ[:;c;O.~}^xEJf,206d} qV;/lAb0-3Ʀgw룸'-*B%v @[#e1}2fa!4!h"M OÐsVHWOSv;RƸ? c3 0TdֽMf SXM)Slnme "<_PkߠCs^+r5MU](Ѐ c!$oZ;6WK궚s3`}/}aa~n}#+7g?g>{uA??{CF$ib2FeYǿCu]ZW^yԩSV!L M'~g[ss 8M뇏̙۞q/eQ=^wt%Q܈>ܝw9;;;/lbgfOc7pQj[kۭ?ǿqBYR7h5VcP<Gmo(TI)9eoq]J%IޠL|Zkg,8F^zp))`_b %@+!JGh\e!f8̒kpGWGq3_z׃B%5眲p80ˆ@km-qNh4>w xlOLJ:3p˗LEtLƗto]go|=խ6QSѯ%jdVczp$Qj|vNiZbP MbO-ׇ68,ظ#!al\L>$3hJA.S˫z~N}R^EhؖBLt]XX~:MSBkG 9|d=ԧ?uc/r;} RD!pWx?MX+-D9y}=>(G}YcKwꫯw:,N:uIpTJGpI8WxphRsss;;oϝ>g{g'~G',}G_O`a67`0T"|U*Nz3CEAӺSm9V~cTKXBUN^3j>F( RDQ;w7s]i6 MY0k3 3Z2We.Fsº3s$j̵ggffZ4nz7%4`Akg΁1گjBH'9܏:|84MFRe*8FKJ@AhjϷ|I4"ʵF%qs¤#mDW nt#{G"Ӭ@Z^F֡7,:vƨXvŰ?spN\RxK^j,LMoKНw.gݞ=vϻ6V[b8bT(2mFkaal`@)Rk_~5˲n魷Οwܱ5[8w(۞͕pc~Rz3[ (EL-xZGi? Wcj-X8PB`_RjaO =#&Wa&%5B!K1efT;ekAcJ5ϓVXwnmvh]@,QkRk +uWWV]Ycgkjm^Dqol\⠱pHsf{so`p2/]~m4.oؙrpb,8\[+?sq <~>?vܕNQ zWH퍍fys h_D!4CRLqK,-6+{ a}Fky6i\)RR{ycADXG(9ghOn5>Q c׈Gb29E3_4M9u.77V|'{Nr /<ۢ(mx@h+V%xg_n04&@CIp<ˆiUZ1cիNe_xQrelY޶Zgz,-^}o7^}㍳B͓'O:ts:gff]^Rxe-]ƘbH'?y9!GHyG:j9';&Bx}֚E]-GUcLLVgx@Ϗ}I)>{&N癷#t4@Gd8{2Ɣeh>M ηj݇ rhG$vu{7?7ћo(i]Ea \OFOFTnBprK!\V)p0.P$IAxiwo;"|&w޿Y]=KkNλ/_.]Zytͧ.^=tbkk=zԩS.\jtyYN1 Uz~4[x+c,l(|0]zjOgK+]u笷#x#>WB>uhRv 䴵`c X (_~~kkzOiE ic{!I'n4Bb'8|c@//on#cD)a#b8muFSʲD1 uu,& Sz cD_$;$L:4gQdm) {s7| AivsD.eH'3./(Lu7I/,mʢ0``ؠ3'ag-esIk^6Tl Zln)x5诬ju:!QRtTijeiR(,! c:xrhJi%3*͸8ڵS)gF(J34Uc,qr() dHldUTI)I,@v)#2Ar``nv.-rG{^1f8z&c^f{;C4YEQDaԜ|@a(Fr-`+~BH뉿O8! ff) - Y@`i"pB\I5D+yaS9Ywxv}A$2Pڠft~~~ڵ8>=z3?z>؜iK)Ͽڙ3g~w`@>}?M!D79R4PZܮ,q`" F(ܜp& Jt8$HPK3 !T0J))"-4ƘNh44W׮fs)677333aq,, :pBW'sxW;+UIDAT5>U?hIөjIo`&֚P^I"9hCUW<*`"lrƈR8˲g~16.Ki8WB:VAoff& tRߺpEARѝ{}?|É/oK%JUJʝs֚8Rd06MCT@L%ViDvfXi5K4o c9k 0F0FukI?_ڈucL94hzٺt$F cbОqՁ8c"`QB 4f,R?WhZAYKc(Rߏ+N2ȍ0XH2v1(rڦYF1( \'8x/d#Р[V VO(p[8ؒC8!(84Y) Y9R, k40Sa™Z)M!R<UcMJT }̟1-߆5ZN) Nc9a`<`~uB`0 x(\^X Z! B`NFmc?T&>xӻ'k=$|}P7; b!@sDRt9ܼ.\|+ __{o=w=}usۿ=ƱG瞻4ppDZ6kKJiQwqQV!D(u%|0 XR%9p$†VItFgas ! hH)9,˴I UX_i0gU֚(BJ]ځVMV"RBiAURBaW!qz]+71 Rn=1&IPJYcc᭵3k=vsF!țJ0PQs,eEv+vGt3y3)ee)iFHEJ)[RJ(vUqpZk) - 8E0F@HB3KuvUի9{]޻9g5ܴuZK˒q^NGO=#lYdgViF2!/J5=իWggg_w}Vgfm]]__JEA8-k5wr_(S CN0>/}^ڈX g+yw_"OC h4:_ @-U ,drJ)F8K͍mX;6ςcqXA(Ux[M"@@ +9ZQN5R*1c\34!`qf2; ] Z*Y 6t~M\^ND3025jl4fBYAm7@X$":<lE BG4!m:Օ,YUwSD!,2Ћ^3X>IXK:+_V|/ceQ)mjV2#3,HI $>x\$B8ڶ*/.00qd TJE ]u;NY8 H;=L9㢺hzMe)w0RsDYJZQБKL#${w7@" 1Rb(nY5xgO vFtqrQFQ7T ,0WW={uabcDC\}?9S|^o)K IH/GI-oyCm>ϫۘ@`̶Y׺ >eE) 452/f뇲R1ۺ(cE])s"'u->?^i`:7%.˵+|KD-5aZdHH9[_Lj=Qu8т,)a-:=jm6Gj%Ј QI2hSJ[q&(H@FPjRJWd-Pfvt.眠aIi ^زj`tZ1ß|h-3"\9)TKRt.rfb&\ h9>faj:_!dwYZ!\Ͳ>9^{nN`y}Sl#: )@ VyT|)k7_޼hJT9g_ڜ8Le>^?|իW}tybg/c{;fû!i !6"㻵l>K)KO=ª")<#3u]m`snZRbHTJYVMTu!89oooowwI@dSnZQfD'%jKF*pYG4Pf !$?>/ڦaN+{QRXcFin3L[__Vۍ;۷owXk9g!O/ݛ|7Usvnpm[A$ؖ|m9u]{<,ZZ+%8s-OTܝ'.oUșȁBgo$B$ VJHjr>{]VMjd% hcI lG k}IjC6r>Dmڶmж< \^j=GKZƜoL-}hZ>+4r~yP*ri ؽWɅs9F+- XBA>Pֵ^PR@JytNhk~&Jc*OD3\u9(ATKq.$xؖKhIDATxbb ̆QIENDB`assets/rectangle.png000066400000000000000000000001611307617767500150530ustar00rootroot00000000000000PNG  IHDR AP=8IDAT(cdfff "dΟХYEᣊ40`K- 0 <]IENDB`assets/rgb16-2x1.png000066400000000000000000000001171307617767500144410ustar00rootroot00000000000000PNG  IHDR+4IDATxb$A IENDB`assets/rgb2x1.png000066400000000000000000000001141307617767500142120ustar00rootroot00000000000000PNG  IHDR{@IDATxb`P \IENDB`doc/000077500000000000000000000000001307617767500116465ustar00rootroot00000000000000doc/colorspace.md000066400000000000000000000053461307617767500143320ustar00rootroot00000000000000 ## Color Space Conversions ## This section includes functions for performing conversions between different color spaces. ### [res] image.rgb2lab([dst,] src) ### Converts a `src` RGB image to [Lab](https://en.wikipedia.org/wiki/Lab_color_space). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.lab2rgb([dst,] src) ### Converts a `src` [Lab](https://en.wikipedia.org/wiki/Lab_color_space) image to RGB. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.rgb2yuv([dst,] src) ### Converts a RGB image to YUV. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.yuv2rgb([dst,] src) ### Converts a YUV image to RGB. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.rgb2y([dst,] src) ### Converts a RGB image to Y (discard U and V). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.rgb2hsl([dst,] src) ### Converts a RGB image to [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.hsl2rgb([dst,] src) ### Converts a HSL image to RGB. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.rgb2hsv([dst,] src) ### Converts a RGB image to [HSV](https://en.wikipedia.org/wiki/HSL_and_HSV). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.hsv2rgb([dst,] src) ### Converts a HSV image to RGB. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.rgb2nrgb([dst,] src) ### Converts an RGB image to normalized-RGB. ### [res] image.y2jet([dst,] src) ### Converts a L-levels (1 to L) greyscale image into a L-levels jet heat-map. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. This is particulary helpful for understanding the magnitude of the values of a matrix, or easily spot peaks in scalar field (like probability densities over a 2D area). For example, you can run it as ```lua image.display{image=image.y2jet(torch.linspace(1,10,10)), zoom=50} ``` doc/drawing.md000066400000000000000000000035711307617767500136310ustar00rootroot00000000000000 ## Simple Drawing Routines ## This section includes simple routines to draw on images. ### [res] image.drawText(src, text, x, y, [options]) ### Draws text onto a 3-channel Tensor (C x H x W) at the x-offset `x` and y-offset `y`. The `options` table can be passed in to set color, background color, in-place etc. Options: * `color` - [table] The text color. A table of 3 numbers `{R, G, B}`, each number scaled between 0 and 255. For example, `red` is `{255, 0, 0}` * bg - [table] The background color where text is drawn. Same format as color. * size - [number] Size of the text to be drawn. `Default value = 1`. * wrap - [boolean] If the text goes out of bounds, wrap it with a newline automatically. `default value = true` * inplace - [boolean] If true, draws directly on the input tensor and returns it. `default value = false` Example: ```lua image.drawText(image.lena(), "hello\nworld", 10, 10) image.drawText(image.lena(), "hello\nworld", 10, 20,{color = {0, 255, 0}, size = 5}) image.drawText(image.lena(), "hello\nworld", 10, 20,{color = {0, 255, 0}, bg = {255, 0, 0}, size = 5}) ``` ### [res] image.drawRect(src, x1, y1, x2, y2, [options]) ### Draws a rectangle onto a 3-channel Tensor (C x H x W). The top-left corner of the rectangle is `x1, y1`, and the bottom-right corner is `x2, y2`. The `options` table can be passed in to set color, in-place etc. Options: * `color` - [table] The rectangle color. A table of 3 numbers `{R, G, B}`, each number scaled between 0 and 255. For example, `red` is `{255, 0, 0}` * `lineWidth` - [number] The width of the rectangle line, in pixels * `inplace` - [boolean] If true, draws directly on the input tensor and returns it. `default value = false` Example: ```lua image.drawRect(image.lena(), 200, 200, 370, 400, {lineWidth = 5, color = {0, 255, 0}}) ``` doc/gui.md000066400000000000000000000056311307617767500127610ustar00rootroot00000000000000 ## Graphical User Interfaces ## The following functions, except for [image.toDisplayTensor](#image.toDisplayTensor), require package [qtlua](https://github.com/torch/qtlua) and can only be accessed via the `qlua` Lua interpreter (as opposed to the [th](https://github.com/torch/trepl) or luajit interpreter). ### [res] image.toDisplayTensor(input, [...]) ### Optional arguments `[...]` expand to `padding`, `nrow`, `scaleeach`, `min`, `max`, `symmetric`, `saturate`. Returns a single `res` Tensor that contains a grid of all in the images in `input`. The latter can either be a table of image Tensors of size `height x width` (greyscale) or `nChannel x height x width` (color), or a single Tensor of size `batchSize x nChannel x height x width` or `nChannel x height x width` where `nChannel=[3,1]`, `batchSize x height x width` or `height x width`. When `scaleeach=false` (the default), all detected images are compressed with successive calls to [image.minmax](simpletransform.md#image.minmax): ```lua image.minmax{tensor=input[i], min=min, max=max, symm=symmetric, saturate=saturate} ``` `padding` specifies the number of padding pixels between images. The default is 0. `nrow` specifies the number of images per row. The default is 6. Note that arguments can also be specified as key-value arguments (in a table). ### [res] image.display(input, [...]) ### Optional arguments `[...]` expand to `zoom`, `min`, `max`, `legend`, `win`, `x`, `y`, `scaleeach`, `gui`, `offscreen`, `padding`, `symm`, `nrow`. Displays `input` image(s) with optional saturation and zooming. The `input`, which is either a Tensor of size `HxW`, `KxHxW` or `Kx3xHxW`, or list, is first prepared for display by passing it through [image.toDisplayTensor](#image.toDisplayTensor): ```lua input = image.toDisplayTensor{ input=input, padding=padding, nrow=nrow, saturate=saturate, scaleeach=scaleeach, min=min, max=max, symmetric=symm } ``` The resulting `input` will be displayed using [qtlua](https://github.com/torch/qtlua). The displayed image will be zoomed by a factor of `zoom`. The default is 1. If `gui=true` (the default), the graphical user inteface (GUI) is an interactive window that provides the user with the ability to zoom in or out. This can be turned off for a faster display. `legend` is a legend to be displayed, which has a default value of `image.display`. `win` is an optional qt window descriptor. If `x` and `y` are given, they are used to offset the image. Both default to 0. When `offscreen=true`, rendering (to generate images) is performed offscreen. ### [window, painter] image.window([...]) ### Creates a window context for images. Optional arguments `[...]` expand to `hook_resize`, `hook_mousepress`, `hook_mousedoublepress`. These have a default value of `nil`, but may correspond to commensurate qt objects. doc/index.md000066400000000000000000000024261307617767500133030ustar00rootroot00000000000000# image Package Reference Manual # __image__ is the [Torch7 distribution](http://torch.ch/) package for processing images. It contains a wide variety of functions divided into the following categories: * [Saving and loading](saveload.md) images as JPEG, PNG, PPM and PGM; * [Simple transformations](simpletransform.md) like translation, scaling and rotation; * [Parameterized transformations](paramtransform.md) like convolutions and warping; * [Simple Drawing Routines](doc/drawing.md) like drawing text or a rectangle on an image; * [Graphical user interfaces](gui.md) like display and window; * [Color Space Conversions](colorspace.md) from and to RGB, YUV, Lab, and HSL; * [Tensor Constructors](tensorconstruct.md) for creating Lenna, Fabio and Gaussian and Laplacian kernels; Note that unless speficied otherwise, this package deals with images of size `nChannel x height x width`. ## Install The easiest way to install this package it by following the [intructions](http://torch.ch/docs/getting-started.html) to install [Torch7](www.torch.ch), which includes __image__. Otherwise, to update or manually re-install it: ```bash $ luarocks install image ``` ## Usage ```lua > require 'image' > l = image.lena() > image.display(l) > f = image.fabio() > image.display(f) ``` doc/paramtransform.md000066400000000000000000000115731307617767500152330ustar00rootroot00000000000000 ## Parameterized transformations ## This section includes functions for performing transformations on images requiring parameter Tensors like a warp `field` or a convolution `kernel`. ### [res] image.warp([dst,]src,field,[mode,offset,clamp_mode,pad_val]) ### Warps image `src` (of size`KxHxW`) according to flow field `field`. The latter has size `2xHxW` where the first dimension is for the `(y,x)` flow field. String `mode` can take on values [lanczos](https://en.wikipedia.org/wiki/Lanczos_resampling), [bicubic](https://en.wikipedia.org/wiki/Bicubic_interpolation), [bilinear](https://en.wikipedia.org/wiki/Bilinear_interpolation) (the default), or *simple*. When `offset` is true (the default), `(x,y)` is added to the flow field. The `clamp_mode` variable specifies how to handle the interpolation of samples off the input image. Permitted values are strings *clamp* (the default) or *pad*. When `clamp_mode` equals `pad`, the user can specify the padding value with `pad_val` (default = 0). Note: setting this value when `clamp_mode` equals `clamp` will result in an error. If `dst` is specified, it is used to store the result of the warp. Otherwise, returns a new `res` Tensor. ### [res] image.affinetransform([dst,]src,matrix,[mode,translation,clamp_mode,pad_val]) ### Warps image `src` (of size`KxHxW`) according to `(y,x)` affine transformation defined by `matrix`. The latter has size `2x2`. String `mode` can take on values [lanczos](https://en.wikipedia.org/wiki/Lanczos_resampling), [bicubic](https://en.wikipedia.org/wiki/Bicubic_interpolation), [bilinear](https://en.wikipedia.org/wiki/Bilinear_interpolation) (the default), or *simple*. Additional translation can be added to the image before affine transformation with `translation`.( Default is `torch.Tensor{0, 0}`.) The `clamp_mode` variable specifies how to handle the interpolation of samples off the input image. Permitted values are strings *clamp* (the default) or *pad*. When `clamp_mode` equals `pad`, the user can specify the padding value with `pad_val` (default = 0). Note: setting this value when `clamp_mode` equals `clamp` will result in an error. If `dst` is specified, it is used to store the result of the warp. Otherwise, returns a new `res` Tensor. ### [res] image.convolve([dst,] src, kernel, [mode]) ### Convolves Tensor `kernel` over image `src`. Valid string values for argument `mode` are : * *full* : the `src` image is effectively zero-padded such that the `res` of the convolution has the same size as `src`; * *valid* (the default) : the `res` image will have `math.ceil(kernel/2)` less columns and rows on each side; * *same* : performs a *full* convolution, but crops out the portion fitting the output size of *valid*; Note that this function internally uses [torch.conv2](https://github.com/torch/torch7/blob/master/doc/maths.md#torch.conv.dok). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.lcn(src, [kernel]) ### Local contrast normalization (LCN) on a given `src` image using kernel `kernel`. If `kernel` is not given, then a default `9x9` Gaussian is used (see [image.gaussian](tensorconstruct.md#image.gaussian)). To prevent border effects, the image is first global contrast normalized (GCN) by substracting the global mean and dividing by the global standard deviation. Then the image is locally contrast normalized using the following equation: ```lua res = (src - lm(src)) / sqrt( lm(src) - lm(src*src) ) ``` where `lm(x)` is the local mean of each pixel in the image (i.e. `image.convolve(x,kernel)`) and `sqrt(x)` is the element-wise square root of `x`. In other words, LCN performs local substractive and divisive normalization. Note that this implementation is different than the LCN Layer defined on page 3 of [What is the Best Multi-Stage Architecture for Object Recognition?](http://yann.lecun.com/exdb/publis/pdf/jarrett-iccv-09.pdf). ### [res] image.erode(src, [kernel, pad]) ### Performs a [morphological erosion](https://en.wikipedia.org/wiki/Erosion_(morphology)) on binary (zeros and ones) image `src` using odd dimensioned morphological binary kernel `kernel`. The default is a kernel consisting of ones of size `3x3`. Number `pad` is the value to assume outside the image boundary when performing the convolution. The default is 1. ### [res] image.dilate(src, [kernel, pad]) ### Performs a [morphological dilation](https://en.wikipedia.org/wiki/Dilation_(morphology)) on binary (zeros and ones) image `src` using odd dimensioned morphological binary kernel `kernel`. The default is a kernel consisting of ones of size `3x3`. Number `pad` is the value to assume outside the image boundary when performing the convolution. The default is 0. doc/saveload.md000066400000000000000000000055671307617767500140030ustar00rootroot00000000000000 ## Saving and Loading ## This sections includes functions for saving and loading different types of images to and from disk. ### [res] image.load(filename, [depth, tensortype]) ### Loads an image located at path `filename` having `depth` channels (1 or 3) into a [Tensor](https://github.com/torch/torch7/blob/master/doc/tensor.md#tensor) of type `tensortype` (*float*, *double* or *byte*). The last two arguments are optional. The image format is determined from the `filename`'s extension suffix. Supported formats are [JPEG](https://en.wikipedia.org/wiki/JPEG), [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics), [PPM and PGM](https://en.wikipedia.org/wiki/Netpbm_format). The returned `res` Tensor has size `nChannel x height x width` where `nChannel` is 1 (greyscale) or 3 (usually [RGB](https://en.wikipedia.org/wiki/RGB_color_model) or [YUV](https://en.wikipedia.org/wiki/YUV). Usage: ```lua --To load as byte tensor for rgb imagefile local img = image.load(imagefile,3,'byte') --To load as byte tensor for gray imagefile local img = image.load(imagefile,1,'byte') ``` ### [res] image.getSize(filename) ### Return the size of an image located at path `filename` into a LongTensor. The image format is determined from the `filename`'s extension suffix. Supported formats are [JPEG](https://en.wikipedia.org/wiki/JPEG), [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics), [PPM and PGM](https://en.wikipedia.org/wiki/Netpbm_format). The returned `res` Tensor has size `3` (nChannel, height, width). ### image.save(filename, tensor) ### Saves Tensor `tensor` to disk at path `filename`. The format to which the image is saved is extrapolated from the `filename`'s extension suffix. The `tensor` should be of size `nChannel x height x width`. To save with a minimal loss, the tensor values should lie in the range [0, 1] since the tensor is clamped between 0 and 1 before being saved to the disk. ### [res] image.decompressJPG(tensor, [depth, tensortype]) ### Decompresses an image from a ByteTensor in memory having `depth` channels (1 or 3) into a [Tensor](https://github.com/torch/torch7/blob/master/doc/tensor.md#tensor) of type `tensortype` (*float*, *double* or *byte*). The last two arguments are optional. Usage: ```lua local fin = torch.DiskFile(imfile, 'r') fin:binary() fin:seekEnd() local file_size_bytes = fin:position() - 1 fin:seek(1) local img_binary = torch.ByteTensor(file_size_bytes) fin:readByte(img_binary:storage()) fin:close() -- Then when you're ready to decompress the ByteTensor: im = image.decompressJPG(img_binary) ``` ### [res] image.compressJPG(tensor, [quality]) ### Compresses an image to a ByteTensor in memory. Optional quality is between 1 and 100 and adjusts compression quality. doc/simpletransform.md000066400000000000000000000152521307617767500154220ustar00rootroot00000000000000 ## Simple Transformations ## This section includes simple but very common image transformations like cropping, translation, scaling and rotation. ### [res] image.crop([dst,] src, x1, y1, [x2, y2]) ### Crops image `src` at coordinate `(x1, y1)` up to coordinate `(x2, y2)`. The coordinate indexing is zero-based and `(x2, y2)` is non-inclusive. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ```lua -- The indexing starts with 0 and 2 is non-inclusive coordinate. > require('image') > image.crop(torch.Tensor(3, 2, 2), 0, 0 , 2, 2) -- crop is a correct crop and the result is 3x2x2 tensor. (1,.,.) = 0 0 0 0 (2,.,.) = 0 0 0 0 (3,.,.) = 0 0 0 0 [torch.DoubleTensor of size 3x2x2] ``` ### [res] image.crop([dst,] src, format, width, height) ### Crops a `width x height` section of source image `src`. The argument `format` is a string specifying where to crop: it can be "c", "tl", "tr", "bl" or "br" for center, top left, top right, bottom left and bottom right, respectively. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.translate([dst,] src, x, y) ### Translates image `src` by `x` pixels horizontally and `y` pixels vertically. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.scale(src, width, height, [mode]) ### Rescale the height and width of image `src` to have width `width` and height `height`. Variable `mode` specifies type of interpolation to be used. Valid values include [bilinear](https://en.wikipedia.org/wiki/Bilinear_interpolation) (the default), [bicubic](https://en.wikipedia.org/wiki/Bicubic_interpolation), or *simple* interpolation. Returns a new `res` Tensor. ### [res] image.scale(src, size, [mode]) ### Rescale the height and width of image `src`. Variable `size` is a number or a string specifying the size of the result image. When `size` is a number, it specifies the maximum height or width of the output. When it is a string like `WxH` or `MAX` or `^MIN`, `*SC` or `*SCn/SCd` it specifies the `height x width`, maximum height or width of the output, minimum height or width of the output, scaling factor (number), or fractional scaling factor (int/int), respectively. ### [res] image.scale(dst, src, [mode]) ### Rescale the height and width of image `src` to fit the dimensions of Tensor `dst`. ### [res] image.rotate([dst,], src, theta, [mode]) ### Rotates image `src` by `theta` radians. If `dst` is specified it is used to store the results of the rotation. Variable `mode` specifies type of interpolation to be used. Valid values include *simple* (the default) or *bilinear* interpolation. ### [res] image.polar([dst,], src, [interpolation], [mode]) ### Converts image `src` to polar coordinates. In the polar image, angular information is in the vertical direction and radius information in the horizontal direction. If `dst` is specified it is used to store the polar image. If `dst` is not specified, its size is automatically determined. Variable `interpolation` specifies type of interpolation to be used. Valid values include *simple* (the default) or *bilinear* interpolation. Variable `mode` determines whether the *full* image is converted to the polar space (implying empty regions in the polar image), or whether only the *valid* central part of the polar transform is returned (the default). ### [res] image.logpolar([dst,], src, [interpolation], [mode]) ### Converts image `src` to log-polar coordinates. In the log-polar image, angular information is in the vertical direction and log-radius information in the horizontal direction. If `dst` is specified it is used to store the polar image. If `dst` is not specified, its size is automatically determined. Variable `interpolation` specifies type of interpolation to be used. Valid values include *simple* (the default) or *bilinear* interpolation. Variable `mode` determines whether the *full* image is converted to the log-polar space (implying empty regions in the log-polar image), or whether only the *valid* central part of the log-polar transform is returned (the default). ### [res] image.hflip([dst,] src) ### Flips image `src` horizontally (left<->right). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.vflip([dst,], src) ### Flips image `src` vertically (upsize<->down). If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.flip([dst,] src, flip_dim) ### Flips image `src` along the specified dimension. If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor. ### [res] image.minmax{tensor, [min, max, ...]} ### Compresses image `tensor` between `min` and `max`. When omitted, `min` and `max` are infered from `tensor:min()` and `tensor:max()`, respectively. The `tensor` is normalized using `min` and `max` by performing : ```lua tensor:add(-min):div(max-min) ``` Other optional arguments (`...`) include `symm`, `inplace`, `saturate`, and `tensorOut`. When `symm=true` and `min` and `max` are both omitted, `max = min*2` in the above equation. This results in a symmetric dynamic range that is particularly useful for drawing filters. The default is `false`. When `inplace=true`, the result of the compression is stored in `tensor`. The default is `false`. When `saturate=true`, the result of the compression is passed through a function that clips the values between 0 and 1 (i.e. anything below 0 is set to 0, anything above 1 is set to 1). When provided, Tensor `tensorOut` is used to store results. Note that arguments should be provided as key-value pairs (in a table). ### [res] image.gaussianpyramid([dst,] src, scales) ### Constructs a [Gaussian pyramid](https://en.wikipedia.org/wiki/Gaussian_pyramid) of scales `scales` from a 2D or 3D `src` image or size `[nChannel x] width x height`. Each Tensor at index `i` in the returned list of Tensors has size `[nChannel x] width*scales[i] x height*scales[i]`. If list `dst` is provided, with or without Tensors, it is used to store the output images. Otherwise, returns a new `res` list of Tensors. Internally, this function makes use of functions [image.gaussian](tensorconstruct.md#image.gaussian), [image.scale](#image.scale) and [image.convolve](paramtransform.md#image.convolve). doc/tensorconstruct.md000066400000000000000000000113011307617767500154430ustar00rootroot00000000000000 ## Tensor Constructors ## The following functions construct Tensors like Gaussian or Laplacian kernels, or images like Lenna and Fabio. ### [res] image.lena() ### Returns the classic `Lenna.jpg` image as a `3 x 512 x 512` Tensor. ### [res] image.fabio() ### Returns the `fabio.jpg` image as a `257 x 271` Tensor. ### [res] image.gaussian([size, sigma, amplitude, normalize, [...]]) ### Returns a 2D [Gaussian](https://en.wikipedia.org/wiki/Gaussian_function) kernel of size `height x width`. When used as a Gaussian smoothing operator in a 2D convolution, this kernel is used to `blur` images and remove detail and noise (ref.: [Gaussian Smoothing](http://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm)). Optional arguments `[...]` expand to `width`, `height`, `sigma_horz`, `sigma_vert`, `mean_horz`, `mean_vert` and `tensor`. The default value of `height` and `width` is `size`, where the latter has a default value of 3. The amplitude of the Gaussian (its maximum value) is `amplitude`. The default is 1. When `normalize=true`, the kernel is normalized to have a sum of 1. This overrides the `amplitude` argument. The default is `false`. The default value of the horizontal and vertical standard deviation `sigma_horz` and `sigma_vert` of the Gaussian kernel is `sigma`, where the latter has a default value of 0.25. The default values for the corresponding means `mean_horz` and `mean_vert` are 0.5. Both the standard deviations and means are relative to kernels of unit width and height where the top-left corner is the origin. In other works, a mean of 0.5 is the center of the kernel size, while a standard deviation of 0.25 is a quarter of it. When `tensor` is provided (a 2D Tensor), the `height`, `width` and `size` are ignored. It is used to store the returned gaussian kernel. Note that arguments can also be specified as key-value arguments (in a table). ### [res] image.gaussian1D([size, sigma, amplitude, normalize, mean, tensor]) ### Returns a 1D Gaussian kernel of size `size`, mean `mean` and standard deviation `sigma`. Respectively, these arguments have default values of 3, 0.25 and 0.5. The amplitude of the Gaussian (its maximum value) is `amplitude`. The default is 1. When `normalize=true`, the kernel is normalized to have a sum of 1. This overrides the `amplitude` argument. The default is `false`. Both the standard deviation and mean are relative to a kernel of unit size. In other works, a mean of 0.5 is the center of the kernel size, while a standard deviation of 0.25 is a quarter of it. When `tensor` is provided (a 1D Tensor), the `size` is ignored. It is used to store the returned gaussian kernel. Note that arguments can also be specified as key-value arguments (in a table). ### [res] image.laplacian([size, sigma, amplitude, normalize, [...]]) ### Returns a 2D [Laplacian](https://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian) kernel of size `height x width`. When used in a 2D convolution, the Laplacian of an image highlights regions of rapid intensity change and is therefore often used for edge detection (ref.: [Laplacian/Laplacian of Gaussian](http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm)). Optional arguments `[...]` expand to `width`, `height`, `sigma_horz`, `sigma_vert`, `mean_horz`, `mean_vert`. The default value of `height` and `width` is `size`, where the latter has a default value of 3. The amplitude of the Laplacian (its maximum value) is `amplitude`. The default is 1. When `normalize=true`, the kernel is normalized to have a sum of 1. This overrides the `amplitude` argument. The default is `false`. The default value of the horizontal and vertical standard deviation `sigma_horz` and `sigma_vert` of the Laplacian kernel is `sigma`, where the latter has a default value of 0.25. The default values for the corresponding means `mean_horz` and `mean_vert` are 0.5. Both the standard deviations and means are relative to kernels of unit width and height where the top-left corner is the origin. In other works, a mean of 0.5 is the center of the kernel size, while a standard deviation of 0.25 is a quarter of it. ### [res] image.colormap(nColor) ### Creates an optimally-spaced RGB color mapping of `nColor` colors. Note that the mapping is obtained by generating the colors around the HSV wheel, varying the Hue component. The returned `res` Tensor has size `nColor x 3`. ### [res] image.jetColormap(nColor) ### Creates a jet (blue to red) RGB color mapping of `nColor` colors. The returned `res` Tensor has size `nColor x 3`. font.c000066400000000000000000000225511307617767500122200ustar00rootroot00000000000000/* Software License Agreement (BSD License) Copyright (c) 2012 Adafruit Industries. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Borrowed from https://github.com/adafruit/Adafruit-GFX-Library // Standard ASCII 5x7 font static const unsigned char image_ada_font[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, 0x18, 0x3C, 0x18, 0x00, 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, 0x18, 0x24, 0x18, 0x00, 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E, 0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, 0x40, 0x7F, 0x05, 0x25, 0x3F, 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, 0x66, 0x89, 0x95, 0x6A, 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49, 0x56, 0x20, 0x50, 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x80, 0x70, 0x30, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x72, 0x49, 0x49, 0x49, 0x46, 0x21, 0x41, 0x49, 0x4D, 0x33, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07, 0x36, 0x49, 0x49, 0x49, 0x36, 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32, 0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 0x03, 0x04, 0x78, 0x04, 0x03, 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x41, 0x7F, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, 0x7F, 0x28, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28, 0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D, 0x40, 0x00, 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, 0x7C, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x18, 0x24, 0x24, 0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24, 0x04, 0x04, 0x3F, 0x44, 0x24, 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x1E, 0xA1, 0xA1, 0x61, 0x12, 0x3A, 0x40, 0x40, 0x20, 0x7A, 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41, 0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut 0x21, 0x55, 0x54, 0x78, 0x40, 0x20, 0x54, 0x55, 0x79, 0x40, 0x0C, 0x1E, 0x52, 0x72, 0x12, 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54, 0x59, 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, 0x00, 0x02, 0x45, 0x7D, 0x42, 0x00, 0x01, 0x45, 0x7C, 0x40, 0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut 0xF0, 0x28, 0x25, 0x28, 0xF0, 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54, 0x54, 0x7C, 0x54, 0x7C, 0x0A, 0x09, 0x7F, 0x49, 0x32, 0x49, 0x49, 0x49, 0x32, 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut 0x32, 0x4A, 0x48, 0x48, 0x30, 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42, 0x40, 0x20, 0x78, 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut 0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, 0x48, 0x7E, 0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 0xFF, 0x09, 0x29, 0xF6, 0x20, 0xC0, 0x88, 0x7E, 0x09, 0x03, 0x20, 0x54, 0x54, 0x79, 0x41, 0x00, 0x00, 0x44, 0x7D, 0x41, 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40, 0x22, 0x7A, 0x00, 0x7A, 0x0A, 0x0A, 0x72, 0x7D, 0x0D, 0x19, 0x31, 0x7D, 0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, 0x30, 0x48, 0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A, 0x14, 0x08, 0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0x10, 0xFF, 0x00, 0x14, 0x14, 0x14, 0xFF, 0x00, 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0x1F, 0x00, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x14, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17, 0x14, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0xFF, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x38, 0x44, 0x44, 0x38, 0x44, 0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta 0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, 0x63, 0x55, 0x49, 0x41, 0x63, 0x38, 0x44, 0x44, 0x3C, 0x04, 0x40, 0x7E, 0x20, 0x1E, 0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 0x4C, 0x72, 0x01, 0x72, 0x4C, 0x30, 0x4A, 0x4D, 0x4D, 0x30, 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D, 0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x44, 0x44, 0x5F, 0x44, 0x44, 0x40, 0x51, 0x4A, 0x44, 0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0, 0x80, 0xFF, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x6B, 0x08, 0x36, 0x12, 0x36, 0x24, 0x36, 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, 0x00, 0x1F, 0x01, 0x01, 0x1E, 0x00, 0x19, 0x1D, 0x17, 0x12, 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP }; generic/000077500000000000000000000000001307617767500125155ustar00rootroot00000000000000generic/image.c000077500000000000000000002246511307617767500137600ustar00rootroot00000000000000#ifndef TH_GENERIC_FILE #define TH_GENERIC_FILE "generic/image.c" #else #undef MAX #define MAX(a,b) ( ((a)>(b)) ? (a) : (b) ) #undef MIN #define MIN(a,b) ( ((a)<(b)) ? (a) : (b) ) #undef TAPI #define TAPI __declspec(dllimport) #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #undef temp_t #if defined(TH_REAL_IS_FLOAT) || defined(TH_REAL_IS_DOUBLE) #define temp_t real #else #define temp_t float #endif static inline real image_(FromIntermediate)(temp_t x) { #ifdef TH_REAL_IS_BYTE x += 0.5; if( x <= 0 ) return 0; if( x >= 255 ) return 255; #endif return x; } static void image_(Main_op_validate)( lua_State *L, THTensor *Tsrc, THTensor *Tdst){ long src_depth = 1; long dst_depth = 1; luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional"); if(Tdst->nDimension == 3) dst_depth = Tdst->size[0]; if(Tsrc->nDimension == 3) src_depth = Tsrc->size[0]; if( (Tdst->nDimension==3 && ( src_depth!=dst_depth)) || (Tdst->nDimension!=Tsrc->nDimension) ) luaL_error(L, "image.scale: src and dst depths do not match"); if( Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.scale: src and dst depths do not match"); } static long image_(Main_op_stride)( THTensor *T,int i){ if (T->nDimension == 2) { if (i == 0) return 0; else return T->stride[i-1]; } return T->stride[i]; } static long image_(Main_op_depth)( THTensor *T){ if(T->nDimension == 3) return T->size[0]; /* rgb or rgba */ return 1; /* greyscale */ } static void image_(Main_scaleLinear_rowcol)(THTensor *Tsrc, THTensor *Tdst, long src_start, long dst_start, long src_stride, long dst_stride, long src_len, long dst_len ) { real *src= THTensor_(data)(Tsrc); real *dst= THTensor_(data)(Tdst); if ( dst_len > src_len ){ long di; float si_f; long si_i; float scale = (float)(src_len - 1) / (dst_len - 1); if ( src_len == 1 ) { for( di = 0; di < dst_len - 1; di++ ) { long dst_pos = dst_start + di*dst_stride; dst[dst_pos] = src[ src_start ]; } } else { for( di = 0; di < dst_len - 1; di++ ) { long dst_pos = dst_start + di*dst_stride; si_f = di * scale; si_i = (long)si_f; si_f -= si_i; dst[dst_pos] = image_(FromIntermediate)( (1 - si_f) * src[ src_start + si_i * src_stride ] + si_f * src[ src_start + (si_i + 1) * src_stride ]); } } dst[ dst_start + (dst_len - 1) * dst_stride ] = src[ src_start + (src_len - 1) * src_stride ]; } else if ( dst_len < src_len ) { long di; long si0_i = 0; float si0_f = 0; long si1_i; float si1_f; long si; float scale = (float)src_len / dst_len; float acc, n; for( di = 0; di < dst_len; di++ ) { si1_f = (di + 1) * scale; si1_i = (long)si1_f; si1_f -= si1_i; acc = (1 - si0_f) * src[ src_start + si0_i * src_stride ]; n = 1 - si0_f; for( si = si0_i + 1; si < si1_i; si++ ) { acc += src[ src_start + si * src_stride ]; n += 1; } if( si1_i < src_len ) { acc += si1_f * src[ src_start + si1_i*src_stride ]; n += si1_f; } dst[ dst_start + di*dst_stride ] = image_(FromIntermediate)(acc / n); si0_i = si1_i; si0_f = si1_f; } } else { long i; for( i = 0; i < dst_len; i++ ) dst[ dst_start + i*dst_stride ] = src[ src_start + i*src_stride ]; } } static inline temp_t image_(Main_cubicInterpolate)(temp_t p0, temp_t p1, temp_t p2, temp_t p3, temp_t x) { temp_t a0 = p1; temp_t a1 = p2 - p0; temp_t a2 = 2 * p0 - 5 * p1 + 4 * p2 - p3; temp_t a3 = 3 * (p1 - p2) + p3 - p0; return a0 + 0.5 * x * (a1 + x * (a2 + x * a3)); } static void image_(Main_scaleCubic_rowcol)(THTensor *Tsrc, THTensor *Tdst, long src_start, long dst_start, long src_stride, long dst_stride, long src_len, long dst_len ) { real *src= THTensor_(data)(Tsrc); real *dst= THTensor_(data)(Tdst); if ( dst_len == src_len ){ long i; for( i = 0; i < dst_len; i++ ) dst[ dst_start + i*dst_stride ] = src[ src_start + i*src_stride ]; } else if ( src_len == 1 ) { long i; for( i = 0; i < dst_len - 1; i++ ) { long dst_pos = dst_start + i*dst_stride; dst[dst_pos] = src[ src_start ]; } } else { long di; float si_f; long si_i; float scale; if (dst_len == 1) scale = (float)(src_len - 1); else scale = (float)(src_len - 1) / (dst_len - 1); for( di = 0; di < dst_len - 1; di++ ) { long dst_pos = dst_start + di*dst_stride; si_f = di * scale; si_i = (long)si_f; si_f -= si_i; temp_t p0; temp_t p1 = src[ src_start + si_i * src_stride ]; temp_t p2 = src[ src_start + (si_i + 1) * src_stride ]; temp_t p3; if (si_i > 0) { p0 = src[ src_start + (si_i - 1) * src_stride ]; } else { p0 = 2 * p1 - p2; } if (si_i + 2 < src_len) { p3 = src[ src_start + (si_i + 2) * src_stride ]; } else { p3 = 2 * p2 - p1; } temp_t value = image_(Main_cubicInterpolate)(p0, p1, p2, p3, si_f); dst[dst_pos] = image_(FromIntermediate)(value); } dst[ dst_start + (dst_len - 1) * dst_stride ] = src[ src_start + (src_len - 1) * src_stride ]; } } static int image_(Main_scaleBilinear)(lua_State *L) { THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor); THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor); THTensor *Ttmp; long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height; long src_stride0, src_stride1, src_stride2, src_width, src_height; long tmp_stride0, tmp_stride1, tmp_stride2, tmp_width, tmp_height; long i, j, k; image_(Main_op_validate)(L, Tsrc,Tdst); int ndims; if (Tdst->nDimension == 3) ndims = 3; else ndims = 2; Ttmp = THTensor_(newWithSize2d)(Tsrc->size[ndims-2], Tdst->size[ndims-1]); dst_stride0= image_(Main_op_stride)(Tdst,0); dst_stride1= image_(Main_op_stride)(Tdst,1); dst_stride2= image_(Main_op_stride)(Tdst,2); src_stride0= image_(Main_op_stride)(Tsrc,0); src_stride1= image_(Main_op_stride)(Tsrc,1); src_stride2= image_(Main_op_stride)(Tsrc,2); tmp_stride0= image_(Main_op_stride)(Ttmp,0); tmp_stride1= image_(Main_op_stride)(Ttmp,1); tmp_stride2= image_(Main_op_stride)(Ttmp,2); dst_width= Tdst->size[ndims-1]; dst_height= Tdst->size[ndims-2]; src_width= Tsrc->size[ndims-1]; src_height= Tsrc->size[ndims-2]; tmp_width= Ttmp->size[1]; tmp_height= Ttmp->size[0]; for(k=0;knDimension == 3) ndims = 3; else ndims = 2; Ttmp = THTensor_(newWithSize2d)(Tsrc->size[ndims-2], Tdst->size[ndims-1]); dst_stride0= image_(Main_op_stride)(Tdst,0); dst_stride1= image_(Main_op_stride)(Tdst,1); dst_stride2= image_(Main_op_stride)(Tdst,2); src_stride0= image_(Main_op_stride)(Tsrc,0); src_stride1= image_(Main_op_stride)(Tsrc,1); src_stride2= image_(Main_op_stride)(Tsrc,2); tmp_stride0= image_(Main_op_stride)(Ttmp,0); tmp_stride1= image_(Main_op_stride)(Ttmp,1); tmp_stride2= image_(Main_op_stride)(Ttmp,2); dst_width= Tdst->size[ndims-1]; dst_height= Tdst->size[ndims-2]; src_width= Tsrc->size[ndims-1]; src_height= Tsrc->size[ndims-2]; tmp_width= Ttmp->size[1]; tmp_height= Ttmp->size[0]; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "image.scale: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "image.scale: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( (Tdst->nDimension==3 && ( src_depth!=dst_depth)) || (Tdst->nDimension!=Tsrc->nDimension) ) { printf("image.scale:%d,%d,%ld,%ld\n",Tsrc->nDimension,Tdst->nDimension,src_depth,dst_depth); luaL_error(L, "image.scale: src and dst depths do not match"); } if( Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.scale: src and dst depths do not match"); /* printf("%d,%d -> %d,%d\n",src_width,src_height,dst_width,dst_height); */ scx=((float)src_width)/((float)dst_width); scy=((float)src_height)/((float)dst_height); #pragma omp parallel for private(j, i, k) for(j = 0; j < dst_height; j++) { for(i = 0; i < dst_width; i++) { float val = 0.0; long ii=(long) (((float)i)*scx); long jj=(long) (((float)j)*scy); if(ii>src_width-1) ii=src_width-1; if(jj>src_height-1) jj=src_height-1; if(Tsrc->nDimension==2) { val=src[ii*src_stride2+jj*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); if (dst == src) { luaL_error(L, "image.rotate: in-place rotate not supported"); } dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.rotate: src and dst depths do not match"); if( (Tsrc->nDimension!=Tdst->nDimension) ) luaL_error(L, "image.rotate: src and dst depths do not match"); xc = (src_width-1)/2.0; yc = (src_height-1)/2.0; sin_theta = sin(theta); cos_theta = cos(theta); for(j = 0; j < dst_height; j++) { jd=j; for(i = 0; i < dst_width; i++) { float val = -1; id= i; ii = (long) round(cos_theta*(id-xc) - sin_theta*(jd-yc) + xc); jj = (long) round(cos_theta*(jd-yc) + sin_theta*(id-xc) + yc); /* rotated corners are blank */ if(ii>src_width-1) val=0; if(jj>src_height-1) val=0; if(ii<0) val=0; if(jj<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val=src[ii*src_stride2+jj*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); if (dst == src) { luaL_error(L, "image.rotate: in-place rotate not supported"); } dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.rotate: src and dst depths do not match"); if( (Tsrc->nDimension!=Tdst->nDimension) ) luaL_error(L, "image.rotate: src and dst depths do not match"); xc = (src_width-1)/2.0; yc = (src_height-1)/2.0; for(j = 0; j < dst_height; j++) { jd=j; for(i = 0; i < dst_width; i++) { float val = -1; temp_t ri, rj, wi, wj; id= i; ri = cos(theta)*(id-xc)-sin(theta)*(jd-yc); rj = cos(theta)*(jd-yc)+sin(theta)*(id-xc); ii_0 = (long)floor(ri+xc); ii_1 = ii_0 + 1; jj_0 = (long)floor(rj+yc); jj_1 = jj_0 + 1; wi = ri+xc-ii_0; wj = rj+yc-jj_0; /* default to the closest value when interpolating on image boundaries (either image pixel or 0) */ if(ii_1==src_width && wi<0.5) ii_1 = ii_0; else if(ii_1>=src_width) val=0; if(jj_1==src_height && wj<0.5) jj_1 = jj_0; else if(jj_1>=src_height) val=0; if(ii_0==-1 && wi>0.5) ii_0 = ii_1; else if(ii_0<0) val=0; if(jj_0==-1 && wj>0.5) jj_0 = jj_1; else if(jj_0<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1] + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1] + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1] + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } if( (Tsrc->nDimension!=Tdst->nDimension) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } // compute maximum distance midY = (float) src_height / 2.0; midX = (float) src_width / 2.0; if(doFull == 1) { m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0; } else { m = (src_width < src_height) ? midX : midY; } // loop to fill polar image for(j = 0; j < dst_height; j++) { // orientation loop jd = (float) j; a = (2 * M_PI * jd) / (float) dst_height; // current angle for(i = 0; i < dst_width; i++) { // radius loop float val = -1; id = (float) i; r = (m * id) / (float) dst_width; // current distance jj = (long) floor( r * cos(a) + midY); // y-location in source image ii = (long) floor(-r * sin(a) + midX); // x-location in source image if(ii>src_width-1) val=0; if(jj>src_height-1) val=0; if(ii<0) val=0; if(jj<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val=src[ii*src_stride2+jj*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } if( (Tsrc->nDimension!=Tdst->nDimension) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } // compute maximum distance midY = (float) src_height / 2.0; midX = (float) src_width / 2.0; if(doFull == 1) { m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0; } else { m = (src_width < src_height) ? midX : midY; } // loop to fill polar image for(j = 0; j < dst_height; j++) { // orientation loop jd = (float) j; a = (2 * M_PI * jd) / (float) dst_height; // current angle for(i = 0; i < dst_width; i++) { // radius loop float val = -1; temp_t ri, rj, wi, wj; id = (float) i; r = (m * id) / (float) dst_width; // current distance rj = r * cos(a) + midY; // y-location in source image ri = -r * sin(a) + midX; // x-location in source image ii_0=(long)floor(ri); ii_1=ii_0 + 1; jj_0=(long)floor(rj); jj_1=jj_0 + 1; wi = ri - ii_0; wj = rj - jj_0; // switch to nearest interpolation when bilinear is impossible if(ii_1>src_width-1 || jj_1>src_height-1 || ii_0<0 || jj_0<0) { if(ii_0>src_width-1) val=0; if(jj_0>src_height-1) val=0; if(ii_0<0) val=0; if(jj_0<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val=src[ii_0*src_stride2+jj_0*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2) { if(val==-1) val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1] + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1] + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1] + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } if( (Tsrc->nDimension!=Tdst->nDimension) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } // compute maximum distance midY = (float) src_height / 2.0; midX = (float) src_width / 2.0; if(doFull == 1) { m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0; } else { m = (src_width < src_height) ? midX : midY; } // loop to fill polar image fw = log(m) / (float) dst_width; for(j = 0; j < dst_height; j++) { // orientation loop jd = (float) j; a = (2 * M_PI * jd) / (float) dst_height; // current angle for(i = 0; i < dst_width; i++) { // radius loop float val = -1; id = (float) i; r = exp(id * fw); jj = (long) floor( r * cos(a) + midY); // y-location in source image ii = (long) floor(-r * sin(a) + midX); // x-location in source image if(ii>src_width-1) val=0; if(jj>src_height-1) val=0; if(ii<0) val=0; if(jj<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val=src[ii*src_stride2+jj*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } if( (Tsrc->nDimension!=Tdst->nDimension) ) { luaL_error(L, "image.polar: src and dst depths do not match"); } // compute maximum distance midY = (float) src_height / 2.0; midX = (float) src_width / 2.0; if(doFull == 1) { m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0; } else { m = (src_width < src_height) ? midX : midY; } // loop to fill polar image fw = log(m) / (float) dst_width; for(j = 0; j < dst_height; j++) { // orientation loop jd = (float) j; a = (2 * M_PI * jd) / (float) dst_height; // current angle for(i = 0; i < dst_width; i++) { // radius loop float val = -1; float ri, rj, wi, wj; id = (float) i; r = exp(id * fw); rj = r * cos(a) + midY; // y-location in source image ri = -r * sin(a) + midX; // x-location in source image ii_0=(long)floor(ri); ii_1=ii_0 + 1; jj_0=(long)floor(rj); jj_1=jj_0 + 1; wi = ri - ii_0; wj = rj - jj_0; // switch to nearest interpolation when bilinear is impossible if(ii_1>src_width-1 || jj_1>src_height-1 || ii_0<0 || jj_0<0) { if(ii_0>src_width-1) val=0; if(jj_0>src_height-1) val=0; if(ii_0<0) val=0; if(jj_0<0) val=0; if(Tsrc->nDimension==2) { if(val==-1) val=src[ii_0*src_stride2+jj_0*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2) { if(val==-1) val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1] + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1] + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1] + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { int do_copy=0; if(val==-1) do_copy=1; for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 0; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 0; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 0; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 0; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( startx<0 || starty<0 || (startx+dst_width>src_width) || (starty+dst_height>src_height)) luaL_error(L, "image.crop: crop goes outside bounds of src"); if( Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.crop: src and dst depths do not match"); for(j = 0; j < dst_height; j++) { for(i = 0; i < dst_width; i++) { float val = 0.0; long ii=i+startx; long jj=j+starty; if(Tsrc->nDimension==2) { val=src[ii*src_stride2+jj*src_stride1]; dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val); } else { for(k=0;knDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional"); luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional"); src= THTensor_(data)(Tsrc); dst= THTensor_(data)(Tdst); dst_stride0 = 1; dst_stride1 = Tdst->stride[Tdst->nDimension-2]; dst_stride2 = Tdst->stride[Tdst->nDimension-1]; dst_depth = 1; dst_height = Tdst->size[Tdst->nDimension-2]; dst_width = Tdst->size[Tdst->nDimension-1]; if(Tdst->nDimension == 3) { dst_stride0 = Tdst->stride[0]; dst_depth = Tdst->size[0]; } src_stride0 = 1; src_stride1 = Tsrc->stride[Tsrc->nDimension-2]; src_stride2 = Tsrc->stride[Tsrc->nDimension-1]; src_depth = 1; src_height = Tsrc->size[Tsrc->nDimension-2]; src_width = Tsrc->size[Tsrc->nDimension-1]; if(Tsrc->nDimension == 3) { src_stride0 = Tsrc->stride[0]; src_depth = Tsrc->size[0]; } if( Tdst->nDimension==3 && ( src_depth!=dst_depth) ) luaL_error(L, "image.translate: src and dst depths do not match"); for(j = 0; j < src_height; j++) { for(i = 0; i < src_width; i++) { long ii=i+shiftx; long jj=j+shifty; // Check it's within destination bounds, else crop if(ii=0 && jj>=0) { for(k=0;k 1) ? 1 : *input_data;) #endif return 1; } /* * Converts an RGB color value to HSL. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes r, g, and b are contained in the set [0, 1] and * returns h, s, and l in the set [0, 1]. */ int image_(Main_rgb2hsl)(lua_State *L) { THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor); THTensor *hsl = luaT_checkudata(L, 2, torch_Tensor); int y,x; temp_t r, g, b, h, s, l; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get Rgb r = THTensor_(get3d)(rgb, 0, y, x); g = THTensor_(get3d)(rgb, 1, y, x); b = THTensor_(get3d)(rgb, 2, y, x); #ifdef TH_REAL_IS_BYTE r /= 255; g /= 255; b /= 255; #endif temp_t mx = max(max(r, g), b); temp_t mn = min(min(r, g), b); if(mx == mn) { h = 0; // achromatic s = 0; l = mx; } else { temp_t d = mx - mn; if (mx == r) { h = (g - b) / d + (g < b ? 6 : 0); } else if (mx == g) { h = (b - r) / d + 2; } else { h = (r - g) / d + 4; } h /= 6; l = (mx + mn) / 2; s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn); } // set hsl #ifdef TH_REAL_IS_BYTE h *= 255; s *= 255; l *= 255; #endif THTensor_(set3d)(hsl, 0, y, x, image_(FromIntermediate)(h)); THTensor_(set3d)(hsl, 1, y, x, image_(FromIntermediate)(s)); THTensor_(set3d)(hsl, 2, y, x, image_(FromIntermediate)(l)); } } return 0; } // helper static inline temp_t image_(hue2rgb)(temp_t p, temp_t q, temp_t t) { if (t < 0.) t += 1; if (t > 1.) t -= 1; if (t < 1./6) return p + (q - p) * 6. * t; else if (t < 1./2) return q; else if (t < 2./3) return p + (q - p) * (2./3 - t) * 6.; else return p; } /* * Converts an HSL color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 1]. */ int image_(Main_hsl2rgb)(lua_State *L) { THTensor *hsl = luaT_checkudata(L, 1, torch_Tensor); THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor); int y,x; temp_t r, g, b, h, s, l; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get hsl h = THTensor_(get3d)(hsl, 0, y, x); s = THTensor_(get3d)(hsl, 1, y, x); l = THTensor_(get3d)(hsl, 2, y, x); #ifdef TH_REAL_IS_BYTE h /= 255; s /= 255; l /= 255; #endif if(s == 0) { // achromatic r = l; g = l; b = l; } else { temp_t q = (l < 0.5) ? (l * (1 + s)) : (l + s - l * s); temp_t p = 2 * l - q; temp_t hr = h + 1./3; temp_t hg = h; temp_t hb = h - 1./3; r = image_(hue2rgb)(p, q, hr); g = image_(hue2rgb)(p, q, hg); b = image_(hue2rgb)(p, q, hb); } // set rgb #ifdef TH_REAL_IS_BYTE r *= 255; g *= 255; b *= 255; #endif THTensor_(set3d)(rgb, 0, y, x, image_(FromIntermediate)(r)); THTensor_(set3d)(rgb, 1, y, x, image_(FromIntermediate)(g)); THTensor_(set3d)(rgb, 2, y, x, image_(FromIntermediate)(b)); } } return 0; } /* * Converts an RGB color value to HSV. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes r, g, and b are contained in the set [0, 1] and * returns h, s, and v in the set [0, 1]. */ int image_(Main_rgb2hsv)(lua_State *L) { THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor); THTensor *hsv = luaT_checkudata(L, 2, torch_Tensor); int y, x; temp_t r, g, b, h, s, v; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get Rgb r = THTensor_(get3d)(rgb, 0, y, x); g = THTensor_(get3d)(rgb, 1, y, x); b = THTensor_(get3d)(rgb, 2, y, x); #ifdef TH_REAL_IS_BYTE r /= 255; g /= 255; b /= 255; #endif temp_t mx = max(max(r, g), b); temp_t mn = min(min(r, g), b); if(mx == mn) { // achromatic h = 0; s = 0; v = mx; } else { temp_t d = mx - mn; if (mx == r) { h = (g - b) / d + (g < b ? 6 : 0); } else if (mx == g) { h = (b - r) / d + 2; } else { h = (r - g) / d + 4; } h /= 6; s = d / mx; v = mx; } // set hsv #ifdef TH_REAL_IS_BYTE h *= 255; s *= 255; v *= 255; #endif THTensor_(set3d)(hsv, 0, y, x, image_(FromIntermediate)(h)); THTensor_(set3d)(hsv, 1, y, x, image_(FromIntermediate)(s)); THTensor_(set3d)(hsv, 2, y, x, image_(FromIntermediate)(v)); } } return 0; } /* * Converts an HSV color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 1]. */ int image_(Main_hsv2rgb)(lua_State *L) { THTensor *hsv = luaT_checkudata(L, 1, torch_Tensor); THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor); int y, x; temp_t r, g, b, h, s, v; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get hsv h = THTensor_(get3d)(hsv, 0, y, x); s = THTensor_(get3d)(hsv, 1, y, x); v = THTensor_(get3d)(hsv, 2, y, x); #ifdef TH_REAL_IS_BYTE h /= 255; s /= 255; v /= 255; #endif int i = floor(h*6.); temp_t f = h*6-i; temp_t p = v*(1-s); temp_t q = v*(1-f*s); temp_t t = v*(1-(1-f)*s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; default: r=0; g = 0, b = 0; break; } // set rgb #ifdef TH_REAL_IS_BYTE r *= 255; g *= 255; b *= 255; #endif THTensor_(set3d)(rgb, 0, y, x, image_(FromIntermediate)(r)); THTensor_(set3d)(rgb, 1, y, x, image_(FromIntermediate)(g)); THTensor_(set3d)(rgb, 2, y, x, image_(FromIntermediate)(b)); } } return 0; } #ifndef TH_REAL_IS_BYTE /* * Convert an sRGB color channel to a linear sRGB color channel. */ static inline real image_(gamma_expand_sRGB)(real nonlinear) { return (nonlinear <= 0.04045) ? (nonlinear / 12.92) : (pow((nonlinear+0.055)/1.055, 2.4)); } /* * Convert a linear sRGB color channel to a sRGB color channel. */ static inline real image_(gamma_compress_sRGB)(real linear) { return (linear <= 0.0031308) ? (12.92 * linear) : (1.055 * pow(linear, 1.0/2.4) - 0.055); } /* * Converts an sRGB color value to LAB. * Based on http://www.brucelindbloom.com/index.html?Equations.html. * Assumes r, g, and b are contained in the set [0, 1]. * LAB output is NOT restricted to [0, 1]! */ int image_(Main_rgb2lab)(lua_State *L) { THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor); THTensor *lab = luaT_checkudata(L, 2, torch_Tensor); // CIE Standard double epsilon = 216.0/24389.0; double k = 24389.0/27.0; // D65 white point double xn = 0.950456; double zn = 1.088754; int y,x; real r,g,b,l,a,_b; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get RGB r = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 0, y, x)); g = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 1, y, x)); b = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 2, y, x)); // sRGB to XYZ double X = 0.412453 * r + 0.357580 * g + 0.180423 * b; double Y = 0.212671 * r + 0.715160 * g + 0.072169 * b; double Z = 0.019334 * r + 0.119193 * g + 0.950227 * b; // normalize for D65 white point X /= xn; Z /= zn; // XYZ normalized to CIE Lab double fx = X > epsilon ? pow(X, 1/3.0) : (k * X + 16)/116; double fy = Y > epsilon ? pow(Y, 1/3.0) : (k * Y + 16)/116; double fz = Z > epsilon ? pow(Z, 1/3.0) : (k * Z + 16)/116; l = 116 * fy - 16; a = 500 * (fx - fy); _b = 200 * (fy - fz); // set lab THTensor_(set3d)(lab, 0, y, x, l); THTensor_(set3d)(lab, 1, y, x, a); THTensor_(set3d)(lab, 2, y, x, _b); } } return 0; } /* * Converts an LAB color value to sRGB. * Based on http://www.brucelindbloom.com/index.html?Equations.html. * returns r, g, and b in the set [0, 1]. */ int image_(Main_lab2rgb)(lua_State *L) { THTensor *lab = luaT_checkudata(L, 1, torch_Tensor); THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor); int y,x; real r,g,b,l,a,_b; // CIE Standard double epsilon = 216.0/24389.0; double k = 24389.0/27.0; // D65 white point double xn = 0.950456; double zn = 1.088754; for (y=0; ysize[1]; y++) { for (x=0; xsize[2]; x++) { // get lab l = THTensor_(get3d)(lab, 0, y, x); a = THTensor_(get3d)(lab, 1, y, x); _b = THTensor_(get3d)(lab, 2, y, x); // LAB to XYZ double fy = (l + 16) / 116; double fz = fy - _b / 200; double fx = (a / 500) + fy; double X = pow(fx, 3); if (X <= epsilon) X = (116 * fx - 16) / k; double Y = l > (k * epsilon) ? pow((l + 16) / 116, 3) : l/k; double Z = pow(fz, 3); if (Z <= epsilon) Z = (116 * fz - 16) / k; X *= xn; Z *= zn; // XYZ to sRGB r = 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z; g = -0.9692660 * X + 1.8760108 * Y + 0.0415560 * Z; b = 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z; // set rgb THTensor_(set3d)(rgb, 0, y, x, image_(gamma_compress_sRGB(r))); THTensor_(set3d)(rgb, 1, y, x, image_(gamma_compress_sRGB(g))); THTensor_(set3d)(rgb, 2, y, x, image_(gamma_compress_sRGB(b))); } } return 0; } #else int image_(Main_rgb2lab)(lua_State *L) { return luaL_error(L, "image.rgb2lab: not supported for torch.ByteTensor"); } int image_(Main_lab2rgb)(lua_State *L) { return luaL_error(L, "image.lab2rgb: not supported for torch.ByteTensor"); } #endif // TH_REAL_IS_BYTE /* Vertically flip an image */ int image_(Main_vflip)(lua_State *L) { THTensor *dst = luaT_checkudata(L, 1, torch_Tensor); THTensor *src = luaT_checkudata(L, 2, torch_Tensor); int width = dst->size[2]; int height = dst->size[1]; int channels = dst->size[0]; long *is = src->stride; long *os = dst->stride; // get raw pointers real *dst_data = THTensor_(data)(dst); real *src_data = THTensor_(data)(src); long k, x, y; if (dst_data != src_data) { /* not in-place. * this branch could be removed by first duplicating the src into dst then doing inplace */ #pragma omp parallel for private(k, x, y) for(k=0; k> 1; for(k=0; ksize[2]; int height = dst->size[1]; int channels = dst->size[0]; long *is = src->stride; long *os = dst->stride; // get raw pointers real *dst_data = THTensor_(data)(dst); real *src_data = THTensor_(data)(src); long k, x, y; if (dst_data != src_data) { /* not in-place. * this branch could be removed by first duplicating the src into dst then doing inplace */ #pragma omp parallel for private(k, x, y) for(k=0; k> 1; for(k=0; knDimension != 5) || (src->nDimension != 5)) { luaL_error(L, "image.flip: expected 5 dimensions for src and dst"); } if (flip_dim < 1 || flip_dim > dst->nDimension || flip_dim > 5) { luaL_error(L, "image.flip: flip_dim out of bounds"); } flip_dim--; // Make it zero indexed // get raw pointers real *dst_data = THTensor_(data)(dst); real *src_data = THTensor_(data)(src); if (dst_data == src_data) { luaL_error(L, "image.flip: in-place flip not supported"); } long size0 = dst->size[0]; long size1 = dst->size[1]; long size2 = dst->size[2]; long size3 = dst->size[3]; long size4 = dst->size[4]; if (src->size[0] != size0 || src->size[1] != size1 || src->size[2] != size2 || src->size[3] != size3 || src->size[4] != size4) { luaL_error(L, "image.flip: src and dst are not the same size"); } long *is = src->stride; long *os = dst->stride; long x, y, z, d, t, isrc, idst = 0; for (t = 0; t < size0; t++) { for (d = 0; d < size1; d++) { for (z = 0; z < size2; z++) { for (y = 0; y < size3; y++) { for (x = 0; x < size4; x++) { isrc = t*is[0] + d*is[1] + z*is[2] + y*is[3] + x*is[4]; // The big switch statement here looks ugly, however on my machine // gcc compiles it to a skip list, so it should be fast. switch (flip_dim) { case 0: idst = (size0 - t - 1)*os[0] + d*os[1] + z*os[2] + y*os[3] + x*os[4]; break; case 1: idst = t*os[0] + (size1 - d - 1)*os[1] + z*os[2] + y*os[3] + x*os[4]; break; case 2: idst = t*os[0] + d*os[1] + (size2 - z - 1)*os[2] + y*os[3] + x*os[4]; break; case 3: idst = t*os[0] + d*os[1] + z*os[2] + (size3 - y - 1)*os[3] + x*os[4]; break; case 4: idst = t*os[0] + d*os[1] + z*os[2] + y*os[3] + (size4 - x - 1)*os[4]; break; } dst_data[ idst ] = src_data[ isrc ]; } } } } } return 0; } static inline void image_(Main_bicubicInterpolate)( real* src, long* is, long* size, temp_t ix, temp_t iy, real* dst, long *os, real pad_value, int bounds_check) { int i, j, k; temp_t arr[4], p[4]; // Calculate fractional and integer components long x_pix = floor(ix); long y_pix = floor(iy); temp_t dx = ix - x_pix; temp_t dy = iy - y_pix; for (k=0; k= size[1] || u < 0 || u >= size[2])) { p[j] = pad_value; } else { p[j] = data[u * is[2]]; } } arr[i] = image_(Main_cubicInterpolate)(p[0], p[1], p[2], p[3], dx); } temp_t value = image_(Main_cubicInterpolate)(arr[0], arr[1], arr[2], arr[3], dy); dst[k * os[0]] = image_(FromIntermediate)(value); } } /* * Warps an image, according to an (x,y) flow field. The flow * field is in the space of the destination image, each vector * ponts to a source pixel in the original image. */ int image_(Main_warp)(lua_State *L) { THTensor *dst = luaT_checkudata(L, 1, torch_Tensor); THTensor *src = luaT_checkudata(L, 2, torch_Tensor); THTensor *flowfield = luaT_checkudata(L, 3, torch_Tensor); int mode = lua_tointeger(L, 4); int offset_mode = lua_toboolean(L, 5); int clamp_mode = lua_tointeger(L, 6); real pad_value = (real)lua_tonumber(L, 7); // dims int width = dst->size[2]; int height = dst->size[1]; int src_width = src->size[2]; int src_height = src->size[1]; int channels = dst->size[0]; long *is = src->stride; long *os = dst->stride; long *fs = flowfield->stride; // get raw pointers real *dst_data = THTensor_(data)(dst); real *src_data = THTensor_(data)(src); real *flow_data = THTensor_(data)(flowfield); // resample long k,x,y,v,u,i,j; #pragma omp parallel for private(k, x, y, v, u, i, j) for (y=0; y src_height - 1 || ix < 0 || ix > src_width - 1) { off_image = 1; } if (off_image == 1 && clamp_mode == 1) { // We're off the image and we're clamping the input image to 0 for (k=0; k= 1 && iy < src_height - 2 && ix >= 1 && ix < src_width - 2); real* dst = dst_data + y*os[1] + x*os[2]; if (edge) { image_(Main_bicubicInterpolate)(src_data, is, src->size, ix, iy, dst, os, pad_value, 1); } else { image_(Main_bicubicInterpolate)(src_data, is, src->size, ix, iy, dst, os, pad_value, 0); } } break; case 3: // Lanczos { // Note: Lanczos can be made fast if the resampling period is // constant... and therefore the Lu, Lv can be cached and reused. // However, unfortunately warp makes no assumptions about resampling // and so we need to perform the O(k^2) convolution on each pixel AND // we have to re-calculate the kernel for every pixel. // See wikipedia for more info. // It is however an extremely good approximation to to full sinc // interpolation (IIR) filter. // Another note is that the version here has been optimized using // pretty aggressive code flow and explicit inlining. It might not // be very readable (contact me, Jonathan Tompson, if it is not) // Calculate fractional and integer components long x_pix = floor(ix); long y_pix = floor(iy); // Precalculate the L(x) function evaluations in the u and v direction #define rad (3) // This is a tunable parameter: 2 to 3 is OK float Lu[2 * rad]; // L(x) for u direction float Lv[2 * rad]; // L(x) for v direction for (u=x_pix-rad+1, i=0; u<=x_pix+rad; u++, i++) { float du = ix - (float)u; // Lanczos kernel x value du = du < 0 ? -du : du; // prefer not to used std absf if (du < 0.000001f) { // TODO: Is there a real eps standard? Lu[i] = 1; } else if (du > (float)rad) { Lu[i] = 0; } else { Lu[i] = ((float)rad * sin((float)M_PI * du) * sin((float)M_PI * du / (float)rad)) / ((float)(M_PI * M_PI) * du * du); } } for (v=y_pix-rad+1, i=0; v<=y_pix+rad; v++, i++) { float dv = iy - (float)v; // Lanczos kernel x value dv = dv < 0 ? -dv : dv; // prefer not to used std absf if (dv < 0.000001f) { // TODO: Is there a real eps standard? Lv[i] = 1; } else if (dv > (float)rad) { Lv[i] = 0; } else { Lv[i] = ((float)rad * sin((float)M_PI * dv) * sin((float)M_PI * dv / (float)rad)) / ((float)(M_PI * M_PI) * dv * dv); } } float sum_weights = 0; for (u=0; u<2*rad; u++) { for (v=0; v<2*rad; v++) { sum_weights += (Lu[u] * Lv[v]); } } for (k=0; ksize[1]; long height = dst->size[0]; long *os = dst->stride; real *dst_data = THTensor_(data)(dst); temp_t amplitude = (temp_t)lua_tonumber(L, 2); int normalize = (int)lua_toboolean(L, 3); temp_t sigma_u = (temp_t)lua_tonumber(L, 4); temp_t sigma_v = (temp_t)lua_tonumber(L, 5); temp_t mean_u = (temp_t)lua_tonumber(L, 6) * width + 0.5; temp_t mean_v = (temp_t)lua_tonumber(L, 7) * height + 0.5; // Precalculate 1/(sigma*size) for speed (for some stupid reason the pragma // omp declaration prevents gcc from optimizing the inside loop on my macine: // verified by checking the assembly output) temp_t over_sigmau = 1.0 / (sigma_u * width); temp_t over_sigmav = 1.0 / (sigma_v * height); long v, u; temp_t du, dv; #pragma omp parallel for private(v, u, du, dv) for (v = 0; v < height; v++) { for (u = 0; u < width; u++) { du = (u + 1 - mean_u) * over_sigmau; dv = (v + 1 - mean_v) * over_sigmav; temp_t value = amplitude * exp(-0.5 * (du*du + dv*dv)); dst_data[ v*os[0] + u*os[1] ] = image_(FromIntermediate)(value); } } if (normalize) { temp_t sum = 0; // We could parallelize this, but it's more trouble than it's worth for(v = 0; v < height; v++) { for(u = 0; u < width; u++) { sum += dst_data[ v*os[0] + u*os[1] ]; } } temp_t one_over_sum = 1.0 / sum; #pragma omp parallel for private(v, u) for(v = 0; v < height; v++) { for(u = 0; u < width; u++) { dst_data[ v*os[0] + u*os[1] ] *= one_over_sum; } } } return 0; } /* * Borrowed from github.com/clementfarabet/lua---imgraph * with Clément's permission for implementing y2jet() */ int image_(Main_colorize)(lua_State *L) { // get args THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor); THTensor *input = (THTensor *)luaT_checkudata(L, 2, torch_Tensor); THTensor *colormap = (THTensor *)luaT_checkudata(L, 3, torch_Tensor); // dims long height = input->size[0]; long width = input->size[1]; // generate color map if not given int noColorMap = THTensor_(nElement)(colormap) == 0; if (noColorMap) { THTensor_(resize2d)(colormap, width*height, 3); THTensor_(fill)(colormap, -1); } // colormap channels int channels = colormap->size[1]; // generate output THTensor_(resize3d)(output, channels, height, width); int x,y,k; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { int id = THTensor_(get2d)(input, y, x); if (noColorMap) { for (k = 0; k < channels; k++) { temp_t value = (float)rand() / (float)RAND_MAX; #ifdef TH_REAL_IS_BYTE value *= 255; #endif THTensor_(set2d)(colormap, id, k, image_(FromIntermediate)(value)); } } for (k = 0; k < channels; k++) { real color = THTensor_(get2d)(colormap, id, k); THTensor_(set3d)(output, k, y, x, color); } } } // return nothing return 0; } int image_(Main_rgb2y)(lua_State *L) { THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor); THTensor *yim = luaT_checkudata(L, 2, torch_Tensor); luaL_argcheck(L, rgb->nDimension == 3, 1, "image.rgb2y: src not 3D"); luaL_argcheck(L, yim->nDimension == 2, 2, "image.rgb2y: dst not 2D"); luaL_argcheck(L, rgb->size[1] == yim->size[0], 2, "image.rgb2y: src and dst not of same height"); luaL_argcheck(L, rgb->size[2] == yim->size[1], 2, "image.rgb2y: src and dst not of same width"); int y, x; temp_t r, g, b, yc; const int height = rgb->size[1]; const int width = rgb->size[2]; for (y=0; ysize[0]; long height = output->size[1]; long width = output->size[2]; /* out of bounds condition, return without drawing */ if((x >= width) || // Clip right (y >= height) || // Clip bottom ((x + 6 * size - 1) < 0) || // Clip left ((y + 8 * size - 1) < 0)) // Clip top return; for(char i = 0; i < 6; i++ ) { unsigned char line; if (i < 5) { line = *(const unsigned char *)(image_ada_font+(c*5) + i); } else { line = 0x0; } for(char j = 0; j < 8; j++, line >>= 1) { if(line & 0x1) { if (size == 1) { image_(drawPixel)(output, y+j, x+i, cr, cg, cb); } else { for (int ii = x+(i*size); ii < x+(i*size) + size; ii++) { for (int jj = y+(j*size); jj < y+(j*size) + size; jj++) { image_(drawPixel)(output, jj, ii, cr, cg, cb); } } } } else if (bg_cr != -1 && bg_cg != -1 && bg_cb != -1) { if (size == 1) { image_(drawPixel)(output, y+j, x+i, bg_cr, bg_cg, bg_cb); } else { for (int ii = x+(i*size); ii < x+(i*size) + size; ii++) { for (int jj = y+(j*size); jj < y+(j*size) + size; jj++) { image_(drawPixel)(output, jj, ii, bg_cr, bg_cg, bg_cb); } } } } } } } int image_(Main_drawtext)(lua_State *L) { // get args THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor); const char* text = lua_tostring(L, 2); long x = luaL_checklong(L, 3); long y = luaL_checklong(L, 4); int size = luaL_checkint(L, 5); int cr = luaL_checkint(L, 6); int cg = luaL_checkint(L, 7); int cb = luaL_checkint(L, 8); int bg_cr = luaL_checkint(L, 9); int bg_cg = luaL_checkint(L, 10); int bg_cb = luaL_checkint(L, 11); int wrap = luaL_checkint(L, 12); long len = strlen(text); // dims long channels = output->size[0]; long height = output->size[1]; long width = output->size[2]; long cursor_y = y; long cursor_x = x; for (long cnt = 0; cnt < len; cnt++) { unsigned char c = text[cnt]; if(c == '\n') { cursor_y += size*8; cursor_x = x; } else if(c == '\r') { // skip em } else { if(wrap && ((cursor_x + size * 6) >= width)) { // Heading off edge? cursor_x = 0; // Reset x to zero cursor_y += size * 8; // Advance y one line } image_(drawChar)(output, cursor_x, cursor_y, c, size, cr, cg, cb, bg_cr, bg_cg, bg_cb); cursor_x += size * 6; } } return 0; } int image_(Main_drawRect)(lua_State *L) { THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor); long x1long = luaL_checklong(L, 2); long y1long = luaL_checklong(L, 3); long x2long = luaL_checklong(L, 4); long y2long = luaL_checklong(L, 5); int lineWidth = luaL_checkint(L, 6); int cr = luaL_checkint(L, 7); int cg = luaL_checkint(L, 8); int cb = luaL_checkint(L, 9); int loffset = lineWidth / 2 + 1; int uoffset = lineWidth - loffset - 1; int x1l = (int) MAX(0, x1long - loffset); int y1l = (int) MAX(0, y1long - loffset); int x1u = (int) MIN(output->size[2], x1long + uoffset + 1); int y1u = (int) MIN(output->size[1], y1long + uoffset + 1); int x2l = (int) MAX(0, x2long - loffset); int y2l = (int) MAX(0, y2long - loffset); int x2u = (int) MIN(output->size[2], x2long + uoffset + 1); int y2u = (int) MIN(output->size[1], y2long + uoffset + 1); for (int y = y1l; y < y2u; y++) { for (int x = x1l; x < x1u; x++) { image_(drawPixel)(output, y, x, cr, cg, cb); } for (int x = x2l; x < x2u; x++) { image_(drawPixel)(output, y, x, cr, cg, cb); } } for (int x = x1l; x < x2u; x++) { for (int y = y1l; y < y1u; y++) { image_(drawPixel)(output, y, x, cr, cg, cb); } for (int y = y2l; y < y2u; y++) { image_(drawPixel)(output, y, x, cr, cg, cb); } } return 0; } static const struct luaL_Reg image_(Main__) [] = { {"scaleSimple", image_(Main_scaleSimple)}, {"scaleBilinear", image_(Main_scaleBilinear)}, {"scaleBicubic", image_(Main_scaleBicubic)}, {"rotate", image_(Main_rotate)}, {"rotateBilinear", image_(Main_rotateBilinear)}, {"polar", image_(Main_polar)}, {"polarBilinear", image_(Main_polarBilinear)}, {"logPolar", image_(Main_logPolar)}, {"logPolarBilinear", image_(Main_logPolarBilinear)}, {"translate", image_(Main_translate)}, {"cropNoScale", image_(Main_cropNoScale)}, {"warp", image_(Main_warp)}, {"saturate", image_(Main_saturate)}, {"rgb2y", image_(Main_rgb2y)}, {"rgb2hsv", image_(Main_rgb2hsv)}, {"rgb2hsl", image_(Main_rgb2hsl)}, {"hsv2rgb", image_(Main_hsv2rgb)}, {"hsl2rgb", image_(Main_hsl2rgb)}, {"rgb2lab", image_(Main_rgb2lab)}, {"lab2rgb", image_(Main_lab2rgb)}, {"gaussian", image_(Main_gaussian)}, {"vflip", image_(Main_vflip)}, {"hflip", image_(Main_hflip)}, {"flip", image_(Main_flip)}, {"colorize", image_(Main_colorize)}, {"text", image_(Main_drawtext)}, {"drawRect", image_(Main_drawRect)}, {NULL, NULL} }; void image_(Main_init)(lua_State *L) { luaT_pushmetatable(L, torch_Tensor); luaT_registeratname(L, image_(Main__), "image"); } #endif // TH_GENERIC_FILE generic/jpeg.c000077500000000000000000000414621307617767500136200ustar00rootroot00000000000000#ifndef TH_GENERIC_FILE #define TH_GENERIC_FILE "generic/jpeg.c" #else /******************** JPEG DECOMPRESSION SAMPLE INTERFACE *******************/ /* This half of the example shows how to read data from the JPEG decompressor. * It's a bit more refined than the above, in that we show: * (a) how to modify the JPEG library's standard error-reporting behavior; * (b) how to allocate workspace using the library's memory manager. * * Just to make this example a little different from the first one, we'll * assume that we do not intend to put the whole image into an in-memory * buffer, but to send it line-by-line someplace else. We need a one- * scanline-high JSAMPLE array as a work buffer, and we will let the JPEG * memory manager allocate it for us. This approach is actually quite useful * because we don't need to remember to deallocate the buffer separately: it * will go away automatically when the JPEG object is cleaned up. */ /* * ERROR HANDLING: * * The JPEG library's standard error handler (jerror.c) is divided into * several "methods" which you can override individually. This lets you * adjust the behavior without duplicating a lot of code, which you might * have to update with each future release. * * Our example here shows how to override the "error_exit" method so that * control is returned to the library's caller when a fatal error occurs, * rather than calling exit() as the standard error_exit method does. * * We use C's setjmp/longjmp facility to return control. This means that the * routine which calls the JPEG library must first execute a setjmp() call to * establish the return point. We want the replacement error_exit to do a * longjmp(). But we need to make the setjmp buffer accessible to the * error_exit routine. To do this, we make a private extension of the * standard JPEG error handler object. (If we were using C++, we'd say we * were making a subclass of the regular error handler.) * * Here's the extended error handler struct: */ #ifndef _LIBJPEG_ERROR_STRUCTS_ #define _LIBJPEG_ERROR_STRUCTS_ struct my_error_mgr { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ char msg[JMSG_LENGTH_MAX]; /* last error message */ }; typedef struct my_error_mgr * my_error_ptr; #endif /* * Here's the routine that will replace the standard error_exit method: */ METHODDEF(void) libjpeg_(Main_error) (j_common_ptr cinfo) { /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ my_error_ptr myerr = (my_error_ptr) cinfo->err; /* See below. */ (*cinfo->err->output_message) (cinfo); /* Return control to the setjmp point */ longjmp(myerr->setjmp_buffer, 1); } /* * Here's the routine that will replace the standard output_message method: */ METHODDEF(void) libjpeg_(Main_output_message) (j_common_ptr cinfo) { my_error_ptr myerr = (my_error_ptr) cinfo->err; (*cinfo->err->format_message) (cinfo, myerr->msg); } /* * Sample routine for JPEG decompression. We assume that the source file name * is passed in. We want to return 1 on success, 0 on error. */ static int libjpeg_(Main_size)(lua_State *L) { /* This struct contains the JPEG decompression parameters and pointers to * working space (which is allocated as needed by the JPEG library). */ struct jpeg_decompress_struct cinfo; /* We use our private extension JPEG error handler. * Note that this struct must live as long as the main JPEG parameter * struct, to avoid dangling-pointer problems. */ struct my_error_mgr jerr; /* More stuff */ FILE * infile; /* source file */ const char *filename = luaL_checkstring(L, 1); /* In this example we want to open the input file before doing anything else, * so that the setjmp() error recovery below can assume the file is open. * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that * requires it in order to read binary files. */ if ((infile = fopen(filename, "rb")) == NULL) { luaL_error(L, "cannot open file <%s> for reading", filename); } /* Step 1: allocate and initialize JPEG decompression object */ /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = libjpeg_(Main_error); jerr.pub.output_message = libjpeg_(Main_output_message); /* Establish the setjmp return context for my_error_exit to use. */ if (setjmp(jerr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object, close the input file, and return. */ jpeg_destroy_decompress(&cinfo); fclose(infile); luaL_error(L, jerr.msg); } /* Now we can initialize the JPEG decompression object. */ jpeg_create_decompress(&cinfo); /* Step 2: specify data source (eg, a file) */ jpeg_stdio_src(&cinfo, infile); /* Step 3: read file parameters with jpeg_read_header() */ jpeg_read_header(&cinfo, TRUE); /* We can ignore the return value from jpeg_read_header since * (a) suspension is not possible with the stdio data source, and * (b) we passed TRUE to reject a tables-only JPEG file as an error. * See libjpeg.doc for more info. */ /* Step 4: set parameters for decompression */ /* In this example, we don't need to change any of the defaults set by * jpeg_read_header(), so we do nothing here. */ /* Step 5: Start decompressor */ (void) jpeg_start_decompress(&cinfo); /* We can ignore the return value since suspension is not possible * with the stdio data source. */ lua_pushnumber(L, cinfo.output_components); lua_pushnumber(L, cinfo.output_height); lua_pushnumber(L, cinfo.output_width); /* Step 8: Release JPEG decompression object */ /* This is an important step since it will release a good deal of memory. */ jpeg_destroy_decompress(&cinfo); /* After finish_decompress, we can close the input file. * Here we postpone it until after no more JPEG errors are possible, * so as to simplify the setjmp error logic above. (Actually, I don't * think that jpeg_destroy can do an error exit, but why assume anything...) */ fclose(infile); /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jerr.pub.num_warnings is nonzero). */ /* And we're done! */ return 3; } static int libjpeg_(Main_load)(lua_State *L) { const int load_from_file = luaL_checkint(L, 1); #if !defined(HAVE_JPEG_MEM_SRC) if (load_from_file != 1) { luaL_error(L, JPEG_MEM_SRC_ERR_MSG); } #endif /* This struct contains the JPEG decompression parameters and pointers to * working space (which is allocated as needed by the JPEG library). */ struct jpeg_decompress_struct cinfo; /* We use our private extension JPEG error handler. * Note that this struct must live as long as the main JPEG parameter * struct, to avoid dangling-pointer problems. */ struct my_error_mgr jerr; /* More stuff */ FILE * infile; /* source file (if loading from file) */ unsigned char * inmem; /* source memory (if loading from memory) */ unsigned long inmem_size; /* source memory size (bytes) */ JSAMPARRAY buffer; /* Output row buffer */ /* int row_stride; /1* physical row width in output buffer *1/ */ int i, k; THTensor *tensor = NULL; if (load_from_file == 1) { const char *filename = luaL_checkstring(L, 2); /* In this example we want to open the input file before doing anything else, * so that the setjmp() error recovery below can assume the file is open. * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that * requires it in order to read binary files. */ if ((infile = fopen(filename, "rb")) == NULL) { luaL_error(L, "cannot open file <%s> for reading", filename); } } else { /* We're loading from a ByteTensor */ THByteTensor *src = luaT_checkudata(L, 2, "torch.ByteTensor"); inmem = THByteTensor_data(src); inmem_size = src->size[0]; infile = NULL; } /* Step 1: allocate and initialize JPEG decompression object */ /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = libjpeg_(Main_error); jerr.pub.output_message = libjpeg_(Main_output_message); /* Establish the setjmp return context for my_error_exit to use. */ if (setjmp(jerr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object, close the input file, and return. */ jpeg_destroy_decompress(&cinfo); if (infile) { fclose(infile); } luaL_error(L, jerr.msg); } /* Now we can initialize the JPEG decompression object. */ jpeg_create_decompress(&cinfo); /* Step 2: specify data source (eg, a file) */ if (load_from_file == 1) { jpeg_stdio_src(&cinfo, infile); } else { jpeg_mem_src(&cinfo, inmem, inmem_size); } /* Step 3: read file parameters with jpeg_read_header() */ (void) jpeg_read_header(&cinfo, TRUE); /* We can ignore the return value from jpeg_read_header since * (a) suspension is not possible with the stdio data source, and * (b) we passed TRUE to reject a tables-only JPEG file as an error. * See libjpeg.doc for more info. */ /* Step 4: set parameters for decompression */ /* In this example, we don't need to change any of the defaults set by * jpeg_read_header(), so we do nothing here. */ /* Step 5: Start decompressor */ (void) jpeg_start_decompress(&cinfo); /* We can ignore the return value since suspension is not possible * with the stdio data source. */ /* We may need to do some setup of our own at this point before reading * the data. After jpeg_start_decompress() we have the correct scaled * output image dimensions available, as well as the output colormap * if we asked for color quantization. * In this example, we need to make an output work buffer of the right size. */ /* Make a one-row-high sample array that will go away when done with image */ const unsigned int chans = cinfo.output_components; const unsigned int height = cinfo.output_height; const unsigned int width = cinfo.output_width; tensor = THTensor_(newWithSize3d)(chans, height, width); real *tdata = THTensor_(data)(tensor); buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, chans * width, 1); /* Step 6: while (scan lines remain to be read) */ /* jpeg_read_scanlines(...); */ /* Here we use the library's state variable cinfo.output_scanline as the * loop counter, so that we don't have to keep track ourselves. */ while (cinfo.output_scanline < height) { /* jpeg_read_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could ask for * more than one scanline at a time if that's more convenient. */ (void) jpeg_read_scanlines(&cinfo, buffer, 1); const unsigned int j = cinfo.output_scanline-1; if (chans == 3) { /* special-case for speed */ real *td1 = tdata + 0 * (height * width) + j * width; real *td2 = tdata + 1 * (height * width) + j * width; real *td3 = tdata + 2 * (height * width) + j * width; const unsigned char *buf = buffer[0]; for(i = 0; i < width; i++) { *td1++ = (real)buf[chans * i + 0]; *td2++ = (real)buf[chans * i + 1]; *td3++ = (real)buf[chans * i + 2]; } } else if (chans == 1) { /* special-case for speed */ real *td = tdata + j * width; for(i = 0; i < width; i++) { *td++ = (real)buffer[0][i]; } } else { /* general case */ for(k = 0; k < chans; k++) { const unsigned int k_ = k; real *td = tdata + k_ * (height * width) + j * width; for(i = 0; i < width; i++) { *td++ = (real)buffer[0][chans * i + k_]; } } } } /* Step 7: Finish decompression */ (void) jpeg_finish_decompress(&cinfo); /* We can ignore the return value since suspension is not possible * with the stdio data source. */ /* Step 8: Release JPEG decompression object */ /* This is an important step since it will release a good deal of memory. */ jpeg_destroy_decompress(&cinfo); /* After finish_decompress, we can close the input file. * Here we postpone it until after no more JPEG errors are possible, * so as to simplify the setjmp error logic above. (Actually, I don't * think that jpeg_destroy can do an error exit, but why assume anything...) */ if (infile) { fclose(infile); } /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jerr.pub.num_warnings is nonzero). */ /* And we're done! */ luaT_pushudata(L, tensor, torch_Tensor); return 1; } /* * save function * */ int libjpeg_(Main_save)(lua_State *L) { const int save_to_file = luaL_checkint(L, 3); #if !defined(HAVE_JPEG_MEM_DEST) if (save_to_file != 1) { luaL_error(L, JPEG_MEM_DEST_ERR_MSG); } #endif unsigned char *inmem = NULL; /* destination memory (if saving to memory) */ unsigned long inmem_size = 0; /* destination memory size (bytes) */ /* get args */ const char *filename = luaL_checkstring(L, 1); THTensor *tensor = luaT_checkudata(L, 2, torch_Tensor); THTensor *tensorc = THTensor_(newContiguous)(tensor); real *tensor_data = THTensor_(data)(tensorc); THByteTensor* tensor_dest = NULL; if (save_to_file == 0) { tensor_dest = luaT_checkudata(L, 5, "torch.ByteTensor"); } int quality = luaL_checkint(L, 4); if (quality < 0 || quality > 100) { luaL_error(L, "quality should be between 0 and 100"); } /* jpeg struct */ struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; /* pointer to raw image */ unsigned char *raw_image = NULL; /* dimensions of the image we want to write */ int width=0, height=0, bytes_per_pixel=0; int color_space=0; if (tensorc->nDimension == 3) { bytes_per_pixel = tensorc->size[0]; height = tensorc->size[1]; width = tensorc->size[2]; if (bytes_per_pixel == 3) { color_space = JCS_RGB; } else if (bytes_per_pixel == 1) { color_space = JCS_GRAYSCALE; } else { luaL_error(L, "tensor should have 1 or 3 channels (gray or RGB)"); } } else if (tensorc->nDimension == 2) { bytes_per_pixel = 1; height = tensorc->size[0]; width = tensorc->size[1]; color_space = JCS_GRAYSCALE; } else { luaL_error(L, "supports only 1 or 3 dimension tensors"); } /* alloc raw image data */ raw_image = (unsigned char *)malloc((sizeof (unsigned char))*width*height*bytes_per_pixel); /* convert tensor to raw bytes */ int x,y,k; for (k=0; ksize[0]; inmem.offset = 8; fp = NULL; if (png_sig_cmp(inmem.buffer, 0, 8)) luaL_error(L, "[read_png_byte_tensor] ByteTensor is not recognized as a PNG file"); } /* initialize stuff */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) luaL_error(L, "[read_png] png_create_read_struct failed"); png_set_error_fn(png_ptr, &errmsg, libpng_error_fn, NULL); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); if (fp) { fclose(fp); } luaL_error(L, "[read_png] png_create_info_struct failed"); } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (fp) { fclose(fp); } luaL_error(L, "[read_png] Error during init_io: %s", errmsg.str); } if (load_from_file == 1) { png_init_io(png_ptr, fp); } else { /* set the read callback */ png_set_read_fn(png_ptr,(png_voidp)&inmem, libpng_userReadData); } png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); /* get depth */ int depth = 0; if (color_type == PNG_COLOR_TYPE_RGBA) { depth = 4; } else if (color_type == PNG_COLOR_TYPE_RGB) { depth = 3; } else if (color_type == PNG_COLOR_TYPE_GRAY) { if (bit_depth < 8) { png_set_expand_gray_1_2_4_to_8(png_ptr); } depth = 1; } else if (color_type == PNG_COLOR_TYPE_GA) { depth = 2; } else if (color_type == PNG_COLOR_TYPE_PALETTE) { depth = 3; png_set_expand(png_ptr); } else { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (fp) { fclose(fp); } luaL_error(L, "[read_png_file] Unknown color space"); } if (bit_depth < 8) { png_set_strip_16(png_ptr); } png_read_update_info(png_ptr, info_ptr); /* read file */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (fp) { fclose(fp); } luaL_error(L, "[read_png_file] Error during read_image: %s", errmsg.str); } /* alloc tensor */ THTensor *tensor = THTensor_(newWithSize3d)(depth, height, width); real *tensor_data = THTensor_(data)(tensor); /* alloc data in lib format */ row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height); int y; for (y=0; y 1)) { for (k=0; knDimension == 3) { depth = tensorc->size[0]; height = tensorc->size[1]; width = tensorc->size[2]; } else if (tensorc->nDimension == 2) { depth = 1; height = tensorc->size[0]; width = tensorc->size[1]; } /* depth check */ if ((depth != 1) && (depth != 3) && (depth != 4)) { luaL_error(L, "[write_png_file] Depth must be 1, 3 or 4"); } if (depth == 4) color_type = PNG_COLOR_TYPE_RGBA; else if (depth == 3) color_type = PNG_COLOR_TYPE_RGB; else if (depth == 1) color_type = PNG_COLOR_TYPE_GRAY; /* initialize stuff */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) luaL_error(L, "[write_png_file] png_create_write_struct failed"); png_set_error_fn(png_ptr, &errmsg, libpng_error_fn, NULL); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) luaL_error(L, "[write_png_file] png_create_info_struct failed"); /* create file */ if(save_to_file) { fp = fopen(file_name, "wb"); if (!fp) luaL_error(L, "[write_png_file] File %s could not be opened for writing", file_name); if (setjmp(png_jmpbuf(png_ptr))) luaL_error(L, "[write_png_file] Error during init_io: %s", errmsg.str); png_init_io(png_ptr, fp); } else { _inmem.inmem=NULL; _inmem.inmem_size=0; png_set_write_fn(png_ptr, &_inmem, libpng_userWriteData, NULL); } /* write header */ if (setjmp(png_jmpbuf(png_ptr))) luaL_error(L, "[write_png_file] Error during writing header: %s", errmsg.str); png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_ptr, info_ptr); /* convert tensor to 8bit bytes */ row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height); int y; for (y=0; y for reading", filename); } long W,H,C; char p,n; // magic number p = (char)getc(fp); if ( p != 'P' ) { W = H = 0; fclose(fp); luaL_error(L, "corrupted file"); } n = (char)getc(fp); // Dimensions W = ppm_get_long(fp); H = ppm_get_long(fp); if ( n=='3' || n == '6') { C = 3; } else if ( n=='2' || n=='5' ) { C = 1; } else { W=H=C=0; fclose ( fp ); luaL_error(L, "unsupported magic number: P%c", n); } fclose(fp); lua_pushnumber(L, C); lua_pushnumber(L, H); lua_pushnumber(L, W); return 3; } static int libppm_(Main_load)(lua_State *L) { const char *filename = luaL_checkstring(L, 1); FILE* fp = fopen ( filename, "r" ); if ( !fp ) { luaL_error(L, "cannot open file <%s> for reading", filename); } long W,H,C; char p,n; int D, bps, bpc; // magic number p = (char)getc(fp); if ( p != 'P' ) { W = H = 0; fclose(fp); luaL_error(L, "corrupted file"); } n = (char)getc(fp); // Dimensions W = ppm_get_long(fp); H = ppm_get_long(fp); // Max color value D = ppm_get_long(fp); // Either 8 or 16 bits per pixel bps = 8; if (D > 255) { bps = 16; } bpc = bps / 8; //printf("Loading PPM\nMAGIC: %c%c\nWidth: %ld, Height: %ld\nChannels: %d, Bits-per-pixel: %d\n", p, n, W, H, D, bps); // load data int ok = 1; size_t s; unsigned char *r = NULL; if ( n=='6' ) { C = 3; s = W*H*C*bpc; r = malloc(s); if (fread ( r, 1, s, fp ) < s) ok = 0; } else if ( n=='5' ) { C = 1; s = W*H*C*bpc; r = malloc(s); if (fread ( r, 1, s, fp ) < s) ok = 0; } else if ( n=='3' ) { int c,i; C = 3; s = W*H*C; r = malloc(s); for (i=0; inDimension == 3) { C = tensorc->size[0]; H = tensorc->size[1]; W = tensorc->size[2]; } else if (tensorc->nDimension == 2) { C = 1; H = tensorc->size[0]; W = tensorc->size[1]; } else { C=W=H=0; luaL_error(L, "can only export tensor with geometry: HxW or 1xHxW or 3xHxW"); } N = C*H*W; // convert to chars unsigned char *bytes = (unsigned char*)malloc(N); long i,k,j=0; for (i=0; i for writing", filename); } // write 3 or 1 channel(s) header if (C == 3) { fprintf(fp, "P6\n%ld %ld\n%d\n", W, H, 255); } else { fprintf(fp, "P5\n%ld %ld\n%d\n", W, H, 255); } // write data fwrite(bytes, 1, N, fp); // cleanup THTensor_(free)(tensorc); free(bytes); fclose (fp); // return result return 1; } static const luaL_Reg libppm_(Main__)[] = { {"load", libppm_(Main_load)}, {"save", libppm_(Main_save)}, {"size", libppm_(Main_size)}, {NULL, NULL} }; DLL_EXPORT int libppm_(Main_init)(lua_State *L) { luaT_pushmetatable(L, torch_Tensor); luaT_registeratname(L, libppm_(Main__), "libppm"); return 1; } #endif image-1.1.alpha-0.rockspec000066400000000000000000000014541307617767500154400ustar00rootroot00000000000000package = "image" version = "1.1.alpha-0" source = { url = "git://github.com/torch/image", tag = "master" } description = { summary = "An image library for Torch", detailed = [[ This package provides routines to load/save and manipulate images using Torch's Tensor data structure. ]], homepage = "https://github.com/torch/image", license = "BSD" } dependencies = { "torch >= 7.0", "sys >= 1.0", "xlua >= 1.0", "dok" } build = { type = "command", build_command = [[ cmake -E make_directory build && cd build && cmake .. -DLUALIB=$(LUALIB) -DLUA_INCDIR="$(LUA_INCDIR)" -DLUA_LIBDIR="$(LUA_LIBDIR)" -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$(LUA_BINDIR)/.." -DCMAKE_INSTALL_PREFIX="$(PREFIX)" && $(MAKE) ]], install_command = "cd build && $(MAKE) install" } image.c000066400000000000000000000020211307617767500123220ustar00rootroot00000000000000 #include #include #if LUA_VERSION_NUM >= 503 #define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) #endif #define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME) #define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor) #define image_(NAME) TH_CONCAT_3(image_, Real, NAME) #ifdef max #undef max #endif #define max( a, b ) ( ((a) > (b)) ? (a) : (b) ) #ifdef min #undef min #endif #define min( a, b ) ( ((a) < (b)) ? (a) : (b) ) #include "font.c" #include "generic/image.c" #include "THGenerateAllTypes.h" DLL_EXPORT int luaopen_libimage(lua_State *L) { image_FloatMain_init(L); image_DoubleMain_init(L); image_ByteMain_init(L); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "image"); lua_newtable(L); luaT_setfuncs(L, image_DoubleMain__, 0); lua_setfield(L, -2, "double"); lua_newtable(L); luaT_setfuncs(L, image_FloatMain__, 0); lua_setfield(L, -2, "float"); lua_newtable(L); luaT_setfuncs(L, image_ByteMain__, 0); lua_setfield(L, -2, "byte"); return 1; } init.lua000066400000000000000000002445461307617767500125660ustar00rootroot00000000000000---------------------------------------------------------------------- -- -- Copyright (c) 2011 Ronan Collobert, Clement Farabet -- -- Permission is hereby granted, free of charge, to any person obtaining -- a copy of this software and associated documentation files (the -- "Software"), to deal in the Software without restriction, including -- without limitation the rights to use, copy, modify, merge, publish, -- distribute, sublicense, and/or sell copies of the Software, and to -- permit persons to whom the Software is furnished to do so, subject to -- the following conditions: -- -- The above copyright notice and this permission notice shall be -- included in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- ---------------------------------------------------------------------- -- description: -- image - an image toolBox, for Torch -- -- history: -- July 1, 2011, 7:42PM - import from Torch5 - Clement Farabet ---------------------------------------------------------------------- require 'torch' require 'xlua' require 'dok' require 'libimage' local fpath = require 'sys.fpath' local startswith = function(str, prefix) return string.find(str, prefix, 1, true) == 1 end local magicJPG = string.char(0xff, 0xd8, 0xff) local magicPNG = string.char(0x89, 0x50, 0x4e, 0x47) ---------------------------------------------------------------------- -- include unit test function -- require 'image.test' ---------------------------------------------------------------------- -- types lookups -- local type2tensor = { float = torch.FloatTensor(), double = torch.DoubleTensor(), byte = torch.ByteTensor(), } local template = function(type) if type then return type2tensor[type] else return torch.Tensor() end end ---------------------------------------------------------------------- -- save/load in multiple formats -- -- depth convertion helper local function todepth(img, depth) if depth and depth == 1 then if img:nDimension() == 2 then -- all good elseif img:size(1) == 3 or img:size(1) == 4 then img = image.rgb2y(img:narrow(1,1,3))[1] elseif img:size(1) == 2 then img = img:narrow(1,1,1) elseif img:size(1) ~= 1 then dok.error('image loaded has wrong #channels', 'image.todepth') end elseif depth and depth == 3 then local chan = img:size(1) if chan == 3 then -- all good elseif img:nDimension() == 2 then local imgrgb = img.new(3, img:size(1), img:size(2)) imgrgb:select(1, 1):copy(img) imgrgb:select(1, 2):copy(img) imgrgb:select(1, 3):copy(img) img = imgrgb elseif chan == 4 then img = img:narrow(1,1,3) elseif chan == 1 then local imgrgb = img.new(3, img:size(2), img:size(3)) imgrgb:select(1, 1):copy(img) imgrgb:select(1, 2):copy(img) imgrgb:select(1, 3):copy(img) img = imgrgb else dok.error('image loaded has wrong #channels', 'image.todepth') end end return img end local function isPNG(magicTensor) local pngMagic = torch.ByteTensor({0x89,0x50,0x4e,0x47}) return torch.all(torch.eq(magicTensor, pngMagic)) end local function isJPG(magicTensor) -- There are many valid 4th bytes, so only check the first 3 bytes. -- libjpeg should support most if not all of these: -- source: http://filesignatures.net/?page=all&order=SIGNATURE&alpha=J local jpgMagic = torch.ByteTensor({0xff, 0xd8, 0xff}) return torch.all(torch.eq(magicTensor, jpgMagic)) end local function decompress(tensor, depth, tensortype) if torch.typename(tensor) ~= 'torch.ByteTensor' then dok.error('Input tensor must be a byte tensor', 'image.decompress') end if tensor:nElement() < 4 then dok.error('Input must be either jpg or png format', 'image.decompress') end if isJPG(tensor[{{1,3}}]) then return image.decompressJPG(tensor, depth, tensortype) elseif isPNG(tensor[{{1,4}}]) then return image.decompressPNG(tensor, depth, tensortype) else dok.error('Input must be either jpg or png format', 'image.decompress') end end rawset(image, 'decompress', decompress) local function processPNG(img, depth, bit_depth, tensortype) local MAXVAL = 255 if bit_depth == 16 then MAXVAL = 65535 end if tensortype ~= 'byte' then img:mul(1/MAXVAL) end img = todepth(img, depth) return img end local function loadPNG(filename, depth, tensortype) if not xlua.require 'liblua_png' then dok.error('libpng package not found, please install libpng','image.loadPNG') end local load_from_file = 1 local a, bit_depth = template(tensortype).libpng.load(load_from_file, filename) return processPNG(a, depth, bit_depth, tensortype) end rawset(image, 'loadPNG', loadPNG) local function clampImage(tensor) if tensor:type() == 'torch.ByteTensor' then return tensor end local a = torch.Tensor():resize(tensor:size()):copy(tensor) a.image.saturate(a) -- bound btwn 0 and 1 a:mul(255) -- remap to [0..255] return a end local function savePNG(filename, tensor) if not xlua.require 'liblua_png' then dok.error('libpng package not found, please install libpng','image.savePNG') end tensor = clampImage(tensor) local save_to_file = 1 tensor.libpng.save(filename, tensor, save_to_file) end rawset(image, 'savePNG', savePNG) local function decompressPNG(tensor, depth, tensortype) if not xlua.require 'liblua_png' then dok.error('libpng package not found, please install libpng', 'image.decompressPNG') end if torch.typename(tensor) ~= 'torch.ByteTensor' then dok.error('Input tensor (with compressed png) must be a byte tensor', 'image.decompressPNG') end local load_from_file = 0 local a, bit_depth = template(tensortype).libpng.load(load_from_file, tensor) if a == nil then return nil else return processPNG(a, depth, bit_depth, tensortype) end end rawset(image, 'decompressPNG', decompressPNG) function image.getPNGsize(filename) if not xlua.require 'liblua_png' then dok.error('libpng package not found, please install libpng','image.getPNGsize') end return torch.Tensor().libpng.size(filename) end local function compressPNG(tensor) if not xlua.require 'liblua_png' then dok.error('libpng package not found, please install libpng', 'image.compressPNG') end tensor = clampImage(tensor) local b = torch.ByteTensor() local save_to_file = 0 tensor.libpng.save("", tensor, save_to_file, b) return b end rawset(image, 'compressPNG', compressPNG) local function processJPG(img, depth, tensortype) local MAXVAL = 255 if tensortype ~= 'byte' then img:mul(1/MAXVAL) end img = todepth(img, depth) return img end local function loadJPG(filename, depth, tensortype) if not xlua.require 'libjpeg' then dok.error('libjpeg package not found, please install libjpeg','image.loadJPG') end local load_from_file = 1 local a = template(tensortype).libjpeg.load(load_from_file, filename) if a == nil then return nil else return processJPG(a, depth, tensortype) end end rawset(image, 'loadJPG', loadJPG) local function decompressJPG(tensor, depth, tensortype) if not xlua.require 'libjpeg' then dok.error('libjpeg package not found, please install libjpeg', 'image.decompressJPG') end if torch.typename(tensor) ~= 'torch.ByteTensor' then dok.error('Input tensor (with compressed jpeg) must be a byte tensor', 'image.decompressJPG') end local load_from_file = 0 local a = template(tensortype).libjpeg.load(load_from_file, tensor) if a == nil then return nil else return processJPG(a, depth, tensortype) end end rawset(image, 'decompressJPG', decompressJPG) local function saveJPG(filename, tensor) if not xlua.require 'libjpeg' then dok.error('libjpeg package not found, please install libjpeg','image.saveJPG') end tensor = clampImage(tensor) local save_to_file = 1 local quality = 75 tensor.libjpeg.save(filename, tensor, save_to_file, quality) end rawset(image, 'saveJPG', saveJPG) function image.getJPGsize(filename) if not xlua.require 'libjpeg' then dok.error('libjpeg package not found, please install libjpeg','image.getJPGsize') end return torch.Tensor().libjpeg.size(filename) end local function compressJPG(tensor, quality) if not xlua.require 'libjpeg' then dok.error('libjpeg package not found, please install libjpeg', 'image.compressJPG') end tensor = clampImage(tensor) local b = torch.ByteTensor() local save_to_file = 0 quality = quality or 75 tensor.libjpeg.save("", tensor, save_to_file, quality, b) return b end rawset(image, 'compressJPG', compressJPG) local function loadPPM(filename, depth, tensortype) require 'libppm' local MAXVAL = 255 local a = template(tensortype).libppm.load(filename) if tensortype ~= 'byte' then a:mul(1/MAXVAL) end a = todepth(a, depth) return a end rawset(image, 'loadPPM', loadPPM) local function savePPM(filename, tensor) require 'libppm' if tensor:nDimension() ~= 3 or tensor:size(1) ~= 3 then dok.error('can only save 3xHxW images as PPM', 'image.savePPM') end tensor = clampImage(tensor) tensor.libppm.save(filename, tensor) end rawset(image, 'savePPM', savePPM) local function savePGM(filename, tensor) require 'libppm' if tensor:nDimension() == 3 and tensor:size(1) ~= 1 then dok.error('can only save 1xHxW or HxW images as PGM', 'image.savePGM') end tensor = clampImage(tensor) tensor.libppm.save(filename, tensor) end rawset(image, 'savePGM', savePGM) function image.getPPMsize(filename) require 'libppm' return torch.Tensor().libppm.size(filename) end local filetypes = { jpg = {loader = image.loadJPG, saver = image.saveJPG}, png = {loader = image.loadPNG, saver = image.savePNG}, ppm = {loader = image.loadPPM, saver = image.savePPM}, -- yes, loadPPM not loadPGM pgm = {loader = image.loadPPM, saver = image.savePGM} } filetypes['JPG'] = filetypes['jpg'] filetypes['JPEG'] = filetypes['jpg'] filetypes['jpeg'] = filetypes['jpg'] filetypes['PNG'] = filetypes['png'] filetypes['PPM'] = filetypes['ppm'] filetypes['PGM'] = filetypes['pgm'] rawset(image, 'supported_filetypes', filetypes) local function is_supported(suffix) return filetypes[suffix] ~= nil end rawset(image, 'is_supported', is_supported) local function load(filename, depth, tensortype) if not filename then print(dok.usage('image.load', 'loads an image into a torch.Tensor', nil, {type='string', help='path to file', req=true}, {type='number', help='force destination depth: 1 | 3'}, {type='string', help='type: byte | float | double'})) dok.error('missing file name', 'image.load') end local ext local f, err = io.open(filename, 'rb') if not f then error(err) end local hdr = f:read(4) or '' f:close() if startswith(hdr, magicJPG) then ext = 'jpg' elseif startswith(hdr, magicPNG) then ext = 'png' elseif hdr:match('^P[25]') then ext = 'pgm' elseif hdr:match('^P[36]') then ext = 'ppm' end if not ext then ext = string.match(filename,'%.(%a+)$') end local tensor if image.is_supported(ext) then tensor = filetypes[ext].loader(filename, depth, tensortype) elseif not ext then dok.error('unable to determine image type for file: ' .. filename, 'image.load') else dok.error('unknown image type: ' .. ext, 'image.load') end return tensor end rawset(image, 'load', load) filetypes.jpg.sizer = image.getJPGsize filetypes.png.sizer = image.getPNGsize filetypes.ppm.sizer = image.getPPMsize filetypes.pgm.sizer = image.getPPMsize -- sim. to loadPPM not loadPGM local function getSize(filename) if not filename then print(dok.usage('image.getSize', 'returns size of image without loading', nil, {type='string', help='path to file', req=true})) dok.error('missing file name', 'image.getSize') end local ext local f, err = io.open(filename, 'rb') if not f then error(err) end local hdr = f:read(4) or '' f:close() if startswith(hdr, magicJPG) then ext = 'jpg' elseif startswith(hdr, magicPNG) then ext = 'png' elseif hdr:match('^P[25]') then ext = 'pgm' elseif hdr:match('^P[36]') then ext = 'ppm' end if not ext then ext = string.match(filename,'%.(%a+)$') end local size if image.is_supported(ext) then size = {filetypes[ext].sizer(filename)} elseif not ext then dok.error('unable to determine image type for file: ' .. filename, 'image.getSize') else dok.error('unknown image type: ' .. ext, 'image.load') end return torch.LongTensor(size) end rawset(image, 'getSize', getSize) local function save(filename, tensor) if not filename or not tensor then print(dok.usage('image.save', 'saves a torch.Tensor to a disk', nil, {type='string', help='path to file', req=true}, {type='torch.Tensor', help='tensor to save (NxHxW, N = 1 | 3)'})) dok.error('missing file name | tensor to save', 'image.save') end local ext = string.match(filename,'%.(%a+)$') if image.is_supported(ext) then tensor = filetypes[ext].saver(filename, tensor) else dok.error('unknown image type: ' .. ext, 'image.save') end end rawset(image, 'save', save) ---------------------------------------------------------------------- -- crop -- local function crop(...) local dst,src,startx,starty,endx,endy local format,width,height local args = {...} if select('#',...) == 6 then dst = args[1] src = args[2] startx = args[3] starty = args[4] endx = args[5] endy = args[6] elseif select('#',...) == 5 then if type(args[3]) == 'string' then dst = args[1] src = args[2] format = args[3] width = args[4] height = args[5] else src = args[1] startx = args[2] starty = args[3] endx = args[4] endy = args[5] end elseif select('#',...) == 4 then if type(args[2]) == 'string' then src = args[1] format = args[2] width = args[3] height = args[4] else dst = args[1] src = args[2] startx = args[3] starty = args[4] end elseif select('#',...) == 3 then src = args[1] startx = args[2] starty = args[3] else print(dok.usage('image.crop', 'crop an image', nil, {type='torch.Tensor', help='input image', req=true}, {type='number', help='start x', req=true}, {type='number', help='start y', req=true}, {type='number', help='end x'}, {type='number', help='end y'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='number', help='start x', req=true}, {type='number', help='start y', req=true}, {type='number', help='end x'}, {type='number', help='end y'}, '', {type='torch.Tensor', help='input image', req=true}, {type='string', help='format: "c" or "tl" or "tr" or "bl" or "br"', req=true}, {type='number', help='width', req=true}, {type='number', help='height', req=true}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='string', help='format: "c" or "tl" or "tr" or "bl" or "br"', req=true}, {type='number', help='width', req=true}, {type='number', help='height', req=true})) dok.error('incorrect arguments', 'image.crop') end if format then local iwidth,iheight if src:nDimension() == 3 then iwidth,iheight = src:size(3),src:size(2) else iwidth,iheight = src:size(2),src:size(1) end local x1, y1 if format == 'c' then x1, y1 = math.floor((iwidth-width)/2), math.floor((iheight-height)/2) elseif format == 'tl' then x1, y1 = 0, 0 elseif format == 'tr' then x1, y1 = iwidth-width, 0 elseif format == 'bl' then x1, y1 = 0, iheight-height elseif format == 'br' then x1, y1 = iwidth-width, iheight-height else error('crop format must be "c"|"tl"|"tr"|"bl"|"br"') end return crop(dst, src, x1, y1, x1+width, y1+height) end if endx==nil then return src.image.cropNoScale(src,dst,startx,starty) else local depth=1 local x if src:nDimension() > 2 then x = src.new(src:size(1),endy-starty,endx-startx) else x = src.new(endy-starty,endx-startx) end src.image.cropNoScale(src,x,startx,starty) if dst then image.scale(dst, x) else dst = x end end return dst end rawset(image, 'crop', crop) ---------------------------------------------------------------------- -- translate -- local function translate(...) local dst,src,x,y local args = {...} if select('#',...) == 4 then dst = args[1] src = args[2] x = args[3] y = args[4] elseif select('#',...) == 3 then src = args[1] x = args[2] y = args[3] else print(dok.usage('image.translate', 'translate an image', nil, {type='torch.Tensor', help='input image', req=true}, {type='number', help='horizontal translation', req=true}, {type='number', help='vertical translation', req=true}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='number', help='horizontal translation', req=true}, {type='number', help='vertical translation', req=true})) dok.error('incorrect arguments', 'image.translate') end dst = dst or src.new() dst:resizeAs(src) dst:zero() src.image.translate(src,dst,x,y) return dst end rawset(image, 'translate', translate) ---------------------------------------------------------------------- -- scale -- local function scale(...) local dst,src,width,height,mode,size local args = {...} if select('#',...) == 4 then src = args[1] width = args[2] height = args[3] mode = args[4] elseif select('#',...) == 3 then if type(args[3]) == 'string' then if type(args[2]) == 'string' or type(args[2]) == 'number' then src = args[1] size = args[2] mode = args[3] else dst = args[1] src = args[2] mode = args[3] end else src = args[1] width = args[2] height = args[3] end elseif select('#',...) == 2 then if type(args[2]) == 'string' or type(args[2]) == 'number' then src = args[1] size = args[2] else dst = args[1] src = args[2] end else print(dok.usage('image.scale', 'rescale an image (geometry)', nil, {type='torch.Tensor', help='input image', req=true}, {type='number', help='destination width', req=true}, {type='number', help='destination height', req=true}, {type='string', help='mode: bilinear | bicubic |simple', default='bilinear'}, '', {type='torch.Tensor', help='input image', req=true}, {type='string | number', help='destination size: "WxH" or "MAX" or "^MIN" or "*SC" or "*SCd/SCn" or MAX', req=true}, {type='string', help='mode: bilinear | bicubic | simple', default='bilinear'}, '', {type='torch.Tensor', help='destination image', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='string', help='mode: bilinear | bicubic | simple', default='bilinear'})) dok.error('incorrect arguments', 'image.scale') end if size then local iwidth, iheight if src:nDimension() == 3 then iwidth, iheight = src:size(3),src:size(2) else iwidth, iheight = src:size(2),src:size(1) end -- MAX? local imax = math.max(iwidth, iheight) local omax = tonumber(size) if omax then height = iheight*omax/imax width = iwidth*omax/imax end -- WxH? if not width or not height then width, height = size:match('(%d+)x(%d+)') end -- ^MIN? if not width or not height then local imin = math.min(iwidth, iheight) local omin = tonumber(size:match('%^(%d+)')) if omin then height = iheight*omin/imin width = iwidth*omin/imin end end -- *SCn/SCd? if not width or not height then local scn, scd = size:match('%*(%d+)%/(%d+)') if scn and scd then height = iheight*scn/scd width = iwidth*scn/scd end end -- *SC? if not width or not height then local sc = tonumber(size:match('%*(.+)')) if sc then height = iheight*sc width = iwidth*sc end end end if not dst and (not width or not height) then dok.error('could not find valid dest size', 'image.scale') end if not dst then height = math.max(height, 1) width = math.max(width, 1) if src:nDimension() == 3 then dst = src.new(src:size(1), height, width) else dst = src.new(height, width) end end mode = mode or 'bilinear' if mode=='bilinear' then src.image.scaleBilinear(src,dst) elseif mode=='bicubic' then src.image.scaleBicubic(src,dst) elseif mode=='simple' then src.image.scaleSimple(src,dst) else dok.error('mode must be one of: simple | bicubic | bilinear', 'image.scale') end return dst end rawset(image, 'scale', scale) ---------------------------------------------------------------------- -- rotate -- local function rotate(...) local dst,src,theta, mode local args = {...} if select('#',...) == 4 then dst = args[1] src = args[2] theta = args[3] mode = args[4] elseif select('#',...) == 3 then if type(args[2]) == 'number' then src = args[1] theta = args[2] mode = args[3] else dst = args[1] src = args[2] theta = args[3] end elseif select('#',...) == 2 then src = args[1] theta = args[2] else print(dok.usage('image.rotate', 'rotate an image by theta radians', nil, {type='torch.Tensor', help='input image', req=true}, {type='number', help='rotation angle (in radians)', req=true}, {type='string', help='mode: simple | bilinear', default='simple'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='number', help='rotation angle (in radians)', req=true}, {type='string', help='mode: simple | bilinear', default='simple'})) dok.error('incorrect arguments', 'image.rotate') end dst = dst or src.new() dst:resizeAs(src) mode = mode or 'simple' if mode == 'simple' then src.image.rotate(src,dst,theta) elseif mode == 'bilinear' then src.image.rotateBilinear(src,dst,theta) else dok.error('mode must be one of: simple | bilinear', 'image.rotate') end return dst end rawset(image, 'rotate', rotate) ---------------------------------------------------------------------- -- polar -- local function polar(...) local dst,src,interp,mode local args = {...} if select('#',...) == 4 then dst = args[1] src = args[2] interp = args[3] mode = args[4] elseif select('#',...) == 3 then if type(args[2]) == 'string' then src = args[1] interp = args[2] mode = args[3] else dst = args[1] src = args[2] interp = args[3] end elseif select('#',...) == 2 then if type(args[2]) == 'string' then src = args[1] interp = args[2] else dst = args[1] src = args[2] end elseif select('#',...) == 1 then src = args[1] else print(dok.usage('image.polar', 'convert an image to polar coordinates', nil, {type='torch.Tensor', help='input image', req=true}, {type='string', help='interpolation: simple | bilinear', default='simple'}, {type='string', help='mode: valid | full', default='valid'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='string', help='interpolation: simple | bilinear', default='simple'}, {type='string', help='mode: valid | full', default='valid'})) dok.error('incorrect arguments', 'image.polar') end interp = interp or 'valid' mode = mode or 'simple' if dst == nil then local maxDist = math.floor(math.max(src:size(2), src:size(3))) dst = src.new() dst:resize(src:size(1), maxDist, maxDist) end if interp == 'simple' then if mode == 'full' then src.image.polar(src,dst,1) elseif mode == 'valid' then src.image.polar(src,dst,0) else dok.error('mode must be one of: valid | full', 'image.polar') end elseif interp == 'bilinear' then if mode == 'full' then src.image.polarBilinear(src,dst,1) elseif mode == 'valid' then src.image.polarBilinear(src,dst,0) else dok.error('mode must be one of: valid | full', 'image.polar') end else dok.error('interpolation must be one of: simple | bilinear', 'image.polar') end return dst end rawset(image, 'polar', polar) ---------------------------------------------------------------------- -- logpolar -- local function logpolar(...) local dst,src,interp,mode local args = {...} if select('#',...) == 4 then dst = args[1] src = args[2] interp = args[3] mode = args[4] elseif select('#',...) == 3 then if type(args[2]) == 'string' then src = args[1] interp = args[2] mode = args[3] else dst = args[1] src = args[2] interp = args[3] end elseif select('#',...) == 2 then if type(args[2]) == 'string' then src = args[1] interp = args[2] else dst = args[1] src = args[2] end elseif select('#',...) == 1 then src = args[1] else print(dok.usage('image.logpolar', 'convert an image to log-polar coordinates', nil, {type='torch.Tensor', help='input image', req=true}, {type='string', help='interpolation: simple | bilinear', default='simple'}, {type='string', help='mode: valid | full', default='valid'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='string', help='interpolation: simple | bilinear', default='simple'}, {type='string', help='mode: valid | full', default='valid'})) dok.error('incorrect arguments', 'image.polar') end interp = interp or 'valid' mode = mode or 'simple' if dst == nil then local maxDist = math.floor(math.max(src:size(2), src:size(3))) dst = src.new() dst:resize(src:size(1), maxDist, maxDist) end if interp == 'simple' then if mode == 'full' then src.image.logPolar(src,dst,1) elseif mode == 'valid' then src.image.logPolar(src,dst,0) else dok.error('mode must be one of: valid | full', 'image.logpolar') end elseif interp == 'bilinear' then if mode == 'full' then src.image.logPolarBilinear(src,dst,1) elseif mode == 'valid' then src.image.logPolarBilinear(src,dst,0) else dok.error('mode must be one of: valid | full', 'image.logpolar') end else dok.error('interpolation must be one of: simple | bilinear', 'image.logpolar') end return dst end rawset(image, 'logpolar', logpolar) ---------------------------------------------------------------------- -- warp -- local function warp(...) local dst,src,field local mode = 'bilinear' local offset_mode = true local clamp_mode = 'clamp' local pad_value = 0 local args = {...} local nargs = select('#',...) local bad_args = false if nargs == 2 then src = args[1] field = args[2] elseif nargs >= 3 then if type(args[3]) == 'string' then -- No destination tensor src = args[1] field = args[2] mode = args[3] if nargs >= 4 then offset_mode = args[4] end if nargs >= 5 then clamp_mode = args[5] end if nargs >= 6 then assert(clamp_mode == 'pad', 'pad_value can only be specified if' .. ' clamp_mode = "pad"') pad_value = args[6] end if nargs >= 7 then bad_args = true end else -- With Destination tensor dst = args[1] src = args[2] field = args[3] if nargs >= 4 then mode = args[4] end if nargs >= 5 then offset_mode = args[5] end if nargs >= 6 then clamp_mode = args[6] end if nargs >= 7 then assert(clamp_mode == 'pad', 'pad_value can only be specified if' .. ' clamp_mode = "pad"') pad_value = args[7] end if nargs >= 8 then bad_args = true end end end if bad_args then print(dok.usage('image.warp', 'warp an image, according to a flow field', nil, {type='torch.Tensor', help='input image (KxHxW)', req=true}, {type='torch.Tensor', help='(y,x) flow field (2xHxW)', req=true}, {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'}, {type='string', help='offset mode (add (x,y) to flow field)', default=true}, {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image (KxHxW)', req=true}, {type='torch.Tensor', help='(y,x) flow field (2xHxW)', req=true}, {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'}, {type='string', help='offset mode (add (x,y) to flow field)', default=true}, {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'}, {type='number', help='pad value: value to pad image. Can only be set when clamp mode equals "pad"', default=0})) dok.error('incorrect arguments', 'image.warp') end -- This is a little messy, but convert mode string to an enum if (mode == 'simple') then mode = 0 elseif (mode == 'bilinear') then mode = 1 elseif (mode == 'bicubic') then mode = 2 elseif (mode == 'lanczos') then mode = 3 else dok.error('Incorrect arguments (mode is not lanczos | bicubic | bilinear | simple)!', 'image.warp') end if (clamp_mode == 'clamp') then clamp_mode = 0 elseif (clamp_mode == 'pad') then clamp_mode = 1 else dok.error('Incorrect arguments (clamp_mode is not clamp | pad)!', 'image.warp') end local dim2 = false if src:nDimension() == 2 then dim2 = true src = src:reshape(1,src:size(1),src:size(2)) end dst = dst or src.new() dst:resize(src:size(1), field:size(2), field:size(3)) src.image.warp(dst, src, field, mode, offset_mode, clamp_mode, pad_value) if dim2 then dst = dst[1] end return dst end rawset(image, 'warp', warp) ---------------------------------------------------------------------- -- affine transform -- local function affinetransform(...) local dst,src,matrix local mode = 'bilinear' local translation = torch.Tensor{0,0} local clamp_mode = 'clamp' local pad_value = 0 local args = {...} local nargs = select('#',...) local bad_args = false if nargs == 2 then src = args[1] matrix = args[2] elseif nargs >= 3 then if type(args[3]) == 'string' then -- No destination tensor src = args[1] matrix = args[2] mode = args[3] if nargs >= 4 then translation = args[4] end if nargs >= 5 then clamp_mode = args[5] end if nargs >= 6 then assert(clamp_mode == 'pad', 'pad_value can only be specified if' .. ' clamp_mode = "pad"') pad_value = args[6] end if nargs >= 7 then bad_args = true end else -- With Destination tensor dst = args[1] src = args[2] matrix = args[3] if nargs >= 4 then mode = args[4] end if nargs >= 5 then translation = args[5] end if nargs >= 6 then clamp_mode = args[6] end if nargs >= 7 then assert(clamp_mode == 'pad', 'pad_value can only be specified if' .. ' clamp_mode = "pad"') pad_value = args[7] end if nargs >= 8 then bad_args = true end end end if bad_args then print(dok.usage('image.warp', 'warp an image, according to given affine transform', nil, {type='torch.Tensor', help='input image (KxHxW)', req=true}, {type='torch.Tensor', help='(y,x) affine translation matrix', req=true}, {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'}, {type='torch.Tensor', help='extra (y,x) translation to be done before transform', default=torch.Tensor{0,0}}, {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'}, '', {type='torch.Tensor', help='input image (KxHxW)', req=true}, {type='torch.Tensor', help='(y,x) affine translation matrix', req=true}, {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'}, {type='torch.Tensor', help='extra (y,x) translation to be done before transform', default=torch.Tensor{0,0}}, {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'}, {type='number', help='pad value: value to pad image. Can only be set when clamp mode equals "pad"', default=0})) dok.error('incorrect arguments', 'image.warp') end -- This is a little messy, but convert mode string to an enum if (mode == 'simple') then mode = 0 elseif (mode == 'bilinear') then mode = 1 elseif (mode == 'bicubic') then mode = 2 elseif (mode == 'lanczos') then mode = 3 else dok.error('Incorrect arguments (mode is not lanczos | bicubic | bilinear | simple)!', 'image.warp') end if (clamp_mode == 'clamp') then clamp_mode = 0 elseif (clamp_mode == 'pad') then clamp_mode = 1 else dok.error('Incorrect arguments (clamp_mode is not clamp | pad)!', 'image.warp') end local dim2 = false if src:nDimension() == 2 then dim2 = true src = src:reshape(1,src:size(1),src:size(2)) end dst = dst or src.new() dst:resize(src:size(1), src:size(2), src:size(3)) -- create field local height = src:size(2) local width = src:size(3) local grid_y = torch.ger( torch.linspace(-1,1,height), torch.ones(width) ) local grid_x = torch.ger( torch.ones(height), torch.linspace(-1,1,width) ) local grid_xy = torch.Tensor() grid_xy:resize(2,height,width) grid_xy[1] = grid_y * ((height-1)/2) * -1 grid_xy[2] = grid_x * ((width-1)/2) * -1 local view_xy = grid_xy:reshape(2,height*width) local matrix = matrix:typeAs(torch.Tensor()) -- coerce matrix to default tensor type local field = torch.mm(matrix, view_xy) field = (grid_xy - field:reshape( 2, height, width )) -- offset field for translation translation = torch.Tensor(translation) field[1] = field[1] - translation[1] field[2] = field[2] - translation[2] local offset_mode = true src.image.warp(dst, src, field, mode, offset_mode, clamp_mode, pad_value) if dim2 then dst = dst[1] end return dst end rawset(image, 'affinetransform', affinetransform) ---------------------------------------------------------------------- -- hflip -- local function hflip(...) local dst,src local args = {...} if select('#',...) == 2 then dst = args[1] src = args[2] elseif select('#',...) == 1 then src = args[1] else print(dok.usage('image.hflip', 'flips an image horizontally (left/right)', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true})) dok.error('incorrect arguments', 'image.hflip') end if (src:dim() == 2) and (not src:isContiguous()) then dok.error('2D input tensor is not contiguous', 'image.hflip') end dst = dst or src.new() local original_size = src:size() if src:nDimension() == 2 then src = src:new():resize(1,src:size(1),src:size(2)) end dst:resizeAs(src) if not dst:isContiguous() then dok.error('destination tensor is not contiguous', 'image.hflip') end dst.image.hflip(dst, src) dst:resize(original_size) return dst end rawset(image, 'hflip', hflip) ---------------------------------------------------------------------- -- vflip -- local function vflip(...) local dst,src local args = {...} if select('#',...) == 2 then dst = args[1] src = args[2] elseif select('#',...) == 1 then src = args[1] else print(dok.usage('image.vflip', 'flips an image vertically (upside-down)', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true})) dok.error('incorrect arguments', 'image.vflip') end if (src:dim() == 2) and (not src:isContiguous()) then dok.error('2D input tensor is not contiguous', 'image.vflip') end dst = dst or src.new() local original_size = src:size() if src:nDimension() == 2 then src = src:new():resize(1,src:size(1),src:size(2)) end dst:resizeAs(src) if not dst:isContiguous() then dok.error('destination tensor is not contiguous', 'image.vflip') end dst.image.vflip(dst, src) dst:resize(original_size) return dst end rawset(image, 'vflip', vflip) ---------------------------------------------------------------------- -- flip (specify dimension, up to 5D tensor) -- local function flip(...) local dst,src,flip_dim local args = {...} if select('#',...) == 3 then dst = args[1] src = args[2] flip_dim = args[3] elseif select('#',...) == 2 then src = args[1] flip_dim = args[2] else print(dok.usage('image.flip', 'flips an image along a specified dimension', nil, {type='torch.Tensor', help='input image', req=true}, {type='number', help='Dimension to flip', req=true}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='number', help='Dimension to flip', req=true})) dok.error('incorrect arguments', 'image.flip') end assert(src:nDimension() <= 5, 'too many input dims (up to 5D supported)') assert(flip_dim <= src:nDimension() and flip_dim >= 1, 'Bad flip dimension') if not src:isContiguous() then dok.error('input tensor is not contiguous', 'image.flip') end dst = dst or src.new() local original_size = src:size() local flip_dim_cpp if src:nDimension() == 1 then src = src:new():resize(1, 1, 1, 1, src:size(1)) flip_dim_cpp = flip_dim + 4 elseif src:nDimension() == 2 then src = src:new():resize(1, 1, 1, src:size(1), src:size(2)) flip_dim_cpp = flip_dim + 3 elseif src:nDimension() == 3 then src = src:new():resize(1, 1, src:size(1), src:size(2),src:size(3)) flip_dim_cpp = flip_dim + 2 elseif src:nDimension() == 4 then src = src:new():resize(1, src:size(1), src:size(2), src:size(3), src:size(4)) flip_dim_cpp = flip_dim + 1 else flip_dim_cpp = flip_dim end dst:resizeAs(src) if not dst:isContiguous() then dok.error('destination tensor is not contiguous', 'image.flip') end dst.image.flip(dst, src, flip_dim_cpp) dst:resize(original_size) return dst end rawset(image, 'flip', flip) ---------------------------------------------------------------------- -- convolve(dst,src,ker,type) -- convolve(dst,src,ker) -- dst = convolve(src,ker,type) -- dst = convolve(src,ker) -- local function convolve(...) local dst,src,kernel,mode local args = {...} if select('#',...) == 4 then dst = args[1] src = args[2] kernel = args[3] mode = args[4] elseif select('#',...) == 3 then if type(args[3]) == 'string' then src = args[1] kernel = args[2] mode = args[3] else dst = args[1] src = args[2] kernel = args[3] end elseif select('#',...) == 2 then src = args[1] kernel = args[2] else print(dok.usage('image.convolve', 'convolves an input image with a kernel, returns the result', nil, {type='torch.Tensor', help='input image', req=true}, {type='torch.Tensor', help='kernel', req=true}, {type='string', help='type: full | valid | same', default='valid'}, '', {type='torch.Tensor', help='destination', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='torch.Tensor', help='kernel', req=true}, {type='string', help='type: full | valid | same', default='valid'})) dok.error('incorrect arguments', 'image.convolve') end if mode and mode ~= 'valid' and mode ~= 'full' and mode ~= 'same' then dok.error('mode has to be one of: full | valid | same', 'image.convolve') end local md = (((mode == 'full') or (mode == 'same')) and 'F') or 'V' if kernel:nDimension() == 2 and src:nDimension() == 3 then local k3d = src.new(src:size(1), kernel:size(1), kernel:size(2)) for i = 1,src:size(1) do k3d[i]:copy(kernel) end kernel = k3d end if dst then torch.conv2(dst,src,kernel,md) else dst = torch.conv2(src,kernel,md) end if mode == 'same' then local cx = dst:dim() local cy = cx-1 local ofy = math.ceil(kernel:size(cy)/2) local ofx = math.ceil(kernel:size(cx)/2) dst = dst:narrow(cy, ofy, src:size(cy)):narrow(cx, ofx, src:size(cx)) end return dst end rawset(image, 'convolve', convolve) ---------------------------------------------------------------------- -- compresses an image between min and max -- local function minmax(args) local tensor = args.tensor local min = args.min local max = args.max local symm = args.symm or false local inplace = args.inplace or false local saturate = args.saturate or false local tensorOut = args.tensorOut or (inplace and tensor) or torch.Tensor(tensor:size()):copy(tensor) -- resize if args.tensorOut then tensorOut:resize(tensor:size()):copy(tensor) end -- saturate useless if min/max inferred if min == nil and max == nil then saturate = false end -- rescale min local fmin = 0 if (min == nil) then if args.symm then fmin = math.max(math.abs(tensorOut:min()),math.abs(tensorOut:max())) min = -fmin else min = tensorOut:min() end end if (min ~= 0) then tensorOut:add(-min) end -- rescale for max if (max == nil) then if args.symm then max = fmin*2 else max = tensorOut:max() end else max = max - min end if (max ~= 0) then tensorOut:div(max) end -- saturate if saturate then tensorOut.image.saturate(tensorOut) end -- and return return tensorOut end rawset(image, 'minmax', minmax) local function toDisplayTensor(...) -- usage local _, input, padding, nrow, scaleeach, min, max, symm, saturate = dok.unpack( {...}, 'image.toDisplayTensor', 'given a pack of tensors, returns a single tensor that contains a grid of all in the pack', {arg='input',type='torch.Tensor | table', help='input (HxW or KxHxW or Kx3xHxW or list)',req=true}, {arg='padding', type='number', help='number of padding pixels between images', default=0}, {arg='nrow',type='number',help='number of images per row', default=6}, {arg='scaleeach', type='boolean', help='individual scaling for list of images', default=false}, {arg='min', type='number', help='lower-bound for range'}, {arg='max', type='number', help='upper-bound for range'}, {arg='symmetric',type='boolean',help='if on, images will be displayed using a symmetric dynamic range, useful for drawing filters', default=false}, {arg='saturate', type='boolean', help='saturate (useful when min/max are lower than actual min/max', default=true} ) local packed = torch.Tensor() if type(input) == 'table' then -- pack images in single tensor local ndims = input[1]:dim() local channels = ((ndims == 2) and 1) or input[1]:size(1) local height = input[1]:size(ndims-1) local width = input[1]:size(ndims) packed:resize(#input,channels,height,width) for i,img in ipairs(input) do packed[i]:copy(input[i]) end elseif torch.isTensor(input) then packed:resize(input:size()):copy(input) else error('Unknown or incompatbile type of input: ' .. torch.type(input)) end -- scale each if scaleeach and ( (packed:dim() == 4 and (packed:size(2) == 3 or packed:size(2) == 1)) or (packed:dim() == 3 and (packed:size(1) ~= 1 and packed:size(1) ~= 3)) ) then for i=1,packed:size(1) do image.minmax{tensor=packed[i], inplace=true, min=min, max=max, symm=symm, saturate=saturate} end end local grid = torch.Tensor() if packed:dim() == 4 and (packed:size(2) == 3 or packed:size(2) == 1) then -- arbitrary number of color images: lay them out on a grid local nmaps = packed:size(1) local xmaps = math.min(nrow, nmaps) local ymaps = math.ceil(nmaps / xmaps) local height = packed:size(3)+padding local width = packed:size(4)+padding grid:resize(packed:size(2), height*ymaps, width*xmaps):fill(packed:max()) local k = 1 for y = 1,ymaps do for x = 1,xmaps do if k > nmaps then break end grid:narrow(2, math.floor((y-1)*height+1+padding/2), math.floor(height-padding)) :narrow(3, math.floor((x-1)*width+1+padding/2), math.floor(width-padding)) :copy(packed[k]) k = k + 1 end end elseif packed:dim() == 2 or (packed:dim() == 3 and (packed:size(1) == 1 or packed:size(1) == 3)) then -- Rescale range image.minmax{tensor=packed, inplace=true, min=min, max=max, symm=symm, saturate=saturate} return packed elseif packed:dim() == 3 then -- arbitrary number of channels: lay them out on a grid local nmaps = packed:size(1) local xmaps = math.min(nrow, nmaps) local ymaps = math.ceil(nmaps / xmaps) local height = packed:size(2)+padding local width = packed:size(3)+padding grid:resize(height*ymaps, width*xmaps):fill(packed:max()) local k = 1 for y = 1,ymaps do for x = 1,xmaps do if k > nmaps then break end grid:narrow(1, math.floor((y-1)*height+1+padding/2),math.floor(height-padding)) :narrow(2,math.floor((x-1)*width+1+padding/2),math.floor(width-padding)) :copy(packed[k]) k = k + 1 end end else xerror('packed must be a HxW or KxHxW or Kx3xHxW tensor, or a list of tensors', 'image.toDisplayTensor') end if not scaleeach then image.minmax{tensor=grid, inplace=true, min=min, max=max, symm=symm, saturate=saturate} end return grid end rawset(image,'toDisplayTensor',toDisplayTensor) ---------------------------------------------------------------------- -- super generic display function -- local function display(...) -- usage local _, input, zoom, min, max, legend, w, ox, oy, scaleeach, gui, offscreen, padding, symm, nrow, saturate = dok.unpack( {...}, 'image.display', 'displays a single image, with optional saturation/zoom', {arg='image', type='torch.Tensor | table', help='image (HxW or KxHxW or Kx3xHxW or list)', req=true}, {arg='zoom', type='number', help='display zoom', default=1}, {arg='min', type='number', help='lower-bound for range'}, {arg='max', type='number', help='upper-bound for range'}, {arg='legend', type='string', help='legend', default='image.display'}, {arg='win', type='qt window', help='window descriptor'}, {arg='x', type='number', help='x offset (only if win is given)', default=0}, {arg='y', type='number', help='y offset (only if win is given)', default=0}, {arg='scaleeach', type='boolean', help='individual scaling for list of images', default=false}, {arg='gui', type='boolean', help='if on, user can zoom in/out (turn off for faster display)', default=true}, {arg='offscreen', type='boolean', help='offscreen rendering (to generate images)', default=false}, {arg='padding', type='number', help='number of padding pixels between images', default=0}, {arg='symmetric',type='boolean',help='if on, images will be displayed using a symmetric dynamic range, useful for drawing filters', default=false}, {arg='nrow',type='number',help='number of images per row', default=6}, {arg='saturate', type='boolean', help='saturate (useful when min/max are lower than actual min/max', default=true} ) -- dependencies require 'qt' require 'qttorch' require 'qtwidget' require 'qtuiloader' input = image.toDisplayTensor{input=input, padding=padding, nrow=nrow, saturate=saturate, scaleeach=scaleeach, min=min, max=max, symmetric=symm} -- if 2 dims or 3 dims and 1/3 channels, then we treat it as a single image if input:nDimension() == 2 or (input:nDimension() == 3 and (input:size(1) == 1 or input:size(1) == 3)) then -- Compute width local d = input:nDimension() local x = input:size(d)*zoom local y = input:size(d-1)*zoom -- if gui active, then create interactive window (with zoom, clicks and so on) if gui and not w and not offscreen then -- create window context local closure = w local hook_resize, hook_mouse if closure and closure.window and closure.image then closure.image = input closure.refresh(x,y) else closure = {image=input} hook_resize = function(wi,he) local qtimg = qt.QImage.fromTensor(closure.image) closure.painter:image(0,0,wi,he,qtimg) collectgarbage() end hook_mouse = function(x,y,button) --local size = closure.window.frame.size:totable() --size.width = --size.height = if button == 'LeftButton' then elseif button == 'RightButton' then end --closure.window.frame.size = qt.QSize(size) end closure.window, closure.painter = image.window(hook_resize,hook_mouse) closure.refresh = hook_resize end closure.window.size = qt.QSize{width=x,height=y} closure.window.windowTitle = legend closure.window:show() hook_resize(x,y) closure.isclosure = true return closure else if offscreen then w = w or qt.QtLuaPainter(x,y) else w = w or qtwidget.newwindow(x,y,legend) end if w.window and not w.window.visible then -- make sure window is visible w.window.visible = true end if w.isclosure then -- window was created with gui, just update closure local closure = w closure.image = input local size = closure.window.size:totable() closure.window.windowTitle = legend closure.refresh(size.width, size.height) else -- if no gui, create plain window, and blit local qtimg = qt.QImage.fromTensor(input) w:image(ox,oy,x,y,qtimg) end end else xerror('image must be a HxW or KxHxW or Kx3xHxW tensor, or a list of tensors', 'image.display') end -- return painter return w end rawset(image, 'display', display) ---------------------------------------------------------------------- -- creates a window context for images -- local function window(hook_resize, hook_mousepress, hook_mousedoublepress) require 'qt' require 'qttorch' require 'qtwidget' require 'qtuiloader' local pathui = paths.concat(fpath(), 'win.ui') local win = qtuiloader.load(pathui) local painter = qt.QtLuaPainter(win.frame) if hook_resize then qt.connect(qt.QtLuaListener(win.frame), 'sigResize(int,int)', hook_resize) end if hook_mousepress then qt.connect(qt.QtLuaListener(win.frame), 'sigMousePress(int,int,QByteArray,QByteArray,QByteArray)', hook_mousepress) end if hook_mousedoublepress then qt.connect(qt.QtLuaListener(win.frame), 'sigMouseDoubleClick(int,int,QByteArray,QByteArray,QByteArray)', hook_mousedoublepress) end local ctrl = false qt.connect(qt.QtLuaListener(win), 'sigKeyPress(QString,QByteArray,QByteArray)', function (str, s2) if s2 and s2 == 'Key_Control' then ctrl = true elseif s2 and s2 == 'Key_W' and ctrl then win:close() else ctrl = false end end) return win,painter end rawset(image, 'window', window) ---------------------------------------------------------------------- -- lena is always useful -- local function lena() local fname = 'grace_hopper_512' if xlua.require 'libjpeg' then lena = image.load(paths.concat(fpath(), 'assets', fname .. '.jpg'), 3) elseif xlua.require 'liblua_png' then lena = image.load(paths.concat(fpath(), 'assets', fname .. '.png'), 3) else dok.error('no bindings available to load images (libjpeg AND libpng missing)', 'image.lena') end return lena end rawset(image, 'lena', lena) ---------------------------------------------------------------------- -- fabio is a nice gender-balancing variation on lena -- See: http://www.claremontmckenna.edu/news/every-picture-tells-a-story/ -- and first use in http://arxiv.org/abs/1202.6429 -- along with original file on http://nuit-blanche.blogspot.co.uk/2012/03/let-there-be-only-one-fabio.html local function fabio() local fname = 'fabio' if xlua.require 'libjpeg' then lena = image.load(paths.concat(fpath(), 'assets', fname .. '.jpg'), 1) elseif xlua.require 'liblua_png' then lena = image.load(paths.concat(fpath(), 'assets', fname .. '.png'), 1) else dok.error('no bindings available to load images (libjpeg AND libpng missing)', 'image.fabio') end return lena end rawset(image, 'fabio', fabio) ---------------------------------------------------------------------- -- image.rgb2yuv(image) -- converts a RGB image to YUV -- function image.rgb2yuv(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2yuv', 'transforms an image from RGB to YUV', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2yuv') end -- resize output = output or input.new() output:resizeAs(input) -- input chanels local inputRed = input[1] local inputGreen = input[2] local inputBlue = input[3] -- output chanels local outputY = output[1] local outputU = output[2] local outputV = output[3] -- convert outputY:zero():add(0.299, inputRed):add(0.587, inputGreen):add(0.114, inputBlue) outputU:zero():add(-0.14713, inputRed):add(-0.28886, inputGreen):add(0.436, inputBlue) outputV:zero():add(0.615, inputRed):add(-0.51499, inputGreen):add(-0.10001, inputBlue) -- return YUV image return output end ---------------------------------------------------------------------- -- image.yuv2rgb(image) -- converts a YUV image to RGB -- function image.yuv2rgb(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.yuv2rgb', 'transforms an image from YUV to RGB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.yuv2rgb') end -- resize output = output or input.new() output:resizeAs(input) -- input chanels local inputY = input[1] local inputU = input[2] local inputV = input[3] -- output chanels local outputRed = output[1] local outputGreen = output[2] local outputBlue = output[3] -- convert outputRed:copy(inputY):add(1.13983, inputV) outputGreen:copy(inputY):add(-0.39465, inputU):add(-0.58060, inputV) outputBlue:copy(inputY):add(2.03211, inputU) -- return RGB image return output end ---------------------------------------------------------------------- -- image.rgb2y(image) -- converts a RGB image to Y (discards U/V) -- function image.rgb2y(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2y', 'transforms an image from RGB to Y', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2y') end -- resize output = output or input.new() output:resize(1, input:size(2), input:size(3)) -- input chanels local inputRed = input[1] local inputGreen = input[2] local inputBlue = input[3] -- output chanels local outputY = output[1] -- convert input.image.rgb2y(input, outputY) -- return YUV image return output end ---------------------------------------------------------------------- -- image.rgb2hsl(image) -- converts an RGB image to HSL -- function image.rgb2hsl(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2hsl', 'transforms an image from RGB to HSL', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2hsl') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.rgb2hsl(input,output) -- return HSL image return output end ---------------------------------------------------------------------- -- image.hsl2rgb(image) -- converts an HSL image to RGB -- function image.hsl2rgb(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.hsl2rgb', 'transforms an image from HSL to RGB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.hsl2rgb') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.hsl2rgb(input,output) -- return HSL image return output end ---------------------------------------------------------------------- -- image.rgb2hsv(image) -- converts an RGB image to HSV -- function image.rgb2hsv(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2hsv', 'transforms an image from RGB to HSV', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2hsv') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.rgb2hsv(input,output) -- return HSV image return output end ---------------------------------------------------------------------- -- image.hsv2rgb(image) -- converts an HSV image to RGB -- function image.hsv2rgb(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.hsv2rgb', 'transforms an image from HSV to RGB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.hsv2rgb') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.hsv2rgb(input,output) -- return HSV image return output end ---------------------------------------------------------------------- -- image.rgb2lab(image) -- converts an RGB image to LAB -- assumes sRGB input in the range [0, 1] -- function image.rgb2lab(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2lab', 'transforms an image from sRGB to LAB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2lab') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.rgb2lab(input,output) -- return LAB image return output end ---------------------------------------------------------------------- -- image.lab2rgb(image) -- converts an LAB image to RGB (assumes sRGB) -- function image.lab2rgb(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.lab2rgb', 'transforms an image from LAB to RGB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.lab2rgb') end -- resize output = output or input.new() output:resizeAs(input) -- compute input.image.lab2rgb(input,output) -- return sRGB image return output end ---------------------------------------------------------------------- -- image.rgb2nrgb(image) -- converts an RGB image to normalized-RGB -- function image.rgb2nrgb(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else print(dok.usage('image.rgb2nrgb', 'transforms an image from RGB to normalized RGB', nil, {type='torch.Tensor', help='input image', req=true}, '', {type='torch.Tensor', help='output image', req=true}, {type='torch.Tensor', help='input image', req=true} )) dok.error('missing input', 'image.rgb2nrgb') end -- resize output = output or input.new() output:resizeAs(input) local sum = input.new() sum:resize(input:size(2), input:size(3)) -- compute sum and normalize sum:copy(input[1]):add(input[2]):add(input[3]):add(1e-6) output:copy(input) output[1]:cdiv(sum) output[2]:cdiv(sum) output[3]:cdiv(sum) -- return HSV image return output end ---------------------------------------------------------------------- -- image.y2jet(image) -- Converts a L-levels (1-L) greyscale image into a jet heat-map -- function image.y2jet(...) -- arg check local output,input local args = {...} if select('#',...) == 2 then output = args[1] input = args[2] elseif select('#',...) == 1 then input = args[1] else error('Invalid input for image.y2jet()') end -- accept 3D grayscale if input:dim() == 3 and input:size(1) == 1 then input = input.new(input):resize(input:size(2), input:size(3)) end -- accept 1D greyscale if input:dim() == 1 then input = input.new(input):resize(1, input:size(1)) end local output = output or input.new() local L = input:max() local colorMap = image.jetColormap(L) if torch.type(input) == 'torch.ByteTensor' then colorMap = colorMap:mul(255):round() colorMap[torch.lt(colorMap, 0)] = 0 colorMap[torch.gt(colorMap, 255)] = 255 colorMap = colorMap:byte() else colorMap = colorMap:typeAs(input) end input.image.colorize(output, input-1, colorMap) return output end -- color, bgcolor, size, wrap, inplace function image.drawText(src, text, x, y, opts) opts = opts or {} assert(torch.isTensor(src) and src:dim() == 3 and src:size(1) == 3, "input image has to be a 3D tensor of shape 3 x H x W ") local out = src if not opts.inplace then out = src:clone() end if not text or text:gsub("%s*$", "") == '' then return out end x = x or 1 y = y or 1 local color = opts.color or {255, 0, 0} -- red default local bgcolor = opts.bg or {-1, -1, -1} -- no bgcolor default local size = opts.size or 1 if opts.wrap == nil then opts.wrap = true end -- to wrap long lines or not src.image.text(out, text, x, y, size, color[1], color[2], color[3], bgcolor[1], bgcolor[2], bgcolor[3], opts.wrap and 1 or 0) return out end ---------------------------------------------------------------------- --- Draw a rectangle on the image -- -- color, bgcolor, size, wrap, inplace function image.drawRect(src, x1, y1, x2, y2, opts) opts = opts or {} assert(torch.isTensor(src) and src:dim() == 3 and src:size(1) == 3, "input image has to be a 3D tensor of shape 3 x H x W ") local out = src if not opts.inplace then out = src:clone() end if not (x1 and x2 and y1 and y2) then return out end local color = opts.color or {255, 0, 0} -- red default local lineWidth = opts.lineWidth or 1 src.image.drawRect(out, x1, y1, x2, y2, lineWidth, color[1], color[2], color[3]) return out end ---------------------------------------------------------------------- --- Returns a gaussian kernel. -- function image.gaussian(...) -- process args local _, size, sigma, amplitude, normalize, width, height, sigma_horz, sigma_vert, mean_horz, mean_vert, tensor = dok.unpack( {...}, 'image.gaussian', 'returns a 2D gaussian kernel', {arg='size', type='number', help='kernel size (size x size)', default=3}, {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.25}, {arg='amplitude', type='number', help='amplitute of the gaussian (max value)', default=1}, {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false}, {arg='width', type='number', help='kernel width', defaulta='size'}, {arg='height', type='number', help='kernel height', defaulta='size'}, {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'}, {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'}, {arg='mean_horz', type='number', help='horizontal mean', default=0.5}, {arg='mean_vert', type='number', help='vertical mean', default=0.5}, {arg='tensor', type='torch.Tensor', help='result tensor (height/width are ignored)'} ) if tensor then assert(tensor:dim() == 2, "expecting 2D tensor") assert(tensor:nElement() > 0, "expecting non-empty tensor") end -- generate kernel local gauss = tensor or torch.Tensor(height, width) gauss.image.gaussian(gauss, amplitude, normalize, sigma_horz, sigma_vert, mean_horz, mean_vert) return gauss end function image.gaussian1D(...) -- process args local _, size, sigma, amplitude, normalize, mean, tensor = dok.unpack( {...}, 'image.gaussian1D', 'returns a 1D gaussian kernel', {arg='size', type='number', help='size the kernel', default=3}, {arg='sigma', type='number', help='Sigma', default=0.25}, {arg='amplitude', type='number', help='Amplitute of the gaussian (max value)', default=1}, {arg='normalize', type='number', help='Normalize kernel (exc Amplitude)', default=false}, {arg='mean', type='number', help='Mean', default=0.5}, {arg='tensor', type='torch.Tensor', help='result tensor (size is ignored)'} ) -- local vars if tensor then assert(tensor:dim() == 1, "expecting 1D tensor") assert(tensor:nElement() > 0, "expecting non-empty tensor") size = tensor:size(1) end local center = mean * size + 0.5 -- generate kernel local gauss = tensor or torch.Tensor(size) for i=1,size do gauss[i] = amplitude * math.exp(-(math.pow((i-center) /(sigma*size),2)/2)) end if normalize then gauss:div(gauss:sum()) end return gauss end ---------------------------------------------------------------------- --- Returns a Laplacian kernel. -- function image.laplacian(...) -- process args local _, size, sigma, amplitude, normalize, width, height, sigma_horz, sigma_vert, mean_horz, mean_vert = dok.unpack( {...}, 'image.laplacian', 'returns a 2D Laplacian kernel', {arg='size', type='number', help='kernel size (size x size)', default=3}, {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.1}, {arg='amplitude', type='number', help='amplitute of the Laplacian (max value)', default=1}, {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false}, {arg='width', type='number', help='kernel width', defaulta='size'}, {arg='height', type='number', help='kernel height', defaulta='size'}, {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'}, {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'}, {arg='mean_horz', type='number', help='horizontal mean', default=0.5}, {arg='mean_vert', type='number', help='vertical mean', default=0.5} ) -- local vars local center_x = mean_horz * width + 0.5 local center_y = mean_vert * height + 0.5 -- generate kernel local logauss = torch.Tensor(height,width) for i=1,height do for j=1,width do local xsq = math.pow((i-center_x)/(sigma_horz*width),2)/2 local ysq = math.pow((j-center_y)/(sigma_vert*height),2)/2 local derivCoef = 1 - (xsq + ysq) logauss[i][j] = derivCoef * amplitude * math.exp(-(xsq + ysq)) end end if normalize then logauss:div(logauss:sum()) end return logauss end ---------------------------------------------------------------------- --- Gaussian Pyramid -- function image.gaussianpyramid(...) local dst,src,scales local args = {...} if select('#',...) == 3 then dst = args[1] src = args[2] scales = args[3] elseif select('#',...) == 2 then dst = {} src = args[1] scales = args[2] else print(dok.usage('image.gaussianpyramid', 'construct a Gaussian pyramid from an image', nil, {type='torch.Tensor', help='input image', req=true}, {type='table', help='list of scales', req=true}, '', {type='table', help='destination (list of Tensors)', req=true}, {type='torch.Tensor', help='input image', req=true}, {type='table', help='list of scales', req=true})) dok.error('incorrect arguments', 'image.gaussianpyramid') end if src:nDimension() == 2 then for i = 1,#scales do dst[i] = dst[i] or src.new() dst[i]:resize(src:size(1)*scales[i], src:size(2)*scales[i]) end elseif src:nDimension() == 3 then for i = 1,#scales do dst[i] = dst[i] or src.new() dst[i]:resize(src:size(1), src:size(2)*scales[i], src:size(3)*scales[i]) end else dok.error('src image must be 2D or 3D', 'image.gaussianpyramid') end local k = image.gaussian{width=3, normalize=true}:typeAs(src) local tmp = src for i = 1,#scales do if scales[i] == 1 then dst[i][{}] = tmp else image.scale(dst[i], tmp, 'simple') end tmp = image.convolve(dst[i], k, 'same') end return dst end ---------------------------------------------------------------------- --- Creates an optimally-spaced RGB color mapping -- function image.colormap(nbColor) -- note: the best way of obtaining optimally-spaced -- colors is to generate them around the HSV wheel, -- by varying the Hue component local map = torch.Tensor(nbColor,3) local huef = 0 local satf = 0 for i = 1,nbColor do -- HSL local hue = math.fmod(huef,360) local sat = math.fmod(satf,0.7) + 0.3 local light = 0.5 huef = huef + 39 satf = satf + 1/9 -- HSL -> RGB local c = (1 - math.abs(2*light-1))*sat local huep = hue/60 local x = c*(1-math.abs(math.fmod(huep,2)-1)) local redp local greenp local bluep if huep < 1 then redp = c; greenp = x; bluep = 0 elseif huep < 2 then redp = x; greenp = c; bluep = 0 elseif huep < 3 then redp = 0; greenp = c; bluep = x elseif huep < 4 then redp = 0; greenp = x; bluep = c elseif huep < 5 then redp = x; greenp = 0; bluep = c else redp = c; greenp = 0; bluep = x end local m = light - c/2 map[i][1] = redp + m map[i][2] = greenp + m map[i][3] = bluep + m end return map end ---------------------------------------------------------------------- --- Creates a jet color mapping - Inspired by http://www.metastine.com/?p=7 -- function image.jetColormap(nbColor) local map = torch.Tensor(nbColor,3) for i = 1,nbColor do local fourValue = 4 * i / nbColor map[i][1] = math.max(math.min(fourValue - 1.5, -fourValue + 4.5, 1),0) map[i][2] = math.max(math.min(fourValue - .5, -fourValue + 3.5, 1),0) map[i][3] = math.max(math.min(fourValue + .5, -fourValue + 2.5, 1),0) end return map end ------------------------------------------------------------------------ --- Local contrast normalization of an image -- -- local contrast normalization on a given image tensor using kernel ker. -- If kernel is not given, then a default 9x9 gaussian will be used function image.lcn(im,ker) ker = ker or image.gaussian({size=9,sigma=1.591/9,normalize=true}) local im = im:clone():type('torch.DoubleTensor') if not(im:dim() == 2 or (im:dim() == 3 and im:size(1) == 1)) then error('grayscale image expected') end if im:dim() == 3 then im = im[1] end local mn = im:mean() local sd = im:std() -- print(ker) -- 1. subtract the mean and divide by the standard deviation im:add(-mn) im:div(sd) -- 2. calculate local mean and std and normalize each pixel -- mean local lmn = torch.conv2(im, ker) -- variance local imsq = im:clone():cmul(im) local lmnsq = torch.conv2(imsq, ker) local lvar = lmn:clone():cmul(lmn) lvar:add(-1,lmnsq):mul(-1) -- avoid numerical errors lvar:apply(function(x) if x < 0 then return 0 end end) -- standard deviation local lstd = lvar:sqrt() lstd:apply(function (x) if x < 1 then return 1 end end) -- apply normalization local shifti = math.floor(ker:size(1)/2)+1 local shiftj = math.floor(ker:size(2)/2)+1 --print(shifti,shiftj,lstd:size(),im:size()) local dim = im:narrow(1,shifti,lstd:size(1)):narrow(2,shiftj,lstd:size(2)):clone() dim:add(-1,lmn) dim:cdiv(lstd) return dim:clone() end ------------------------------------------------------------------------ --- Morphological erosion function image.erode(im,kern,pad) if not im then print(dok.usage("image.erode", "Morphological erosion for odd dimension kernels",nil, {type="torch.Tensor",help="binary image of 0 and 1",req=true}, {type="torch.Tensor",help="morphological kernel of 0 and 1; default is 3x3"}, {type="number",help="value to assume outside boundary; default is 1"})) dok.error("missing image","image.erode") end -- Default kernel is 3x3 local kern = kern or torch.ones(3,3):typeAs(im) local pad = pad or 1 -- Padding the image local hpad = kern:size(1)/2-0.5 local wpad = kern:size(2)/2-0.5 local padded = torch.zeros(im:size(1)+2*hpad,im:size(2)+2*wpad):fill(pad):typeAs(im) padded[{{hpad+1,im:size(1)+hpad},{wpad+1,im:size(2)+wpad}}]:copy(im) -- Do convolution local n = kern:sum() local conv = padded:conv2(kern) -- Do erosion return conv:eq(n):typeAs(im) end ------------------------------------------------------------------------ --- Morphological dilation function image.dilate(im,kern,pad) if not im then print(dok.usage("image.dilate", "Morphological dilation for odd dimension kernels",nil, {type="torch.Tensor",help="binary image of 0 and 1",req=true}, {type="torch.Tensor",help="morphological kernel of 0 and 1; default is 3x3"}, {type="number",help="value to assume outside boundary; default is 0"})) dok.error("missing image","image.dilate") end -- Default kernel is 3x3 local kern = kern or torch.ones(3,3):typeAs(im) kern = image.hflip(image.vflip(kern)) local pad = pad or 0 -- Padding the image local hpad = kern:size(1)/2-0.5 local wpad = kern:size(2)/2-0.5 local padded = torch.zeros(im:size(1)+2*hpad,im:size(2)+2*wpad):fill(pad):typeAs(im) padded[{{hpad+1,im:size(1)+hpad},{wpad+1,im:size(2)+wpad}}]:copy(im) -- Do convolution local conv = padded:conv2(kern) -- Do erosion return conv:gt(0):typeAs(im) end return image jpeg.c000066400000000000000000000032551307617767500121770ustar00rootroot00000000000000 #include #include #include #include #if LUA_VERSION_NUM >= 503 #define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) #endif #define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME) #define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor) #define libjpeg_(NAME) TH_CONCAT_3(libjpeg_, Real, NAME) static void jpeg_mem_src_dummy(j_decompress_ptr c, unsigned char *ibuf, unsigned long isiz) { } static void jpeg_mem_dest_dummy(j_compress_ptr c, unsigned char **obuf, unsigned long *osiz) { } #define JPEG_MEM_SRC_NOT_DEF "`jpeg_mem_src` is not defined." #define JPEG_MEM_DEST_NOT_DEF "`jpeg_mem_dest` is not defined." #define JPEG_REQUIRED_VERSION " Use libjpeg v8+, libjpeg-turbo 1.3+ or build" \ " libjpeg-turbo with `--with-mem-srcdst`." #define JPEG_MEM_SRC_ERR_MSG JPEG_MEM_SRC_NOT_DEF JPEG_REQUIRED_VERSION #define JPEG_MEM_DEST_ERR_MSG JPEG_MEM_DEST_NOT_DEF JPEG_REQUIRED_VERSION #if !defined(HAVE_JPEG_MEM_SRC) #define jpeg_mem_src jpeg_mem_src_dummy #endif #if !defined(HAVE_JPEG_MEM_DEST) #define jpeg_mem_dest jpeg_mem_dest_dummy #endif #include "generic/jpeg.c" #include "THGenerateAllTypes.h" DLL_EXPORT int luaopen_libjpeg(lua_State *L) { libjpeg_FloatMain_init(L); libjpeg_DoubleMain_init(L); libjpeg_ByteMain_init(L); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "libjpeg"); lua_newtable(L); luaT_setfuncs(L, libjpeg_DoubleMain__, 0); lua_setfield(L, -2, "double"); lua_newtable(L); luaT_setfuncs(L, libjpeg_FloatMain__, 0); lua_setfield(L, -2, "float"); lua_newtable(L); luaT_setfuncs(L, libjpeg_ByteMain__, 0); lua_setfield(L, -2, "byte"); return 1; } mkdocs.yml000066400000000000000000000006621307617767500131100ustar00rootroot00000000000000site_name: image theme : simplex repo_url : https://github.com/torch/image use_directory_urls : false markdown_extensions: [extra] docs_dir : doc pages: - [index.md, Image] - [saveload.md, Saving and Loading] - [simpletransform.md, Simple Transformations] - [paramtransform.md, Parameterized Transformations] - [gui.md, Graphical User Interfaces] - [colorspace.md, Color Space Conversions] - [tensorconstruct.md, Tensor Constructors] png.c000066400000000000000000000047261307617767500120420ustar00rootroot00000000000000 #include #include #include #include #include #include #if LUA_VERSION_NUM >= 503 #define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) #endif #define PNG_DEBUG 3 #include #define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME) #define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor) #define libpng_(NAME) TH_CONCAT_3(libpng_, Real, NAME) /* * Bookkeeping struct for reading png data from memory */ typedef struct { unsigned char* buffer; png_size_t offset; png_size_t length; } libpng_inmem_buffer; /* * Call back for reading png data from memory */ static void libpng_userReadData(png_structp pngPtrSrc, png_bytep dest, png_size_t length) { libpng_inmem_buffer* src = png_get_io_ptr(pngPtrSrc); assert(src->offset+length <= src->length); memcpy(dest, src->buffer + src->offset, length); src->offset += length; } struct libpng_inmem_write_struct { unsigned char *inmem; /* destination memory (if saving to memory) */ unsigned long inmem_size; /* destination memory size (bytes) */ }; /* * Call back for writing png data to memory */ static void libpng_userWriteData(png_structp png_ptr, png_bytep data, png_size_t length) { struct libpng_inmem_write_struct *p = (struct libpng_inmem_write_struct*)png_get_io_ptr(png_ptr); p->inmem=realloc(p->inmem,p->inmem_size+length); memmove(p->inmem+p->inmem_size,data,length); p->inmem_size+=length; } /* * Error message wrapper (single member struct to preserve `str` size info) */ typedef struct { char str[256]; } libpng_errmsg; /* * Custom error handling function (see `png_set_error_fn`) */ static void libpng_error_fn(png_structp png_ptr, png_const_charp error_msg) { libpng_errmsg *errmsg = png_get_error_ptr(png_ptr); int max = sizeof(errmsg->str) - 1; strncpy(errmsg->str, error_msg, max); errmsg->str[max] = '\0'; longjmp(png_jmpbuf(png_ptr), 1); } #include "generic/png.c" #include "THGenerateAllTypes.h" DLL_EXPORT int luaopen_liblua_png(lua_State *L) { libpng_FloatMain_init(L); libpng_DoubleMain_init(L); libpng_ByteMain_init(L); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "libpng"); lua_newtable(L); luaT_setfuncs(L, libpng_DoubleMain__, 0); lua_setfield(L, -2, "double"); lua_newtable(L); luaT_setfuncs(L, libpng_FloatMain__, 0); lua_setfield(L, -2, "float"); lua_newtable(L); luaT_setfuncs(L, libpng_ByteMain__, 0); lua_setfield(L, -2, "byte"); return 1; } ppm.c000066400000000000000000000026521307617767500120460ustar00rootroot00000000000000 #include #include #define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME) #define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor) #define libppm_(NAME) TH_CONCAT_3(libppm_, Real, NAME) /* Get the next character in the file, skipping over comments, which * start with a # and continue to the end of the line. */ static char ppm_getc(FILE *fp) { char ch; ch = (char)getc(fp); if (ch == '#') { do { ch = (char)getc(fp); } while (ch != '\n' && ch != '\r'); } return ch; } /* Get the next integer, skipping whitespace and comments. */ static long ppm_get_long(FILE *fp) { char ch; long i = 0; do { ch = ppm_getc(fp); } while (ch == ' ' || ch == ',' || ch == '\t' || ch == '\n' || ch == '\r'); do { i = i * 10 + ch - '0'; ch = ppm_getc(fp); } while (ch >= '0' && ch <= '9'); return i; } #include "generic/ppm.c" #include "THGenerateAllTypes.h" DLL_EXPORT int luaopen_libppm(lua_State *L) { libppm_FloatMain_init(L); libppm_DoubleMain_init(L); libppm_ByteMain_init(L); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "libppm"); lua_newtable(L); luaT_setfuncs(L, libppm_DoubleMain__, 0); lua_setfield(L, -2, "double"); lua_newtable(L); luaT_setfuncs(L, libppm_FloatMain__, 0); lua_setfield(L, -2, "float"); lua_newtable(L); luaT_setfuncs(L, libppm_ByteMain__, 0); lua_setfield(L, -2, "byte"); return 1; } test/000077500000000000000000000000001307617767500120605ustar00rootroot00000000000000test/test.lua000066400000000000000000000562121307617767500135500ustar00rootroot00000000000000local test = torch.TestSuite() local precision = 1e-4 local precision_mean = 1e-3 local precision_std = 1e-1 local function getTestImagePath(name) return paths.concat(sys.fpath(), 'assets', name) end local function assertByteTensorEq(actual, expected, rcond, msg) rcond = rcond or 1e-5 tester:assertTensorEq(actual:double(), expected:double(), rcond, msg) end local function toByteTensor(x) local y = torch.round(x) y[torch.le(x, 0)] = 0 y[torch.ge(x, 255)] = 255 return y:byte() end local function toByteImage(x) return toByteTensor(torch.mul(x, 255)) end local function testFunctionOnByteTensor(f, msg) local lena = image.lena():float() local expected = toByteImage(f(lena)) local actual = f(toByteImage(lena)) assertByteTensorEq(actual, expected, nil, msg) end local unpack = unpack and unpack or table.unpack -- lua52 compatibility ---------------------------------------------------------------------- -- Flip test -- function test.FlipAgainstHFlip() for ndims = 1, 5 do for flip_dim = 1, ndims do local sz = {} for i = 1, ndims do sz[i] = math.random(5,10) end local input = torch.rand(unpack(sz)) local output = image.flip(input, flip_dim) -- Now perform the same operation using HFLIP local input_tran = input if (flip_dim < ndims) then -- First permute the flip dimension to X dim input_tran = input:transpose(flip_dim, ndims):contiguous() end -- Now reshape it to 3D local original_hflip_sz = input_tran:size() if ndims == 1 then input_tran:resize(1, original_hflip_sz[1]) end if ndims > 3 then sz1 = 1 for i = 1, ndims - 2 do sz1 = sz1 * original_hflip_sz[i] end input_tran:resize(sz1, original_hflip_sz[input_tran:dim()-1], original_hflip_sz[input_tran:dim()]) end local output_hflip = image.hflip(input_tran) -- Put it back to Ndim output_hflip:resize(original_hflip_sz) if (flip_dim < ndims) then -- permute bacx the flip dimension output_hflip = output_hflip:transpose(flip_dim, ndims):contiguous() end local err = output_hflip - output tester:asserteq(err:abs():max(), 0, 'error - bad flip! (ndims='.. ndims..',flip_dim='..flip_dim..')') end end end ---------------------------------------------------------------------- -- Gaussian tests -- -- The old gaussian function, commit: 71670e1dcfcfe040aba5403c800a0d316987c2ed local function naive_gaussian(...) -- process args local _, size, sigma, amplitude, normalize, width, height, sigma_horz, sigma_vert, mean_horz, mean_vert = dok.unpack( {...}, 'image.gaussian', 'returns a 2D gaussian kernel', {arg='size', type='number', help='kernel size (size x size)', default=3}, {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.25}, {arg='amplitude', type='number', help='amplitute of the gaussian (max value)', default=1}, {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false}, {arg='width', type='number', help='kernel width', defaulta='size'}, {arg='height', type='number', help='kernel height', defaulta='size'}, {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'}, {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'}, {arg='mean_horz', type='number', help='horizontal mean', default=0.5}, {arg='mean_vert', type='number', help='vertical mean', default=0.5} ) -- local vars local center_x = mean_horz * width + 0.5 local center_y = mean_vert * height + 0.5 -- generate kernel local gauss = torch.Tensor(height, width) for i=1,height do for j=1,width do gauss[i][j] = amplitude * math.exp(-(math.pow((j-center_x) /(sigma_horz*width),2)/2 + math.pow((i-center_y) /(sigma_vert*height),2)/2)) end end if normalize then gauss:div(gauss:sum()) end return gauss end function test.gaussian() local sigma_horz = 0.1 + math.random() * 0.3; -- [0.1, 0.4] local sigma_vert = 0.1 + math.random() * 0.3; -- [0.1, 0.4] local mean_horz = 0.1 + math.random() * 0.8; -- [0.1, 0.9] local mean_vert = 0.1 + math.random() * 0.8; -- [0.1, 0.9] local width = 640 local height = 480 local amplitude = 10 for _, normalize in pairs{true, false} do im1 = image.gaussian{amplitude=amplitude, normalize=normalize, width=width, height=height, sigma_horz=sigma_horz, sigma_vert=sigma_vert, mean_horz=mean_horz, mean_vert=mean_vert} im2 = naive_gaussian{amplitude=amplitude, normalize=normalize, width=width, height=height, sigma_horz=sigma_horz, sigma_vert=sigma_vert, mean_horz=mean_horz, mean_vert=mean_vert} tester:assertlt(im1:add(-1, im2):sum(), precision, "Incorrect gaussian") end end function test.byteGaussian() local expected = toByteTensor(image.gaussian{ amplitude = 1000, tensor = torch.FloatTensor(5, 5), }) local actual = image.gaussian{ amplitude = 1000, tensor = torch.ByteTensor(5, 5), } assertByteTensorEq(actual, expected) end ---------------------------------------------------------------------- -- Gaussian pyramid test -- function test.gaussianpyramid() -- Char, Short and Int tensors not supported. types = { 'torch.ByteTensor', 'torch.FloatTensor', 'torch.DoubleTensor' } for _, type in ipairs(types) do local output = unpack(image.gaussianpyramid(torch.rand(8, 8):type(type), {0.5})) tester:assert(output:type() == type, 'Type ' .. type .. ' produces a different output.') end end ---------------------------------------------------------------------- -- Scale test -- local function outerProduct(x) x = torch.Tensor(x) return torch.ger(x, x) end function test.bilinearUpscale() local im = outerProduct{1, 2, 4, 2} local expected = outerProduct{1, 1.5, 2, 3, 4, 3, 2} local actual = image.scale(im, expected:size(2), expected:size(1), 'bilinear') tester:assertTensorEq(actual, expected, 1e-5) end function test.bilinearDownscale() local im = outerProduct{1, 2, 4, 2} local expected = outerProduct{1.25, 3, 2.5} local actual = image.scale(im, expected:size(2), expected:size(1), 'bilinear') tester:assertTensorEq(actual, expected, 1e-5) end function test.bicubicUpscale() local im = outerProduct{1, 2, 4, 2} local expected = outerProduct{1, 1.4375, 2, 3.1875, 4, 3.25, 2} local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic') tester:assertTensorEq(actual, expected, 1e-5) end function test.bicubicDownscale() local im = outerProduct{1, 2, 4, 2} local expected = outerProduct{1, 3.1875, 2} local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic') tester:assertTensorEq(actual, expected, 1e-5) end function test.bicubicUpscale_ByteTensor() local im = torch.ByteTensor{{0, 1, 32}} local expected = torch.ByteTensor{{0, 0, 9, 32}} local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic') assertByteTensorEq(actual, expected) end function test.bilinearUpscale_ByteTensor() local im = torch.ByteTensor{{1, 2}, {2, 3}} local expected = torch.ByteTensor{{1, 2, 2}, {2, 3, 3}, {2, 3, 3}} local actual = image.scale(im, expected:size(2), expected:size(1)) assertByteTensorEq(actual, expected) end ---------------------------------------------------------------------- -- Scale test -- local flip_tests = {} function flip_tests.test_transformation_largeByteImage(flip) local x_real = image.fabio():double():mul(255) local x_byte = x_real:clone():byte() assert(x_byte:size(1) > 256 and x_byte:size(2) > 256, 'Tricky case only occurs for images larger than 256 px, pick another example') local f_real, f_byte f_real = image[flip](x_real) f_byte = image[flip](x_byte) assertByteTensorEq(f_real:byte(), f_byte, 1e-16, flip .. ': result for double and byte images do not match') end function flip_tests.test_inplace(flip) local im = image.lena() local not_inplace = image[flip](im) local in_place = im:clone() image[flip](in_place, in_place) tester:assertTensorEq(in_place, not_inplace, 1e-16, flip .. ': result in-place does not match result not in-place') end for _, flip in pairs{'vflip', 'hflip'} do for name, flip_test in pairs(flip_tests) do test[name .. '_' .. flip] = function() return flip_test(flip) end end end function test.test_vflip_simple() local im_even = torch.Tensor{{1,2}, {3, 4}} local expected_even = torch.Tensor{{3, 4}, {1, 2}} local x_even = image.vflip(im_even) tester:assertTensorEq(expected_even, x_even, 1e-16, 'vflip: fails on even size') -- test inplace image.vflip(im_even, im_even) tester:assertTensorEq(expected_even, im_even, 1e-16, 'vflip: fails on even size in place') local im_odd = torch.Tensor{{1,2}, {3, 4}, {5, 6}} local expected_odd = torch.Tensor{{5,6}, {3, 4}, {1, 2}} local x_odd = image.vflip(im_odd) tester:assertTensorEq(expected_odd, x_odd, 1e-16, 'vflip: fails on odd size') -- test inplace image.vflip(im_odd, im_odd) tester:assertTensorEq(expected_odd, im_odd, 1e-16, 'vflip: fails on odd size in place') end function test.test_hflip_simple() local im_even = torch.Tensor{{1, 2}, {3, 4}} local expected_even = torch.Tensor{{2, 1}, {4, 3}} local x_even = image.hflip(im_even) tester:assertTensorEq(expected_even, x_even, 1e-16, 'hflip: fails on even size') -- test inplace image.hflip(im_even, im_even) tester:assertTensorEq(expected_even, im_even, 1e-16, 'hflip: fails on even size in place') local im_odd = torch.Tensor{{1,2, 3}, {4, 5, 6}} local expected_odd = torch.Tensor{{3, 2, 1}, {6, 5, 4}} local x_odd = image.hflip(im_odd) tester:assertTensorEq(expected_odd, x_odd, 1e-16, 'hflip: fails on odd size') -- test inplace image.hflip(im_odd, im_odd) tester:assertTensorEq(expected_odd, im_odd, 1e-16, 'hflip: fails on odd size in place') end ---------------------------------------------------------------------- -- decompress jpg test -- function test.CompareLoadAndDecompress() -- This test breaks if someone removes lena from the repo local imfile = getTestImagePath('grace_hopper_512.jpg') if not paths.filep(imfile) then error(imfile .. ' is missing!') end -- Load lena directly from the filename local img = image.loadJPG(imfile) -- Make sure the returned image width and height match the height and width -- reported by graphicsmagick (just a sanity check) local ok, gm = pcall(require, 'graphicsmagick') if not ok then -- skip this part of the test if graphicsmagick is not installed print('\ntest.CompareLoadAndDecompress partially requires the ' .. 'graphicsmagick package to run. You can install it with ' .. '"luarocks install graphicsmagick".') else local info = gm.info(imfile) local w = info.width local h = info.height tester:assert(w == img:size(3), 'image dimension error ') tester:assert(h == img:size(3), 'image dimension error ') end -- Now load the raw binary from the source file into a ByteTensor local fin = torch.DiskFile(imfile, 'r') fin:binary() fin:seekEnd() local file_size_bytes = fin:position() - 1 fin:seek(1) local img_binary = torch.ByteTensor(file_size_bytes) fin:readByte(img_binary:storage()) fin:close() -- Now decompress the image from the ByteTensor local img_from_tensor = image.decompressJPG(img_binary) tester:assertlt((img_from_tensor - img):abs():max(), precision, 'images from load and decompress dont match! ') end function test.LoadInvalid() -- Make sure nothing nasty happens if we try and load a "garbage" tensor local file_size_bytes = 1000 local img_binary = torch.rand(file_size_bytes):mul(255):byte() -- Now decompress the image from the ByteTensor tester:assertError( function() image.decompressJPG(img_binary) end, 'A non-nil was returned on an invalid input!' ) end ---------------------------------------------------------------------- -- compress jpg test -- function test.CompressAndDecompress() -- This test is unfortunately a correlated test: it will only be valid -- if decompressJPG is OK. However, since decompressJPG has it's own unit -- test, this is problably fine. local img = image.lena() local quality = 100 local img_compressed = image.compressJPG(img, quality) local size_100 = img_compressed:size(1) local img_decompressed = image.decompressJPG(img_compressed) local err = img_decompressed - img -- Now in general we will get BIG compression artifacts (even at quality=100) -- but they will be relatively small, so instead of a abs():max() test, we do -- a mean and std test. local mean_err = err:mean() local std_err = err:std() tester:assertlt(mean_err, precision_mean, 'compressJPG error is too high! ') tester:assertlt(std_err, precision_std, 'compressJPG error is too high! ') -- Also check that the quality setting scales the size of the compressed image quality = 25 img_compressed = image.compressJPG(img, quality) local size_25 = img_compressed:size(1) tester:assertlt(size_25, size_100, 'compressJPG quality setting error! ') end function test.CompressAndDecompressPNG() local img = image.lena() local img_compressed_png = image.compressPNG(img) local size_png = img_compressed_png:size(1) local img_decompressed_png = image.decompressPNG(img_compressed_png) local err_png = img_decompressed_png - img local mean_err_png = err_png:mean() local std_err_png = err_png:std() tester:assertlt(mean_err_png, precision_mean, 'compressPNG error is too high! ') tester:assertlt(std_err_png, precision_std, 'compressPNG error is too high! ') end ---------------------------------------------------------------------- -- Lab conversion test -- These tests break if someone removes lena from the repo local function testRoundtrip(forward, backward) local expected = image.lena() local actual = backward(forward(expected)) tester:assertTensorEq(actual, expected, 1e-4) end function test.rgb2lab() testRoundtrip(image.rgb2lab, image.lab2rgb) end function test.rgb2hsv() testRoundtrip(image.rgb2hsv, image.hsv2rgb) end function test.rgb2hsl() testRoundtrip(image.rgb2hsl, image.hsl2rgb) end function test.rgb2y() local x = torch.FloatTensor{{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}:transpose(1, 3) local actual = image.rgb2y(x) local expected = torch.FloatTensor{{{0.299}, {0.587}, {0.114}}} tester:assertTensorEq(actual, expected, 1e-5) end function test.y2jet() local levels = torch.Tensor{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} local expected = image.jetColormap(10) local actual = image.y2jet(levels)[{{}, 1, {}}]:t() tester:assertTensorEq(actual, expected, 1e-5) end function test.rgb2labByteTensor() local lena = image.lena():byte() tester:assertError(function () image.rgb2lab(lena) end) tester:assertError(function () image.lab2rgb(lena) end) end local function testByteTensorRoundtrip(forward, backward, cond, msg) local lena = toByteImage(image.lena()) local expected = lena local actual = backward(forward(expected)) assertByteTensorEq(actual, expected, cond, msg) end function test.toFromByteTensor() local expected = toByteImage(image.lena():float()) local actual = toByteImage(expected:float():div(255)) assertByteTensorEq(actual, expected, nil, msg) end function test.rgb2hsvByteTensor() testFunctionOnByteTensor(image.rgb2hsv, 'image.rgb2hsv error for ByteTensor') testFunctionOnByteTensor(image.hsv2rgb, 'image.hsv2rgb error for ByteTensor') testByteTensorRoundtrip(image.rgb2hsv, image.hsv2rgb, 3, 'image.rgb2hsv roundtrip error for ByteTensor') end function test.rgb2hslByteTensor() testFunctionOnByteTensor(image.rgb2hsl, 'image.hsl2rgb error for ByteTensor') testFunctionOnByteTensor(image.hsl2rgb, 'image.rgb2hsl error for ByteTensor') testByteTensorRoundtrip(image.rgb2hsl, image.hsl2rgb, 3, 'image.rgb2hsl roundtrip error for ByteTensor') end function test.rgb2yByteTensor() testFunctionOnByteTensor(image.rgb2y, 'image.rgb2y error for ByteTensor') end function test.y2jetByteTensor() local levels = torch.Tensor{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} local expected = toByteImage(image.y2jet(levels)) local actual = image.y2jet(levels:byte()) assertByteTensorEq(actual, expected, nil) end ---------------------------------------------------------------------- -- PNG test -- local function toBlob(filename) local f = torch.DiskFile(filename, 'r') f:binary() f:seekEnd() local size = f:position() - 1 f:seek(1) local blob = torch.ByteTensor(size) f:readByte(blob:storage()) f:close() return blob end local function checkPNG(imfile, depth, tensortype, want) local img = image.load(imfile, depth, tensortype) -- Tensors have to be converted to double, since assertTensorEq does not support ByteTensor --print('img: ', img) --print('want: ', want) assertByteTensorEq(img, want, precision_mean, string.format('%s: pixel values are unexpected', imfile)) end function test.LoadPNG() -- Gray 8-bit PNG image with width = 3, height = 1 local gray8byte = torch.ByteTensor({{{0,127,255}}}) checkPNG(getTestImagePath('gray3x1.png'), 1, 'byte', gray8byte) local gray8double = torch.DoubleTensor({{{0, 127/255, 1}}}) checkPNG(getTestImagePath('gray3x1.png'), 1, 'double', gray8double) -- Gray 16-bit PNG image with width=1, height = 2 local gray16byte = torch.ByteTensor{{{0}, {255}}} checkPNG(getTestImagePath('gray16-1x2.png'), 1, 'byte', gray16byte) local gray16float = torch.FloatTensor{{{0}, {65534/65535}}} checkPNG(getTestImagePath('gray16-1x2.png'), 1, 'float', gray16float) -- Color 8-bit PNG image with width = 2, height = 1 local rgb8byte = torch.ByteTensor{{{255, 0}}, {{0, 127}}, {{63, 0}}} checkPNG(getTestImagePath('rgb2x1.png'), 3, 'byte', rgb8byte) local rgb8float = torch.FloatTensor{{{1, 0}}, {{0, 127/255}}, {{63/255, 0}}} checkPNG(getTestImagePath('rgb2x1.png'), 3, 'float', rgb8float) -- Color 16-bit PNG image with width = 2, height = 1 local rgb16byte = torch.ByteTensor{{{255, 0}}, {{0, 127}}, {{63, 0}}} checkPNG(getTestImagePath('rgb16-2x1.png'), 3, 'byte', rgb16byte) local rgb16float = torch.FloatTensor{{{1, 0}}, {{0, 32767/65535}}, {{16383/65535, 0}}} checkPNG(getTestImagePath('rgb16-2x1.png'), 3, 'float', rgb16float) end function test.DecompressPNG() tester:assertTensorEq( image.load(getTestImagePath('rgb2x1.png')), image.decompressPNG(toBlob(getTestImagePath('rgb2x1.png'))), precision_mean, 'decompressed and loaded images should be equal' ) end function test.LoadCorruptedPNG() tester:assertErrorPattern( function() image.load(getTestImagePath("corrupt-ihdr.png")) end, "Error during init_io", "corrupted image should not be loaded or unexpected error message" ) end ---------------------------------------------------------------------- -- PPM test -- function test.test_ppmload() -- test.ppm is a 100x1 "French flag" like image, i.e the first pixel is blue -- the 84 next pixels are white and the 15 last pixels are red. -- This makes possible to implement a non regression test vs. the former -- PPM loader which had for effect to skip the first 85 pixels because of -- a header parser bug local img = image.load(getTestImagePath("P6.ppm")) local pix = img[{ {}, {1}, {1} }] -- Check the first pixel is blue local ref = torch.zeros(3, 1, 1) ref[3][1][1] = 1 tester:assertTensorEq(pix, ref, 0, "PPM load: first pixel check failed") end function test.test_pgmaload() -- ascii.ppm is a PGMA file (ascii pgm) -- example comes from ehere -- http://people.sc.fsu.edu/~jburkardt/data/pgma/pgma.html local img = image.load(getTestImagePath("P2.pgm"), 1, 'byte') local max_gray = 15 -- 4th line of ascii.pgm local ascii_val = 3 -- pixel (2,2) in the file local pix_val = math.floor(255 * ascii_val / max_gray) local pix = img[1][2][2] -- Check that Pixel(1, 2,2) == 3 local ref = pix_val tester:asserteq(pix, ref, "PGMA load: pixel check failed") end function test.test_pgmload() -- test.ppm is a 100x1 "French flag" like image, i.e the first pixel is blue -- the 84 next pixels are white and the 15 last pixels are red. -- This makes possible to implement a non regression test vs. the former -- PPM loader which had for effect to skip the first 85 pixels because of -- a header parser bug local img = image.load(getTestImagePath("P5.pgm")) local pix = img[{ {}, {1}, {1} }] local ref = torch.zeros(1, 1, 1); ref[1][1][1] = 0.07 tester:assertTensorEq(pix, ref, 0.001, "PPM load: first pixel check failed") end function test.test_pbmload() -- test.pbm is a Portable BitMap (not supported) tester:assertErrorPattern( function() image.loadPPM(getTestImagePath("P4.pbm")) end, "unsupported magic number", "PBM format should not be loaded or unexpected error message" ) end ---------------------------------------------------------------------- -- Load unknown image type without extension test -- function test.LoadUnknownImageTypeWithoutExtension() tester:assertErrorPattern( function() image.load(getTestImagePath("bmp-without-ext")) end, "unable to determine image type for file", "unknown image type should not be loaded or unexpected error message" ) end ---------------------------------------------------------------------- -- Text drawing test -- function test.test_textdraw() local types = { ["torch.ByteTensor"] = "byte", ["torch.DoubleTensor"] = "double", ["torch.FloatTensor"] = "float" } for k,v in pairs(types) do local img = image.drawText( torch.zeros(3, 24, 24):type(k), "foo\nbar", 2, 4, {color={255, 255, 255}, bg={255, 0, 0}} ) checkPNG(getTestImagePath("foobar.png"), 3, v, img) end end ---------------------------------------------------------------------- -- Text drawing rect -- function test.test_drawRect() local types = { ["torch.ByteTensor"] = "byte", ["torch.DoubleTensor"] = "double", ["torch.FloatTensor"] = "float" } for k,v in pairs(types) do local bg = torch.zeros(3, 24, 12):type(k) if k == 'torch.ByteTensor' then bg:fill(3) else bg:fill(3/255) end local img = image.drawRect(bg, 5, 5, 10, 20, {color={255, 0, 255}}) checkPNG(getTestImagePath("rectangle.png"), 3, v, img) end end function image.test(tests, seed) local defaultTensorType = torch.getdefaulttensortype() torch.setdefaulttensortype('torch.DoubleTensor') seed = seed or os.time() print('seed: ', seed) math.randomseed(seed) tester = torch.Tester() tester:add(test) tester:run(tests) torch.setdefaulttensortype(defaultTensorType) return tester end test/test_rotate.lua000066400000000000000000000047051307617767500151260ustar00rootroot00000000000000require 'image' torch.setdefaulttensortype('torch.FloatTensor') torch.setnumthreads(16) local function test_rotate(src, mode) torch.manualSeed(11) local mean_dist = 0.0 for i = 1, 10 do local theta = torch.uniform(0, 2 * math.pi) local d1, d2, d3, d4 -- rotate if mode then d1 = image.rotate(src, theta, mode) d2 = src.new():resizeAs(src) image.rotate(d2, src, theta, mode) else d1 = image.rotate(src, theta) d2 = src.new():resizeAs(src) image.rotate(d2, src, theta) end -- revert local revert = 2 * math.pi - theta if mode then d3 = image.rotate(d1, revert, mode) d4 = src.new():resizeAs(src) image.rotate(d4, d2, revert, mode) else d3 = image.rotate(d1, revert) d4 = src.new():resizeAs(src) image.rotate(d4, d2, revert) end -- diff if src:dim() == 3 then local cs = image.crop(src, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3) local c3 = image.crop(d3, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3) local c4 = image.crop(d4, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3) mean_dist = mean_dist + cs:dist(c3) mean_dist = mean_dist + cs:dist(c4) elseif src:dim() == 2 then local cs = image.crop(src, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3) local c3 = image.crop(d3, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3) local c4 = image.crop(d4, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3) mean_dist = mean_dist + cs:dist(c3) mean_dist = mean_dist + cs:dist(c4) end --[[ if i == 1 then image.display(src) image.display(d1) image.display(d2) image.display(d3) image.display(d4) end --]] end if mode then print("mode = " .. mode .. ", mean dist: " .. mean_dist / (10 * 2)) else print("mode = nil, mean dist: " .. mean_dist / (10 * 2)) end end local src = image.scale(image.lena(), 128, 128, 'bilinear') print("** dim3") test_rotate(src, nil) test_rotate(src, 'simple') test_rotate(src, 'bilinear') print("** dim2") src = src:select(1, 1) test_rotate(src, nil) test_rotate(src, 'simple') test_rotate(src, 'bilinear') test/test_warp.lua000066400000000000000000000116571307617767500146050ustar00rootroot00000000000000require 'image' torch.setdefaulttensortype('torch.FloatTensor') torch.setnumthreads(16) im = image.lena() -- Subsample lena like crazy im = image.scale(im, im:size()[3] / 8, im:size()[2] / 8, 'bilinear') width = im:size()[3] -- 512 / 8 height = im:size()[2] -- 512 / 8 nchan = im:size()[1] -- 3 upscale = 8 width_up = width * upscale height_up = height * upscale -- ****************************************** -- COMPARE RESULTS OF UPSCALE (INTERPOLATION) -- ****************************************** -- x/y grids grid_y = torch.ger( torch.linspace(-1,1,height_up), torch.ones(width_up) ) grid_x = torch.ger( torch.ones(height_up), torch.linspace(-1,1,width_up) ) flow = torch.FloatTensor() flow:resize(2,height_up,width_up) flow:zero() -- Apply scale flow_scale = torch.FloatTensor() flow_scale:resize(2,height_up,width_up) flow_scale[1] = grid_y flow_scale[2] = grid_x flow_scale[1]:add(1):mul(0.5) -- 0 to 1 flow_scale[2]:add(1):mul(0.5) -- 0 to 1 flow_scale[1]:mul(height-1) flow_scale[2]:mul(width-1) flow:add(flow_scale) t0 = sys.clock() im_simple = image.warp(im, flow, 'simple', false) t1 = sys.clock() print("Upscale Time simple = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_simple, zoom = 1, legend = 'upscale simple'} t0 = sys.clock() im_bilinear = image.warp(im, flow, 'bilinear', false) t1 = sys.clock() print("Upscale Time bilinear = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_bilinear, zoom = 1, legend = 'upscale bilinear'} t0 = sys.clock() im_bicubic = image.warp(im, flow, 'bicubic', false) t1 = sys.clock() print("Upscale Time bicubic = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_bicubic, zoom = 1, legend = 'upscale bicubic'} t0 = sys.clock() im_lanczos = image.warp(im, flow, 'lanczos', false) t1 = sys.clock() print("Upscale Time lanczos = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_lanczos, zoom = 1, legend = 'upscale lanczos'} -- ********************************************* -- NOW TRY A ROTATION AT THE STANDARD RESOLUTION -- ********************************************* im = image.lena() -- Subsample lena a little bit im = image.scale(im, im:size()[3] / 4, im:size()[2] / 4, 'bilinear') width = im:size()[3] -- 512 / 4 height = im:size()[2] -- 512 / 4 nchan = im:size()[1] -- 3 grid_y = torch.ger( torch.linspace(-1,1,height), torch.ones(width) ) grid_x = torch.ger( torch.ones(height), torch.linspace(-1,1,width) ) flow = torch.FloatTensor() flow:resize(2,height,width) flow:zero() -- Apply uniform scale flow_scale = torch.FloatTensor() flow_scale:resize(2,height,width) flow_scale[1] = grid_y flow_scale[2] = grid_x flow_scale[1]:add(1):mul(0.5) -- 0 to 1 flow_scale[2]:add(1):mul(0.5) -- 0 to 1 flow_scale[1]:mul(height-1) flow_scale[2]:mul(width-1) flow:add(flow_scale) flow_rot = torch.FloatTensor() flow_rot:resize(2,height,width) flow_rot[1] = grid_y * ((height-1)/2) * -1 flow_rot[2] = grid_x * ((width-1)/2) * -1 view = flow_rot:reshape(2,height*width) function rmat(deg) local r = deg/180*math.pi return torch.FloatTensor{{math.cos(r), -math.sin(r)}, {math.sin(r), math.cos(r)}} end rot_angle = 360/7 -- a nice non-integer value rotmat = rmat(rot_angle) flow_rotr = torch.mm(rotmat, view) flow_rot = flow_rot - flow_rotr:reshape( 2, height, width ) flow:add(flow_rot) t0 = sys.clock() im_simple = image.warp(im, flow, 'simple', false) t1 = sys.clock() print("Rotation Time simple = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_simple, zoom = 4, legend = 'rotation simple'} t0 = sys.clock() im_bilinear = image.warp(im, flow, 'bilinear', false) t1 = sys.clock() print("Rotation Time bilinear = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_bilinear, zoom = 4, legend = 'rotation bilinear'} t0 = sys.clock() im_bicubic = image.warp(im, flow, 'bicubic', false) t1 = sys.clock() print("Rotation Time bicubic = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_bicubic, zoom = 4, legend = 'rotation bicubic'} t0 = sys.clock() im_lanczos = image.warp(im, flow, 'lanczos', false) t1 = sys.clock() print("Rotation Time lanczos = " .. (t1 - t0)) -- Not a robust measure (should average) image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos'} im_lanczos = image.warp(im, flow, 'lanczos', false, 'pad') image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos (default pad)'} im_lanczos = image.warp(im, flow, 'lanczos', false, 'pad', 1) image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos (pad 1)'} image.display{image = im, zoom = 4, legend = 'source image'} -- *********************************************** -- NOW MAKE SURE BOTH FLOAT AND DOUBLE INPUTS WORK -- *********************************************** mtx = torch.zeros(2, 32, 32) out = image.warp(im:float(), mtx:float(), 'bilinear') out = image.warp(im:double(), mtx:double(), 'bilinear') win.ui000066400000000000000000000015731307617767500122430ustar00rootroot00000000000000 Display 0 0 640 480 Form 0 10 10 QFrame::NoFrame QFrame::Raised