From 300896ed4718e29ae2aae6198b836b6df2f9974e Mon Sep 17 00:00:00 2001 From: Balint Cristian <cristian.balint@gmail.com> Date: Thu, 26 Nov 2015 06:55:47 +0200 Subject: [PATCH] Add HDF module & implement HDF version 5 I/O. --- modules/README.md | 2 + modules/hdf/CMakeLists.txt | 21 + modules/hdf/README.md | 4 + modules/hdf/doc/pics/hdfview_demo.gif | Bin 0 -> 34574 bytes modules/hdf/include/opencv2/hdf.hpp | 54 ++ modules/hdf/include/opencv2/hdf/hdf5.hpp | 681 ++++++++++++++ modules/hdf/src/hdf5.cpp | 1051 ++++++++++++++++++++++ modules/hdf/src/precomp.hpp | 43 + 8 files changed, 1856 insertions(+) create mode 100644 modules/hdf/CMakeLists.txt create mode 100644 modules/hdf/README.md create mode 100644 modules/hdf/doc/pics/hdfview_demo.gif create mode 100644 modules/hdf/include/opencv2/hdf.hpp create mode 100644 modules/hdf/include/opencv2/hdf/hdf5.hpp create mode 100644 modules/hdf/src/hdf5.cpp create mode 100644 modules/hdf/src/precomp.hpp diff --git a/modules/README.md b/modules/README.md index dbdbfe19..46b6530b 100644 --- a/modules/README.md +++ b/modules/README.md @@ -53,3 +53,5 @@ $ cmake -D OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -D BUILD_opencv_re 22. **opencv_xphoto**: Additional photo processing algorithms: Color balance / Denoising / Inpainting. 23. **opencv_stereo**: Stereo Correspondence done with different descriptors: Census / CS-Census / MCT / BRIEF / MV. + +24. **opencv_hdf**: Hierarchical Data Format I/O. diff --git a/modules/hdf/CMakeLists.txt b/modules/hdf/CMakeLists.txt new file mode 100644 index 00000000..9bba8b48 --- /dev/null +++ b/modules/hdf/CMakeLists.txt @@ -0,0 +1,21 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) + +find_package(HDF5) +if(HDF5_FOUND) + set(HAVE_HDF5 1) + message(STATUS "HDF5: YES") +else() + ocv_module_disable(hdf) + message(STATUS "HDF5: NO") +endif() + +if(${HDF5_FOUND}) + include_directories(${HDF5_INCLUDE_DIRS}) +endif() + +set(the_description "Hierarchical Data Format I/O") +ocv_define_module(hdf opencv_core WRAP python) + +if(${HDF5_FOUND}) + target_link_libraries(opencv_hdf ${HDF5_LIBRARIES}) +endif() diff --git a/modules/hdf/README.md b/modules/hdf/README.md new file mode 100644 index 00000000..92c1a848 --- /dev/null +++ b/modules/hdf/README.md @@ -0,0 +1,4 @@ +HDF I/O +============================================================ + +The module contains I/O routines for Hierarchical Data Formats. diff --git a/modules/hdf/doc/pics/hdfview_demo.gif b/modules/hdf/doc/pics/hdfview_demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..410bdf59a55592fca970cc731119d41ae0cb501b GIT binary patch literal 34574 zcmV($K;yqhNk%w1VZ;O50pkz=0096qG%`a)Lt<rQT!X@YfqvC=V|$96pP`<_#>2tb z>)q$_>g?(8^6&rt@BRP$A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i006`T+W|lX{*VDw z0=PKq&Ab0#D2`-lo@lDBZ0o*oEYEap-*~R?eDD9jPN0x!7>~vS<H39&0tUmN!DJke z*sOMoY;wOxFL+EYo4?|-dd+UT-|)D6e!gJs4Lc(-00<aHct}K3M|}l!M?(pFeT7?s zNRf$@m6n&7nVOrNot~dY33?2qqz_676aoSlLkNskrLv?Avk$YTxVX2vy}Q1$wX(NO z#lXkGwoAoT$GphU(bCh@)z;V8+1lIO-QM5e;o{@u<>u$;>FVJH?e6Wp@9*&ivhcp5 z0F2E1{r>*|0}32Su%N+%2oow?$grWqhY%x5oJg^v#fum-YTU@NqsNc_AVV@_fZ##` zD*OVZWTMi^08;G;4oL98-2*->S-xz5U;vYzqz1GE%3uJ3nn#}`El{xK&zD7yx(q6{ zrGS$Rm=1KBb81$tEdxL`DzdEEvuM+*UCXwu+qZDz%56x}Bp^_D^KuHHx3Ay7fCCF2 zOt`S&!-x|rPE=Rly2p?sOP)-*vgON|Gi%<=xwGfbpuv^YC+ujiq|auVwze7<>eEhA z%W56Ew(Z-vbL-yCySMM(z(v~)PQ1AB<H(aMU(UR_bK#D!ES_`VfP%T$vuoeZy}S4C z;KPSkApK1B>0^|PW^`bI`}YCf7Z~8ar~7vG=`Sdd|33c(V)gz9;CloPSYUhx+PB|- z0w8c-feTc3-9KCb)j&)STy+ar{tN*~Pb>)_mQ>=EXyS<|rl{hIEVf17fdRxQBZBW$ z2cZBECa52N3(Vx7djaNXpn&)#fMbt93aOq+4OpkZlTbz}<&;!bY2}qzW=W-lEq3YU zmtck|=9pwcM4pyXN{Hr~Y_@6TlF?N+<^j#%QXK;X<f-SLeD>+*pMVA`=%9oaYUrVe zCaUP7j5g}%qmV`_>7<laYU!nzW~%9?oObHzr<GdKL}Tp9`2YbE@{?yjTs74ps}a#D zn-W$Wz$bdR;`%@syc)5quf0+c?61NCYizN|607X6%>Ev`?6Ju<8?Cd_K0EET)@EDn zw$M`REw<c(`>nR)hD)xv=$@;tx$KtfZo2Kl`|i5)#!Iif_})vb8UXC8#f$(0j0K;v z-m+e;vZDHr0tA#O>nyZZ$0vj|8i?S<1YQhqlJE&hARi*e$g#&gI!H2&6$k7d$XAVg zV8%HfDI~@ndu$ZR9zXD&&hGKNfX_hhEOgI9|6Fv?L?>;u(nv4ubkjaRee~2!Q!RDX zSYNF*)K+s{px0Y>Eq2ymmpyjaWS?zz+H9lk_S$c&EqB{+*FAUKbl+_^#SI_WH_q@8 z@E!mS6At9x?h!oddQF<jOsW#ODLIu*JS<DZ{<iKp`R1H=?)m4Shc5c)q?c~`>8Pi! z`s%E=?)vMn$1XeUidS)Pbp;m#IY0{gvuYDh25^9g13(!-@WBr+K=Dv2&%6S{hb&{1 zmDfoM=C@)IFkf46ul**thmU)%<d<*$`RJ#w{`%~<uSV?#+l4zrs9xDH@Ud4v>-CxI z=}Q2zFj4>-1V9D^00FD02Pq0j5)n1%IS`DX1Sd$r3R>`j$ABNH5H=_4#iCYx!k`F8 zNWv1D@PsJ*jRtS$I51hR6xiDnDPWPp8rtxNILx6AGXnrw^iTp9kbn<o$O;S)5da4y z#Ril(#96>%h4O3RmtJ^1{)KQ4JItc~7PrX7E_yLEPux?%HaI^qQSpNklOYzZKpWm1 z<p2(ti31$KhdJ8O0Rgz99`|@hInr?ed(0yr2T9068uE~cOr#<g$;d`J@{y2?q$DRv z$x2%Cl9<e-CO65+PI~f_pp>NL!l<V)vM_g3^k5AAx5fjsks33ZAyk0DKw56k7jF!u zE_cbxUi$Kvzzn7^he^z08uOUQoa87;0mV3>(o1JtWu9huJp@?un%K;yHn+*mZhG^Z z+&l%s#7E9@n)96KOs6{6$<B7V^PTXFr#$CL&wASPp7_kCKKIGbe){vD01c=>2TD+Q zHiMLTGG!E>*`;V&QH?Z&7!3X=TE&Wf@S?QBC`C1D(TsX@qaW=kNJlEtl5+H<Aw?-k zRcg|dx^$&4Z7ED=D$|;>^rkVzDNS{1)13Nrr$6l}P=_kiqVn{pK}9N2m1@+aI(4Z} zZ7Ni!D%Gko)rrg$<Kh<D!To^{qWm+CY<fc{;*>QwX9Z4Lf3sHKw3V!5<;`2=8rR)K z?4VgK<yM{9Rf&F;m9V%|aq8NQST=$ja|K5&$B|fXATXlhXlytZ`whu%BVdRnY&ibF zSj}!@mJHx*WIf9b!)CS|wWO>!EbCYWM)rDm#gkTT$5k_`@}Yp`--)Cco&wBnbXqY{ za7#2?@f5eWC^{~9l>Yl2=6Xkg%apBNY0FpJdNqx?t<_Buz}@b8_q*T??{@>>-K&WA zyy*3A3tLqT0iYzb0FhA>*xLm6RzUy^@h$YCBmgY+*Gkhn9+duTB?3!MEB3twe*M7{ zpA@6P%z*HNK{R0pdx63NLGXJQ%nu{cgdgywP>eFPVSjkIA0aM=hVN@)4aIlF2~IIH zPMlvR#F)d(Xw@HBoS|iec*hF1v5yxFWElfw#X*KuF<#P@CeZi+Mlk}0M}=dY6a>je zo^L;%Yz!Qy1jZ#k2$sF9<&~H@$4W-CLB{+lfynp9ZEgs3W82J9F|<Vv{p&PaNvHJs z`Okn3ba;8Y{@mmm`b5s{PNKQ9=<PIGJCDvzq_Hz+ULceJ3axIK@{HX*2e!|G4mEel zD{4WTY9>suvOluy4^-d8!YC&5nwj8gRrjL-WX3f=bRFSb`<ftHsWq&f>|zkhx*x*c za<Q3_<zRo<*2Mm>OrAaLR4-d0&Q5i;n@sB=cbmsdPI9<IJnm!jBipZzGfMy#?PBnS z2@PPcj9m?ET2ouy)|NK2&CQHti`m}V#y7XitmcBd``=K$cbcai@P#h~-Ox^W!;3)y zfTx=o6_@zMDIV!cQ&YML-78JXxar&KnE)S`Q>j<Zaz7KgxrY{R%!xa5<JKIxh=#4D z^BQvg>e{)lm&2{6b=z`37ob5!ce$*HLY4<Or5+n{Fx0am^(9c92~(dB)~&AfnsD90 zP6>Mxw8HeNGy#Fj7`xR6z#)K?y~${2#?;w<?x#=%?m#BEKi=;2v**3WUTJ!(T15ev zkX?ZoD*RV|g80K5zR6<8y4|BPaQfms?X*UKl&gLVx=$SP1Sk7g%zki+|K01IZ~H&^ z9{IjES@d^zz20X(@Y>71^aO7`>?wcy-dq0cqp$o!b}x9~$G#p7Fw!#kj(p`$zUj>e za_gV|e9{MADp8hj);WK5)OUaTpntv2KVD}JTGy|gMme9in{@H^WPmei|DX$ozy|(p zSttTP#Jc5MkouFT7<@25`sZ{r(htGqfA=SVC@~NS*kSlLfY|1D_V;D-HUO(YB1Lh5 zqhf&**m)XQ1swQ)M<#(HxPPtY6akoj@5O=P#%}u;BmAafB*-cw27nKU5-vy(4Tx|~ z@PRp2gYyN1Cg^TLNPvS^gc&x26o?QzNQA#PYd_(G80Ui*B859xg&o*~9f*Y`_zwpt zg{Iem8Mr*lkb+S6Y64h>52%J|SZ`67g<(h{0K;KpC>31jWH+d7Z8#7fw?^ehaxP?Y z>6db#CV$|CK=5aX;N>_w7<K}Kh8_5XZzzXBI3rY$f?fE9kywc`LIskjh5nTIf-$my z2%(9gXoNyoe-NmO3&@HRxQYpcgSj_$N+^3dh<gD;1!EX)n7Cy6W{aU$i;l>Ds5pv{ zc#5m%gVPs`lbCx2D2kjoj6j%!)HsWh*Nl+&jJ?+njTnt`CX8|DcVeM$JBWz%28!ZH zZ_9X%=*Vq5xQlG4f_f;AkT`&5D2w4&6W~~j8W)BGXpYjBi}i?)z_@jyvU4DZX(DHS zC3ju@Gj!Ueh~jm0q$ZKvWh#8<i?z^o0+EqTFcTZOZC6%(_!e4Np%o^%k@t0yE2)wS z#*%zUk`AYbA9<1vmVFc^i3!kgGI?h97LvBtk->*>9*KKKNqgIv{*<36cRguv3^<fN z`EX4sllM>nQo($Pca=j~m0f6nQ+a)vw|PG)VIWDCCt;Ri8Ixd%aZu@GD0vkw$(29Z zePuaiBe<3n*OsX^T=W%yH3^hZrg;*UVdLkAvlTy@7KjdcMW4og7WtUs^>PHUlpHZ> zb77fsftd?InNo>p48fTip_y<Ynr}gxZef~j(TA6oX+L*<L3eV5=$IB+k&t<jvf^)1 zX_e<zg1RYpyxEm^iJMNjD}O*=y!LLp5*7gwZb_(d5f_xi37r5ToNh>+NJ(GVIg?2l zoC+6}0%4ufIg-v<ZT?ndH>q&uX>nGTo@!~G{)U|KwrlME6`v6{pOgu4<k_7cxeUG8 zUOrZBy!K%Csh;`ydEZ8RxMo1NwqWo%pySC8{??yk_nW&}p<-5;tGSSXm`aNYR<)7< z1r<;?S7@GDqB3WqG>4)#mvaY6euqg$lDQ(B_K@n=n6+snN4Gq+X)16wle*cLQYV-h zH$WrOVUxyY+SzbK+H?caqfqFi!>N?onVnA>X5JYPP<oURNMbzJUSU>bME0fL#-&LH zrf!&}WEx^tDP_eqnDAzoZn`UW>4i3CrS)Z}HxXP75{p9CrFa-&bLtOB>ZN{~r^ET9 z_lI>t%9CkIW<9#6^a!Z~D5)qxsW#?{YKf+gimCoK7$QYV5vs{^3h8qUshH?Tql6fu z0VNNt+N!Sls<0ZXvO24@TC28ttGJr0y1J{p+N-|$tH2tp!aA(PTCB!;tjL<I%DSw~ znyVNps;pV69D1sZNpv^LT}TI+rot7LXL-(NgOSFqrO0jFI;G}%rFSW=NlGDF8lG;b zu147sRjHh1Hm~H0b%^Jn@Tzw$5n3>adH+g;e;2SpczSetd+fEZP?mNcvUoL-uQN8V z1B7|$s-+6coC9ZN?DehwI<fC+v8C6r7$UM@(ReIjvQCGx&8e^md$NxQfTza?s41xT zYH+P0v-d@?9UHR}Yp{a)tO*&d8wyQ=i2kbUXCg7mXd?=9DvEO_`m`wuwZ;Xsb#@9# zn<6t>tr1z9Y{y;mp{=rlrnX0=QA)OFil<1qgkrm$2&buG#)V<ZV{luia+{+Fbhd@s zs9_nWd<&<3E0T4mx6A;gwq~ZgVz*UxZE^~@Oewg8nq&srdo38T(uR|aD}#=Er#xo3 zoI9qSo3}KZxpZ5mW14TV_F|x^kD|M}<R+vy>AEoci;Ii5kgK`1o3~k(yJW^{21vCp zdP+b?QG>XC*IK+FdS}nIw8`7FBr3H|8@13owJmCx(}kF=*^t!gp(<y*U;CJ+rl0LP zo#30c2<M*Tn{n2OzURxR=BuPA{z0D)_G`ViYcWWR587|~d5-1A4;gn8iVJV`dyVb8 zas4=Z0n={t^}Y@|z*e@u3kz;z>c9@%oD6)ErORbq2C4)Kq2n834ayP<jKK6;o#Shr zx@&AGyl*5NxhBkETsFa8fMcl}szIxvSBoN9tGyIkCOq83KK#Q#9K=El#4cLBFp9h~ zTC||D#7x}8PW;4B9K}*R#Z+9yR(!=+oW)wKDAD>wUi`&i9L8ci#$<d9r=+TAoW^Rr z#%$ciZv4h@9LI7z$8=oBc6`TpoX6rrMyPTH-t#X;@W*q|6L^t*?sLe9oXCp2$c)^` zj{L~!LnlvCR=3l~@rDHc+>^<HJbIhQ2s;_bNC1~vKmmq;c!kgt+-13`ESQ2UWuiO? zf(gm8Jj=9P%eH*WxSY%AA<5)ny;fk!Yp@ER@XAPliC7>MfSd)ItOcUn%46`y&TI+M z49Z#%T6bZFcVW$M(1}=(qlj<@+&l=esLSMB&gOj1=zPw+Ov&;C%w<rPp)du|T+9yi z1V^BJS&$V8bQjUAig$s@|KiWojEKst2u7gK`FG9r%sm8s$qr4;2yM^WBhVYU&(u86 zJRu_a{0D&yFo2-V4-L+icm&>@1=+_3+B4EdP}1q#(k}hdFdfso`~Z{e%eF!uYA_-O z-3HSP2R;2gJN^CzKMl~EfXaLj%QBtRO1;!f-PBI42J39o?)=nLUDZ~7)mWX?TI~i= z&Bwp>)nFagVm;PmUDjrO)@Ys9YQ5HM-PUfcI_w-B7S#)?RSk7b3Rhtb4Fh0K@+41i zB?7i4flVcYy*cVv#)zHRioMv3Eg3fbn!4c82<=)CO<4t$S_@<k`+N*au-8*k*!wlf z^(@)~hKK@i0D+yOsO{GdwA$n$BCI_?uB|19-PpE$+qj+Ex~&VoyxYG0+rS;%!mZnk zlLZTaCJT@WY2YjVf(GzL2hXh+%l*DvUES7w-PoPogpn1K;44b-fE0jg2{5tc(FV<( z1?O!B>Hd8NyRzPi(B5R=-r7yolYj@2APU^m-A2F)*5n6AVBZ^13l~rl1Rg|OFd9g$ z1^TVu^WESM4hP>n2w(6HV~`KRpbH1R85u4a8=e^*KHMN4;vzoczmX3oaS*9%5h<<_ zE1nQ7KEyB{<1#+uG+yIivO&pA)C>)hpIin{a1!Gk%ga2_W5GT1K^3m7c?gu?VDQIB z9u+Ii$qrorK<-{-Ajmro<lmghQvTpx{^ejE=GwhMBwprbe&%SN=Dl$+r0_~@9viT6 z4Q@^naIOolVdtc!=6b&8eBS3H#5is4O->-_axUoKH0a%w=YGEEjNa&u?i&YVyplfY z{*+$nmVW7&p6Qyt>73r_p8n~e9_pe#>ZGoSWW*gW&Ltf2;x^vuuKwz<9_uaA%Lj48 zY>O6f!X6YuAUD$!3a|it;2sCSJpbYAz^)%D;~xE$BNf9j2x9EP-XjUH00;yjs_qYY z2NWB!b^hC@`RICcH?y)n?&MzX=6>#x(;k3RAU0AU3eX_O{U6U>Jjbr=4x#`;;WE4a z9?(u7M&cemvh3DQDvhJZ27mAfpYRI5@C@JZ4*&2FAMp}@Pe;rI^&=&}gS1M##T?)9 z9{=$mAMzqU@+4pKCV%o=Y{Ln&JGyf}@#=y*xe6avuC&Ly1d+pfg05ZB;ylm(fQi<6 z20`;tDiQsQ?nZy~NT2i|u{|o00b6lDgwq639{?_I@xRL?L)%aruP5u?^<Mw=UoY?{ z1NLNJ_GT~kV81tbv-WD=_C>??dK33~Gxu=+HF96~c<=UfulIR>_k92NejoUIFZg{w z_=fK`8IvaggE)Xg^#s85F~azNNN6nYJG-MWRnG*nqd(c3^Gg5updb38Kl-me`A94f zz0(g2qdTFEJfSUMgZ*CyW;{^>+MNHI!yDPYz**!)M44@pr65||(AR15=*EBi$iGFV z-w~V71USEE$shgFKmF9d{H`gyMoazLzx~`#LKQG-2!IPeL=NMR3)cRRy|{0*kk$R{ z-~R63*cnQyS&wI150S2<=%0~G$+Z9c-~ayq{}ABhq}5)W_2%7wFce3!G*2{DSJq;l zhhY+#u<Zkm5dfeMpiVd>7L7+_Qkl%fWU>SFaI?v1j03{?<aWKX!8I}Xb^~Q}`l&rQ z!jiFuA@IB%@yxun=lA`8z(B!4!a~DC#6-nKl_skis-~TupP;2n%uG!rN3JNWDXK>- zEj80q)Ko7v##Yx?*jU+F+FILN+}w%ID=Gp>1|FXWAj!>R<m92wr%xc?QB~<_>T2t2 z>}>6A?r!gI@Nn^Q@^bTY^mO%g_OjkconT5(V`cmMC1xlo=>8e0Vt@e!4kTF6;6a25 z6)t4h(BVUf5hYHfSkdA|j2Sg<<k->UN01>!jwE^T#VB9EgjK4C&*e)U`mpd5<quRl za9hBIu|g(Xj&f(n0X-)`98q&Z!66kVAc4}QI=?|3$MjnP1m>)Y^Z8S%H>gy*ZS7|C zN!M@AV4#Gzb|pQSapTH#7V5?*AUMB$)oYDxUuu5=OA|aeRbj)Y5GPi%m@yj1kH18g z+=X)GW>D7JOd0QOF}b1f(WQCUW{?D_Rj+2<+I8y!0uUe&P~i1#+^j9Z2yg^}&jDI6 z^)~VwjBsQH6eu_#V7Y(;%cUy_Fx~p~>CK-<w_Y9l{&(=p3&1eWAUKgQ4I2E0k$(Mv z_6gLpf8d_Jefag|zrQs8LHYBBNRNR6@*_?Y0Fl#Szyf(o!oLL3laIjsl!#Ek2^&lX zLf{k>;=r|>YJk8HGc0jL|K>0;1`$bIg2UsC08xiA4rDOD;N~+hi3I#p0!H&Z)M3UO z9o*!?On?lKh#8F>qR9uKlu{EVg>>=~B%j2m0L`3u=LVJL0ZlYAks%F~ey%_*H#XaJ z^Uc<FI|j-!kx(u?JiWtnPd@qdu}(n$bcxP71=WO5K#4Fk(a;uUG%iQwLUhYV3sq*) zO1Z>hvoCS*OiVc|6_uFHGDEW_HFJP-Rn-FiV0BeX!hm56CXbQz0a~?m$-i53HKKtg zfh9uNA&3>iSRIhHGrn0XETRDcuEeBQXNQe8$_JUnwc28@Rf1Y1pjC#~1e%p`2&@zp zGF^1nwK7}%%q=2Vqo5Qw$_8K{c2Y9>)7DpApS>2-BEY5cUv8yU7~5JK4%pv#w>>u# zgB>0hVF~3mYAqbgmH2^;W9V4p7ewx_OSZuL)Cagmb@|L^P?aKqk~(s^(VOLpH0R`a zhG}P?4-T5rq3;2@=ogW;=V+zvM47X;CSiHZrbC0-t}0Gb^;K4X<C;xN1jv`gBltU( zzGTtXSM3MPPCMZu{1ZDwBytTvfB^oMRAO$jkFXnWy>*a#>^bU&n+U!O_xtdqK_=YW zhe4*Twy|XsfN~ft$6WHu3#fc@&mm46@(<Nzc!*rFn$N$x^A@s4Oa`ZW-o<$X{M{l( z-$eGlEgq6~-xnrW2;LPBJ_+4{Upn*05pN#kk`+%rWa*!$p7bM%$3A)0*Avw3wW|fc zZML(2y3=1)W~pk?u1@oxnmH<9e){XT|9<@Q*MEQh`}hBU00vNi10-Mp4R}BVCQyM3 zWMBgw_&^9oP=XVrU<ECBLH(T%5-M{Gs9M6l(UfW*r!q~daP>7e1>kN7Kp}1{0U|;r zAY?Qe0Mkx)L)6W%S%zX=>Hdr$!;&chhJMrG?yf|q83jO7^W)7DS?9zi%7BScf@0%_ zs62T!35X$5VG+k5sD450ipRs17i)NlF~;tQ6N{Yiu=u+qt?`XQ^dT9AmPR?wPm093 zq8<svM<4pJ0K@Ac9P7xiIdn0IQM%hA75Sk?Lb8b2lj9+Y6htLT(LaKm(H4gk$rovm z0iEgK)ItTq5K5&k?*j!YGjU2+E-RK0o8^yc*|=NEGMALxB`<p!w_h5;hO^WZFE;3@ zQm!wUn{c1ZTG_(f$R;(eg63<&_B<lU&|^nXVQ-R`O{Xl+akKN?^)eT|Ync;zvs0%n z$Jx8cg|U<2Y!3o-{zL@heN1%NY-1U>DTI77NrmqWXyd-w&nX=*p=u;3_7)~kZ}Rhy z8)6<gb&$LU#Kv+J?OR4KM;neR&Z8SOs6-oD&wHLtd~6%v_;`8HkU|b%>KrLZX+%$z z?$e?%6#~GdCC?mgGe<5JrYK1%HDxaIl_H!83A1LxYDV>IungufV`)`aVilEI1tC}2 z=hdo#wP|8i8d;H6Oimp$eaUQ@QZdoY5=!BOQoRjr=(?I%dCaGVY!5p7I=e6`F0j`c zEW|1nxo&pzrvD7U*pvm&TNzfbaML1Ahfq)>99FXOQs^9U=*BokY_AVJf@b+jQPFbC zv<M&<XLZ2-T8s?=wv98b!5XVCdRa@i-P)`^7c^NRM7BPDm93Ao`74J4GqoA>t#GB4 z*n!T<vbSv;A!8d{;0_nC#4W6L`_|nb88vG17zWkcnh&O~(wS?zYkPA;)nA&mX!0Ez zeS>CSpW&Bh`pp@CZ{}Z@0eDns^^#h{WXcbo*9dTZs$AWxngXR}0AX;iB`%aiZoamN zL!}RwL}uJ-9Z1B2BTt8(E88M^*gS>wjfqb;VkcGU#GR!vVhwp98RHPcNVsr6jrboI zALv3{YJew)%zz???8r$j8H`VtV<Eek!}(FMhN?{ElUO;+J8}|^OB|!qA<3t#)g%mv zY~=nFZ@J5cgz{@$?Az1Ukv5C0k3G%iWG@35%uxn2nF$1;D*Abff(CS%U(Dt&2gykn zA~bo@{9)3rd0_-j88VUDRs}O5y`^dJGavjE=stKile3aydpdx-g^rP?#?nWR;UiMd zE4rUd;;eaN>s3Dq*Y5m}t08(MU+a2!@anZ}iWBUb3L)0|(x`%#-L_^kwAk-@ws|fW z#v0Y8WZCxcwo<)qRBM}-&qlGaZ{1CAS=-#nZdR(t#%((LlHAG;HgpHJ>-HRo+k_jy zwc{;J4V#+?<W@Iw3LcMs3;f_53pTOm9dHFLeBbBBx6vSeY=J`;;r?d00I(fyRsO^K zWSc7RD98I5_-0xN3?=|xw)W}Zd^$Cwt~ae`4c*&j8#$PIHl7jf=#1N3#EBJp(J4N3 zR{Pw{7w<CBBMtPO8y&UN=IgU*led3b9dKC>_SGY+^asGY=1dnE$GyF|iJv{>ARimp z#U6CBM~vQ^^ZGV%p0BjKo$MeYw{tb_u)CMt<4wLXiJ@NghJ$Ww#a1=r7q9q$t6J}e zzY@72zv^z{J={@80@#VS_qiiS=Nlh<$K?rK&AVIDPZlo+FKu3xFL={VZMoLw>fYL< zCN`pOLY5{9T7-I5mcEa35{UnFh!KCtOBBxXrPzEI3leweN_61#tmrzY{uDp)(f)q! z<9)_`{`%G@KI~YuBI$FVLfj{+a+UvP8vO}MHTo@zaAfE50et*Ix{!)cbRrk=zW{8J z6d}OmgTLw%4gn+=`$Il)dcQp@82r1x?rT8(NGW%MK<rzn-}@{N3@?(aw6#JiVo15z zBNbKRwAzCznQOtBOR)I5l(SMf0TY!P%oG~T!JHX3UOTTFd=yH98rLJQq&mSOU^$n& zz4men)nLN*$}89EKqN3N2Xv>}$}Z{|nUBfB*y2Lk$}a56BF73AG8~pN{K6<)E&(YS zb`b(EJQnPEEd9H}Gz=q!fffd^mw_3>-r6gJX+yzr7t?8%AEU$mj{(Hp;=xzDuHVX{ zdErBR!L8jo7VA>OF#H$}EWS+aL)FTzNx2t9EEYThL1QwxAw)SCG(sfQ8k&h8jnF{_ z`$0yL#YCaSK!HUc#6`EV#X@O4OCv%|Gs0z<y_hK>7d%E}Oh#o~MrLeAXM9Fzj7Djk zMry1^YrIAmG%)p{2YPVDkzlH3hyrgMM{+Djb38|MOh<KGM|NyScYH^9j7Nmn#xB?b zdwha>l!1Ia34O#zf9ywq<VS(rM}qvvfi%d149J8$NQFd5hipiQWJrlzNQ!*OiL}Uy zjL3|<NR7lukL*a0<Vca+NRs@>ku=GY49S!{NtHxNmu&t?m}E(rTuGXI$(gjtnvBVu zyh)wJNuTUVpyWxJl!4D6fIc}&q)bYsTuP>FN~e5EsEkUfoJy*!N~^p|tjtQS+)A$O zO0WD%unbGF980n+%kTh-qGS$W;fdc+kH!KPx9kudp@I2{OC*C!^~g*0Fp9hk4k;Rd zy}Zjg2u$@DOualzy&QwQG)%;7Ovijo$c#+MoJ`8BOv}7X%*;&9+)U2wOwasG&<st{ z98J<JP18J0)J#p)Tus(&P1k(Q#>|1`Kua+($^(E++{{hg+)du>P2c=Y;0#XT98Tga zPUAdI<ZMmZWRBOf&C6`d1{kU{$jj+O%)tbrjQ+B-ycCfnbIZoeq(4)SF+j4nJWN*+ zPqD$v@-$EC+{^utA?r-f^?b|f%tQAS&-HZ6_e@XjgirMpO!{=p>P$@T9FhFY%>1m+ z1H}r~5>WF5(BzCz37t?1txyZSPz=q`&s<L0oEqoUP!J7K5gkzyEm0FaQ4~#46@5(( zZBDhc%(raN0BujatWo-eP+&p58hy~hoYBJ^1H4fo@1)To9a04?QuWl!yDPGU+Yij- z%MnS>6`fKltx_w!QY_6<Ep1U?oPrnCQZNluF&$GfEmJc+Q#5S=F8z!V!VEPafCn&3 zuB<&bty4R_Q#{R6J>642?NdMf)4q7U{x&TGs&EP`FbhMS0kIH@u#f?zNK_z712_eb z>IjbCppNI54(Gts@7UB$T@F3bR8R$vPu&wb1yoc`RaIS8R&7;ReN|Y+Q$d{!c`Sx5 z@C97WRbAaxUhP$1?bTQfR$(1hVl7r<Jyv98j7cZ}02q(F)ED)DR?KXhXKfp4mDb0k zR=vbl$kf(u-BxR@R&do!Z`BuV<yOb^)@W7NG;LRReOGvmS9wiN^XLHpaLgzW5P^$< z+niT_{a1PI0e~G?f-TtNOaOshJZk-f-w2KXFaQHc02a!Me$CcoSyyyTSI%Tti$&Lr z-OOV-%#RgWgDqLjOxV0kSd=aPG5~nXmF>%BRS#wb%mzr<nI({z9e|h>000o1nT<>y zI8eQOS@j@VlRa9bHO~BTO!|@8x_nH8y;psVhfgRMtVoTM00Npp18Tj?5t&+W`~tp6 zg|V&2!d%Q3_}a3ShOR~1wh72ISleWsTRg2<sg(h{En2A^gPz4(f&Btt>4C=bTd8GM zv!D$W=>gAS)@JpB^-x@|)!VO4*?T=($t_H}&0Njh+;-3uF7RA0xF0o$T5%nbEHDKG z_=GZuR?I~*Xg!6mcw5*-1=juAdgKDxrH0xS+u1c+&i!5B4PN0LUg9lYclcZ5O<v_) zUgm9H=Y3x2?SiP44`KeSUhBPH?9E>7-CpkPUhmBrB2?JotHL6ft*7vqP)q_v%!KtN zU*AEL_WfS@onQK`U;DjZ{7n>K{7%H2(Iu5k6#>jJfKne#QW~Alz%&X4hEXML;2oV& zAT7|yOi;Jf(E=9Qq|IOr-e3;y;NaX}4`xja4q*~5VG}-K6sAlNPGJ^qVHbX37>?n{ z1jgvCVH>_-9L`}K-eGol3!<D2AP(ZJ5Mm;>1|k;XU^rqRPGTj_3Lbu9D2`$&o?<G- z1|JTFvDJc7_<}8-hA!>`EzaWq5aTK?V>3QuG)`mYU5T&#VlD{dGH~NmkYl18<2bfs zE|yz0-eW%QWBxz>V>@MIKptd5E@VSK<Q%?YL|$Y@Ze&M(WLW)-1(;+?u4GHTWK7Ov zP2OZq?&M4c0yzz3Q66PdE@e|bWmHaORbFLQZe^)F08h4LAc$pJu4P-^<S6(I?gi!i z?PXv7Wnd0wVa_z>)C3c(rCb)1{w+-CEKLj^;SrWhUe2&hKqBEAW^B%8ZQf>X7RE2F zOdvo^ZDN20@SGj}%xHGd*zD3v4uDA>0|c04oRjBvhAw)hWOrVGd~Roc)@OOP=LLx1 z1i0jBW+Tjtt{%A@kFn-B!>n$8Xo!wziJoXiVGc}AEKHtdOBR5O{%4F1=zi;_d(Py2 z)@Oj8{+xmK=Zp?tSiWVKercG_=;)wmnyzV^zG<AkC5yIYjgD!b{^<osW*<;fSB}bp z&S|7hYNcLkrnU#>P)blPYVZ(hmZ;T>C<Cj`YOUUCuI_5D{%WueYq1_{k6<g<yIy8c zUE2Fqwr*>;ervdnYq_3lx~^-xzH7YBYrWoUzV2(k{%gPvY{4FE!Y*vXK5WFM>jP6J zqa*^Ogi}C?-?u6iwJt^*0BX$6Y|Y+m&hBi_{%p_=ZP6ZW(k^Y&K5f)aZPi|F)^6=s zKB_LYgvK@k1%T{BGLDRrn{sgxBqB2UXdUY_X3F*itg-F!H4@3L-sEm-=5B81er_fH z0DuRO?j;BS<96)phHX@Yf!QvC+D4opGL9J7q!*B(b1FPIQn52ihO{PbUufq6Sb$)N zZ<WsHyqV<SnB?D(@A;8u`c~;p4g>x!fRWy0j^5;bF6sULvq^S}NhWXrx8(btZ+X5Q zPPXs(rf>s?=?9?X4xi)?Uw{xFaStzX5I1oVM{yEAaTZVU7GH4}k8v0eaT!-}8-MW| zuW=mTaUJjR8PD+__i-Q}avl$IAun<wKXN5EawbpmCP#88Uvej(@+g0DDX(%Wzj7_N zaxO>lcE0WohYwn|?n(v#8~AYRrg81|Anx|n^J?#Kd~XS_Wc$A7yjhL`XzBh-1?d8J z4o;Sh1yBG~Yj6eF<T2>vefD$x9)LnW^aT)b+8}g%o^(nVa6yM~k}mL)wq!?t=`P1| zP~Y+_H*!%A^-nK#RDbeQU-eOMbyIisREKp|e|1`q^;(~GThDb|KlLxCaeao<F(-fp zFmoC=^E9vIH6QA;wq7__L8*TBR*uu<kah*2c59c8YoB&(-*#>9_G;($Z};|W4|j4O zcXOYPa~F4TPxo?PcXwBJcyIT3XZLKU_ji|fe7E;`zjuAlcYgo(eGhnl7x;cJcz_>x zgg1DCKX`>tc!q!Yg%^12Kmw$^gy~3j`B?U9UjknB?y-!C;g0jf9{zcfFL{$cd6Z9i zm0x+5Z+Vx0d6<v+#9r)Rgn^A$g6<ZA#~y-Fy$<jAd8Q22X@`Mkr#<LCdZbT!rC<8E z!1$V{_KDZ|n<s+r)`6byd7$UiOjUqV-PBMO`kOC$%O-%wT+aw51Gfa7yDU$&7tNL? zXc>-sxu1KwPfn*_c1*~5o;U8R=hUnZfadVH?7$o7==<gfRgm9;$}Vm+fC|}o0!9@B zvk;0zg=EXWe9X^$oVfdAehIQnd{a?DFTgpm(0tQBebi6=bKv|`JVCK%eASPA*`Iyd z2M5;Q<&YQs+TVTN@BK3d0CE8SGPwQief@s$edJGm<tJX@{+EH~e}bM(4Q<OqC!R3p z&x-LR5JR<o?U(-Nztxjt^Wx8a+KYzeKY#R3|6m=YX>wvNh<+K;6ac_}o|OSZ<pC)& z)JWYu8Tx)9{6z>r03>$q%X;(fKNyPRcVrBDpy@%lE?TWKUE4RF>pS23KQJg95{t$o zGO1iLo6aXRDxFfR)@xAqa+yvNiUmU&lgs8%ccoIdwy_i<E}zru_B%eWzdtViKVYEX zAYq~5A!4H9B4eZDBV?rHBxR-LC1$4PCTFMTCunFQ!^0cH7C~3(SB2l%D{O4ZNy^Lx zs_6kjDHhZ2frA5rfB-`9ad3dLGIF!CFtM@oGV`<k^)+_3_BMC7_cwUB_&9mF`8j&J z`Z{~N`#XHR{Cs#CgVvTR18bP7>_5OU_AJ<H^CH(SBL#>R(6Ow^F9!jTO|+I^Kml$Q zlU=)*ksip9B1e)eY4Rk>lqy%UY<bQez8CuVxe93WriX!RSm3$`hu4q*GlV+iL5&)Y zh|r21+jubmQ;yk&MU{B$X;qh6w{q?3^()x0V#kJsi2~Kk39ZE7Y<p8@!Jaa70UfeX zKtmKnX&hamlq}%Df(H{WZ1^x@z-CY7)38r%Kev(t!et}(XD?rU5_9hC`7`LyqDQY> zjMgztwv$(%sm#-jPg1OB)2?m%HtyWIck}-4?fW<I;KGL!A7kpY7sxe>OLiURGL4<m z)ljcxo#6H9*{fskuH8HL@8ZLg-)<iK`SIz?qi?U?J^T0a<I`VnAO8LM`Rn8Fuirm^ z{|Oi%f%_Q<;DG}sSRjH8D(E1C4@wvzg$rJHTw}?Bu^e-4Id=<eGGsO)i6xqNB8n-h zxFU-!y7(fDF(wfc7pN(xT8D4Z(%o|*vLNFRbpeTk2}ABsq>(uwndA*iGFgL@Pue1+ zlqpbIWt3G~aHW)Mn3mxxH~N#KO&=NrLu?mlX#<x^+?Cf8SuXLA6mlwo6`W4kIpY9O z<=MoZOY|8<phxrtMWIJvg#e;$BK~od0F6d*5itXZ2&kh-+{GxOR`Nn)DmHG2Bbfl1 z37wj2(vYbUdokcD6Nwr+g{+DSaA~Zp8VVJzOVm2VMS2mctD+c53Pk{U74WC8#2R7g zuu=%1Or*y?u`CJ&!0M}~7|JB-T4R>_E!q3WtqycsRBNLlN|QQIo<s}KnEi5I(5 z$UCpI_1?>Dv+6RG?-S70*>4sDRN5=QQ4AA+!EtsQ8**WeD(<+klzUK`JE2R;$6JO> zFN`F=XtIkax2Q6UEU)OYiZG|>B`u()ML{2=W(>>5XwGFq0znHsG|@#HeKgWZD_ykC zJ9kik0#QpnHPuyHeKpou{%d`80Zxz5wbvnl9rg!fll}DBWvA_d*=uLew%d7q`K^ZG zx@|_!1mptr(tRHtz|gn)J$TTn1ptBo4V)lA8Haz6_~Noatv~=%3s5ywP#v(f)Rjwp zIn|j9usP>ggAI8pjDOI8y&SMUfa|Sq@Otd9&o2Ad8KBO9>K|N`LFNPOGRE$`d+_@M z!4J>D>58Ww0`emi554WMG#|MKj%!d1L(X%Lm-kc^5PtW%i|>5y4@{r^)4OQD0rCYP z&;aXTE8jr^aD#CD^X&U>`~8ULJl>swe+2X&2i~Uv1p*I&8(^Ra3~0Ounqhq%fM5Y9 zW;1X_=5A!j+uk1jK)4a+uNcE9015qOxCCmzUMLV=14p1h?YWQvFpS0I4gk8*Ma^>x z7=Y9U07S}ZO@~qAArYfC#2+rPbW{Ud4R5HuhLoZJwxc2gtoSyNY4HJA?BW-%$VC7y zu#9iZKoo~Z!$>g?WEhDd2+|10VaP@USbQV%&^Uk>s<DM^q+kf-m^?q$QF40ZBODX? z00bxtij73$BNrJ-MrO-?W*p=h7r93p_7RW>q$3A}M?cx!act3pqxPnFNKPtJfuP(# zDHYiPL8kJOs?;PRF?mT(@^Y8HY^5#jxJoIqu$QykBr%Qo3c1znEMR)f#XR`I9eE5y z9Q&ClKC%9+ZE~}l-uxyw!zs>jlCzxVJSRHSsm^t>vz_jICp_aR&w0|bp7y*aKJ%&1 zee$!P{uHMMok=%~*-e@x0Aa^Om_olTW1)j%3ssi)Kx&jBqS>RUA<-C(<NZrEc=BjR zGw@N7hE#+lP2VCniobt3?<f^jDF^V>QjF$Nqs8E;pU^h~i>@@M8BOU;FQ|+T*bt^g z4QeSAhSZ}jkEvrA8&4BBR1fq?12R2<QuhcvnnLQSenjd-6?oO;(Ub&HEdx|x8dj;! zaH<P*>sH%}!To(zqHmpR9MyW)fTR_y8;EKHHuYDS23D<r73@<3%9w#dv!DhQ0YZPK zQ2vMZ4RDlQni$?USC5QAvmoFs4ikw%Y88^S6aC{>QA<|UnwA8X{H0Y@Yf}9=aI>-n zEi6U*S=oZXv%cdkT6OEd<V7-Bl>}aKlY3m(3ipCBC1Xtg$h+2B6^|Ows#kTXU1f^! zw$>GaZ-cAd(GFLZ+chs<+1A`*k|Dh8&8T~Yi>)j=&bRZ`mVA|!UOOtbVvX(KW6?m( zxR?tAmsQ$P4vaL2!c=>Uf-MK4>fn3XwSaZK<XAJzQj+@Aq#Yh>E#cVL<)!i{ihHnm zBi!J-F5|=$R<BJ-%wlCIZod+qaR78`h7sGctmpgc_<)SQAKNs@x(u-wFO15v{`Qy} ziaRQHQJeuwu@}iaUh*<<>|&_MSOhml<t49tWUi7q%W-w9OV5mCD=T@-W<KvMSFGlL zi1~d{eHfM7jOPRl7O8s<rilCd%+z2kz#E7x3k=${v(c8(k8L#BARRVIdyUdgv-Hj| zoiR<1i<zGiXpQOyU_<9*z?La6-xO>#;RQ{Is%Baj1i1lAjna0srriT>EjwM?Isvtw zvafNSK3QMa*I9(F8Hf$+1{^!Z!o(#jLuu?T?i867rggJJ3+)>^cLThe_Ts>uI&puS z*<dSo7PG5t*gmD$=C<|xaGl=#soUBgP$0IuxL*&fd)W3aFPtGLaDa>cZI~5y_6Pb6 zXA5t;!h*c-@E)$Li2obn_0HvA|1C;f^Lp7F5B9D>UcCqknz4i?H3$$LhRMFVXv082 z%ZVli3wA)*8L;^SaISOQ-JItISKk097+XFQRp@J_Is9(rfdl3i=-9)b(nFACqz|3W zx(oWfIUrrtZC!V=Ibhe-_jRMgnCgneu2`d<>v;RTe<T&J+2Nc3wR@ZFado=e$sob3 zcl*7t9XQ;vPH?v3o#}qJ-qoMJJ;4{2dx`H~<A?G114iENHKqFcHtzGCk38p_Z+qEF z9%_P)8s&#J^=qz~=$Ny<Z;Ot!TlVc1PWK%+v?tT;bCmm6$X@>2(%e15a|8V144*ij z_SWeC%OTTiK;;H*J@k+E`bj^!`UWljaIh~N?FWbZwzvN8MIS0rFQ55+Kz)x?zw+o8 z&9bZ)L!M7rbJg{J)<l=T>*yarV80y%4Y0iZ_223M;Qr;>{dJx%$xr(%AoEn10`3_9 znH1m6PNYE4{{fHlG(dfc0CuHM{2`#TUEsoT-~lq+2Tq_e0p8(_pxPLq0IpyNoZ#Sz zmkb7=1d1J2MIiD>SFiOS4`x>l7F+-2*v&DJ2G*PrCLs`(ptQZ<l*z^eZom)b9}*_k zmVDY`CEv-J-{}EGHJypxBozA*jr+wM{4rs45na)ZLH-a{-O>pi*tDTY(cK8hVRxya zw%Or!Vb=liq3ZbI6Jie_GM5Y9VC|6=P0bx15@F6Ukm2Ro5<1~8DdGnhUJRn4-svC+ z1{}btAa~JKA>v=~b$}k$A1KP995Rw7&Qcs=A|yqkAqwFQGU5?Zp)FG2{J|pr1tBEn z+Ssw88|I>GiQmmQ78jnM7ZRBIT|fe4Qzw9*2YKIbNTY91V{cd^Z(!qYXrpd$qwqZ% zF@j6Z6dKAoU#g8E`%Pb^LF3(cBW}3kZNQ^#$YX8LBW>8@Y~Ukn93RCDnwT78^P!(L z`ONCEA4ViV8Jb}i#GNEQj^O=IM#Y^1eUFh*{v`Q;kNK33`Vd+ANaO-SQ2el+{Y(qD zS(T>k-QXcl0FeMgf}%$rB6@Wq1Qs6Inc_=Y9Zhz?`h?w1{$ftzVBY;4<_Vtsj8fZh zUhO0$Ar7VDiO|?sq}MfNEPCE1qRmfA;X{g4+c1y<g5(v6q!rGM7XBkZA|rRCA3?4k zIwEBBi2*GU5)8J`zpYUrY1>=&5+!AkBP~~6E(;{_WpM#ok~JJKP2ekC7u$(ZQkhcc zt=Y5Pr7s=k7A<D*L8fD75@g0COmgBOUgltC=60nQXqsXRLgvFUKm*L3Y@B9l{t;`c zCYXts!OfAzCFW#iTNvFY7>!YE>Jk+G&81vooc#4A*fd0IuBL3|=8=WjZ0eG%iQ-(* zWhmL^at7KJBH#FtUimSfIXYt}{1bSFr+AJhd6uVno+o;yr+Tg@d$y;0dQ&)(pE^F4 zIkFt<B^dNEm|xu;2RId08I@8sRe(CifD$559o(CR(u8#wl*vY#AtJS8*MVM?SZx@D z*56rWVvJEJ;jNX2?$w7%Xom9VOYJ8j@>QFu7*a)EpDhoIw%Lms=!kCUe+od9mDEUi z7`|}Wj>@QBwOLoy=<vj-hWSZdg5reoTZ2&p{lV3&Eht7NRizwh3npotft7zs=~5M_ zmI~;PhTe7dqsfsaK-TA44uk%sv|eU$*9<9_ULx0W8P}T<*K(y#eEE_}LfzO=7fH2E z2ihr{{#IS$7I=LkDBh`NuAnDE*r5h$<2Wi#-l=`*SAJ#72Aa`*$=++pl5b5HRFu}I zo))OGR;Uu9r1nw=%Bf#^5v3;Gs8T76ovJVW>2~F5bnR)W0_rdMma5v1o33iF!s(oT zO?+zSd@7nif?)`;-^1WVTdKmBU1*;f8BkFxM_#K&dS;!?7y`x1lVuf1ahVMY<}F!i ziM^S(>S&h*lAKkiZvrW|&Y8SQ>%2aww!&+@?o_)zDB{tmcHJ3C;vv97jKIRwpY>+B zMyr)JD32Ohkwz(q3I67`rf4q~ne!a%g%V-J_U6R;YazyKm6@29qMgLztH)w%lveAI zX{5>`EPsXR2FWKml3IOA)3I(EJH}1Uu8lsfM$b;h&q_wn&K@|zEU0Ct^4+WlnQ8Q$ zPy*OXe#Y9rZDzYITefK)viT0C{U*lwUA|>487SP(DcqtaY6{LI*(Tf6#-(_zo5W=c z#pRO45hkm`?Zrh~#^n;$dR)l;ngRN4#}T62uB}#vo4LKs#93_@1+Kq!ZF9np*?6V) zeC?D`XXD~+a7G)to!hM(?znmG<{ECg2`=H%ZRq}OwvpzJ6>HMYtf4V2^p&CDyx&4* zC8|1T8X~6O{?;A*RAt%OF1yj8R307hc4YB#rlQJZ;0-Se?$u33B=D|J<xQUIOz%%x zuklJ{=2>pfbskdQj_)=dQ|8?%g&p?ZVa<Ww?`m&W;vc@@ZqljY-Y#YI3La1T<o#k# z*kx}}{^b6m?;7@&lM<`ZW+BpUVVNc?s+{9ms;TR)V{NFec>SI%NU+fEY}yQMTWBy& zaPUoNumwL2>mu+m!fpdQFa|r?^`$WEt*{GI@C!%d2$N|ED=@0rXY>_A)UM*-iJ<E0 z;m?(#@+R+Q2C*wL>IVohK(wMH%HR|_@z?d>4Njp49<dD4q7u4J7SE2R@~{o=A{15_ z6>EV02y(G5A|NW3*8IJS2l#LXc5x`8F%w3i9XX&KGawCG@f;r<8SCFZda*3pY6876 z1{R?(n(;6euz!&-7n(5i>F~1xvN2KO8?xaMS8+ok@7ZpqDKha?A~986a`T=sDIT#Z z#xXFWa)fQIB&#wOA95eV^34%oCZQrG_TnJ^l_?4DER%8*#$v^W=7UmlAvdBgf3dCk z<RC7yGW#SZf3ghbGBNijEzhJaOX2~&a`F)J9O5A^CbC!_Fby}d4KL#XG^2NF0t-`L z47cMtLof`-b2JuhI2U8-nV%z{aLXxesd%tW_%n*Ov(5@M&%X1e(Q`8ya50W!S(^Uk zg$!gEN|r&QQ2wMT?NToa{@oB8>iYKYC~DpDhVZkEFWBL){hGA?I&%S%wCWUP{U{|F zf1dZMuT`>iOg$x${jU?^oJ-bkQ_`C;btOl=4Or4I`|58_C$&aG<wmmXO8a#GFm>+& z4@vSdPTF+xRCOI2B~f$rbHVgeA9eq%Bw1@URYMGknKd{k^dhSTLziVfx16(<&<@k> z3~jVVcV@}jwcm**aB}qBCKKHnCu|NjY8Liu{?Wbib#u<Iay~Y1dQoIAwrNi7a7Geg zyJm7u_Fh-EaDrfT!sV<{CuRRN-4f?t&M9h35@5z@XPdSlt+r%JHf1)iXZ|*wXGdEs ztsUS}=WHuyY3C9RRh%0YXK~*q9_{vSYwHY06I_>bTBh@Nz9)58w{>4Pc4xPCZ#Q>$ zw|Bo&a~mT$C*!G^GY-d`Qc*Mtdn|~SD1r{~GNovXk}+cWYm8dyeHWmWs;HORXt?%h zjt2Ni>GzD9E0YqaN)f4r<|s-PxP%?}iBc7m8X3d-sEO`(mS#AYYWSDJ*nZD9f(I3d zG1))MH;~d)g;)5)Qjvx8@q?plnW0&X_nw&XH^`1vmo}_}!nfFj_dRnbbVqbSLZ7QW zMO`E6D8CnZE%AC)Dt$RQevKF6T55L5Y7BWQsb=}BifWY`Q+%ZWT0o`0D*k4-YHV$} zse1YC`kf$;`Qq^D&sjMp1uLD-DV)!FB<U)r0ur37dGA`|rk+=PffknI>TPLxm+xvX z5$bF`SKA;uqBp9c<LP&GmxI>%YN;_=`S^C4UR=N0LZEdd`|G))nZ6=bsEc&RS}De& zdccBsN)ar%KAX9+x{5D2t$&z>E_SR_tT(r8t3zVCZmheW`dL*hzcyKmjV$~IyUFf1 z%33SSHhW9$b*`Umwu-EjrPpo4y23`4l%+Vhs+q>#y0mXQOi7u!zZj1$&$W+xyISeB zi!85?IJ!zKrt`Dgm}AJ~yT0!`zxTVp|2x13{L8SlKRR~;lXvQ+{-wY#Ji|A<!#_O4 zN4&(VNX#1R1Jh@aPrSx&JjZvu$A3J?_j_9#d<`Q!TA~8Vr@YFqJj=Jd%fCF#$GptX zJk8g<&EGuE=e*AEJkR&M&;LBo2ffe_J<%7v(H}k1C%w`my**z%%H2RiN4?ZfJ=Ise z)n7f<1IK@@08n&2*MB_<czxJ|J;;o`*_S=qi+$RYz1p`u+rRzUyM5fl{o2pH-Pb+d z%YEL{z25gd-~avH`+eX8{@xG1;TJyQ3x47gzT!7N<3Il4JAULt{^C!*<ySuDOMd24 zzUFs6=YRg?dw%GH{^pOq>6bq0&j3G!JnOf<>%Tti$G+_T&%P1R1OYe^?&rSl?>_JM zzVH7&@CU!}4?po2zwsYG@+ZIYFF*4)zw<vo^hdw+Pe1inKkyfc?JK|m&=2=_zxRJX z_=msvk3adBzxkg(`lrA8uRr^@zx%&G{Kvoi&p-Xwzy04o{^!5`??3<dzyJR~1T;Bm zwHIf-dG{X-#Zf#SMKKu!021aP(k9Sqn!GKfn1+JEC=4WsK_W3g6pzVe<`^7K3Qy7% zo7HZ)UGEnh7LUng^BJ91ui0()8)HJLST30CfScU-k^n=3fiHlN(2qi=LI6<WM~bLn zF;GB4vVnneBS9d60uv52RCJWI)btcJRdtoMwN?Hu<zc1eC1&Q$h%0eJ$<_B4I9Pa? zxY+m@c@>t9_U<+rI$C;~y4w00J6oG#S+=egPo#=x4?&<Nz`Q0&Kwu!q5|c5opdE;& zu%hY$Ghx@yoPc{c3hgSWM~FQ}?A!r5s3+aIBlOxK_!m(iql6g`(NH(>A%%$+K1oQT zW!K1)B0Du$Ss`W1m)mI8w0RR}PEy@w*|C-9gP?+c^mMF*Ac!E7M|vodndx7Hca!`K zwGiNG$EZbba59-m0Ma0R_88UU)#b&kk=BycieY3Cv=e6LsGt&(NCf0A#9XQqaA3iM z2^X%w^P4lEYvdWG0EEGhtBxsGwtN|LX8vJ_oBe#VRm&#OSVn6$eHwLY)vHl)_8AUD zjeQUi`2k|_HlzUWB0n+mqCnH#^QiFdRg|R^;kEZng=qXqBDW2G3u($p3W<1`Sa<jS z9ens`uE9O?po7N^B@B|d)xifklNSk=Ul<ZZGV%%7+gEDIwyXe$>#Y%_`U6lt_L?eS zKJG&J%?a@&oRGo_EnI^<#V*{C!wx<C5X3h!{0W8wOgs_A6jfZ2#TH$B5ylv0oRP*F zZM+f39Ch50#~ywB5y&8g9FoW)jXV;`B$Zr}$tInAazp~8oRZ2at-KP;EVbP7N)){O z63j5g9Fxp4%{((qF4bI<%{JY9{u9nP<(!kwI_<m@&ph?qlg~c={1ebX1s#;oLJg&o z8J~hUAgM+jeH7A2C7qPgM+rCq%}h1jl+!dT{S?$tMIDvYQcXP-)l^komDN^VeHGSN zWu2AQT5Y`**IaeowNY<6Ta?m29IYp#q=>cXQDGz6?S+D*P>9k?$qb-?15ilM%LCdO z0EL3Q96+Kki9H~IZ^Ko<TNJ?EZBqhxeHY$%<(-$_dhNXz-+cAmm*0LN^;J<Af^`%- z_7>?U2!E0tt5Ic>DtMD<F%`311={Hr1pz4jQUGf)w)o46JNB~TiZk{YT}|Efm*tjS zei`PNWuBSlnr*ff;94I3W%N`al7P%so*9s~)1r+wdgYsyUYhBqoqihXsHMg?XMv}_ z8tbgJ-kR&Kz5W_(Qvq&)?6S>18|}2!UYqT<-F_SHxaFRk?z-*18}GdJ-ka~f{r(&9 zzy%+i@WKs09Pz{zU!3u|832HgfgT8B^2sT%Tm#HU(EJ0=8+gF;Ej$OEgU~}f9re^z zM{R`w%B}(Q*e^G|gV;5<o%YKuAiZ`EY@eO?34#~lcj0C4y@KIaU!M8qozF&)7Kocb zfdwi+;6Di#P=Jlx&+y(2@Sh)_{PN8=rH(iluw9UL5{Mm!^W_Jug$n9#!0fOnKpTG- zC`h1w_uE2yT0s7-=}n+)_9NT;7-&8K0ziF%*n|Nn=#@p8L3|m=pfEPrKx_z50`~*q z007Vw5C%eeC<Fi?1ZWY+3CDjdSONe`^Ee1*;DjN(p$B=0nc)2JhZqRXW15122Y${1 zjH;Fb_ToALJm6>X)8OuihlLE%u5w+#A`(m2!@#Ibhn^7O*-QdCKlrbIXY9n|YQTa; zkPQ<6u)+oWAx915FpXZ+qhpFlg$w|3h#ENJC4w*kC2k7|?~{c)5+JlkRgQFznn0yw zajHsE3X+pc3KG}Hs!S3R5SqN?q(&JwQ@SjTn;at{rBcE$N)mwW@E;3r=(_Ax0&?hZ zA}BYx3I52fkvNS?r7-JvN@E@qnY99%u_gsXO1Y$%o0Px-kidf<`~Z6bYh)1Mmy}YL za*~)tRVgF&O^tA~RNX{pug=*?N+uJY`5GoIbx5|bs1kyas)JbQ2?!SgK!5C!UJ#n$ zsQ!_0m<L@TEfW||hd%U}fc#;kmMH~_21hv+b<`k1*@%RlXaL}92!13Lh(cb%r0tXx zIY9?1azZMlDfMQrUizt+@^YQxbW}?r+EbtY6sSQJD!z^gDFhrKq)An3QV+n-SgEs+ zHT@S)S@k)2K{b>^)#_Hc+EuT9b)(S)>sZNJR<oWJt!Yh|SoMX~QMvVIa8>JE=~`F2 z{@xW<4}1ac_}W*${uQu+73^RMTUf&$7O{y{>|z<)SjRpVvXPbSWGP!&%U%|<nbquO zIony!eipQ$Wo%CrDO%H>7PYBW?P^)uTGzf7wy~A%Y-w9t+X|Mn>*MQCKo@}CF7^nd zqXg)Z@BrTu_PCNA3Ly@gT*NxpxPXQ3ZK+#b>s}YT+12iLx!c{>lGd-&J??R-V~@|_ zcDNQnz~mMYUE_X4kqtN&d5P772eelZoqQs4e^=l0%GbQVh2j7L5ElO0j=TY}uX{1F zVDCN{!V#A6gehEM3tu>}x$VnC$g&*|kYf>)m<=E1(G{7P_$w~y!8PT3OB+!B04<yF zPm0fil-Mk{5*mSuAJ(!*AO9H0=hN_HVA2swgdn6n7(!mAgW|Nz*d>`z0uiPh8yK%8 z#ykF>gI>&>BMYd<K^F6v$^4-q7a7gyk%)^k(3r-&B|sao2t_!OmQE<l%9E_GM)uqh z_?`#N<z<#u6nwxk9~#k#R?V6Bfa5JJ*D9cZG#Vgnhz*oR(V5otrUye>PJbHIp%!&D z7(HrJpBmMv7DaCH+G<z78rHFvwX42?(^=md*SXg9uE&}fUjG`{!4~$gx#sH?{B^zS z_3L^~rR=^Y8`%$VHeZ=dY-?W|+u1&=v8(+kXzNp|l<KylzHL-#*L42e+E(|v+1>6_ zavR;}26waLo$jQR+uPxG_rCevZ-2k+8<n(o5b#ZIeM@TK$4+>`8<p^XKOEu_mw2w= z4eo^>eBS21_{AsQagTo-<fsa`Hvy~Lzgn{78P0IXS>AG&huqjNm-)<TUh{s%+~zsg z`ObMBE1dTn=s_3y(8=8yC><T?Nmu&Pncj4#KOO2(m-^JHUUjQq9Y&r$zyi45b+3ON z>|q!C*vVdYvy0tC3c&E%+1_@yza8#zw{Qy1{&f@89q)P9JJ}hPn8jox?Q$3V;0a%N z!yg{;hc%A}OoWWScc$%uPyFO5UwO-49`mz}#bHg<-N*aS1pc@yggX{++3EJSAOb!; zVI?2l#IFAIyv_XVX<vKW-(I_$H*Dwm+8zVD*RhZ;?CL*X3nE`a&2=uX{^U~UWq~9g zffmpN3cZv9&vBa8`fKVzr{MGYe!bjB@JDpJdfcBM{pnZ#`YCIkN?SVn7B~)bmOl`N zIA>XXxt>C#A`=2fpBr*(>%Oia1fc6U;Q+Y~0ljVk7r+1)AOO5B0wGZBhJx!RZ~`0f z0mEYKTCcC1tGyNpbnXek!b`Vk0C#?d{Z^0#ThIkrknRZ30_|=9B{1wVFaZ(}>>h9e zE06(mPyxAa917q9(M|xtt_L?k0fF!bzpeqht_LYl{tA(h3Z?J~zs?-M&I5PLc0#bP zHbKC+Fm|R36JF2^&kzmMu<&9K38T>K?(XZ9pbD?h0TBQSi*W3skPelA2k=k}kB|Vp z&Iq_p9XOEdw54OZt`O~T3Z+n6+^`Yxum=~B5Z6o#wGY|OA$H)4xYn%uG!gim>$t#A z4NK7!PZ1Tliw*G(4zZBH3~>_A!4MY_1If+}DNzm=APE-W5&tmYYH;s@Q5c7j2GuSV zj}aM@Q5g?!73;1Qi4hv3vG`!%XsXc~jb;m%Q5(0B8@n;PQlL}P?i$Cj8jJ4%P=*}S zQ61+19oNwv-w__;Q6A@!9_!H_?-3vKQ6KmIkstffAO8^`!I4wMF(8j-8nF%`6H*}; zk|7(?As-SVBT^zKQX;E{B4@;%cIF~Ck|R6PBR>)(LsBG1a_MwS0C{iiOfn-EB?b<~ zC0`OIV^St(k|t}?CT|iab5bXFk|%r8UEs&>cuz|nEcDokC4CYplTs;{k|~?gDW4K5 zqf#oT@=8K4XByx{Kt~2}XDcto^LVP1OvFl}ku1y7EYA`x(^4(hk}ccPE#DF@<5Di? zk}m7gF7FaA^U^J?#41lR^tO_(WWXy$@AX`7x5Q7eigKool1lW_F&`5$BU3UblQJvQ zGA|P|GgC7+b1kc+FN5+gjj^w!MD+d&VG{#vz^>1@OjGp8FAN<^F~hPYr^FjWYcOv! zc5qWGbCWlB(>EVLH-WP^h4VLq(>RCoIFVC1i*q@N(>as#IhzwYr&Bt6lRB9bJEJo@ zuTwj_lRLk&I=3@Cy>mRlQ#{X;JkirU(~~{d(>==*KGicm2MYi|r#=NM0Mvvl5d|n| z0V{=4H2F#a26GI%&?NVZ1Pk;7PY}9PaIq9Kr^J#<#E>u<OtH)_urM@sz;OG<t1v(C z0}&HM5sO0;>pkTYMcZ>ejq^lRG(}q!Mwc^2Uvx!lltypVMRRmUb<{?A6i0UyNP9F$ z(UY)RVE8Tt00{H}k~BZ}bN)&`)96C;uLhJt@5|(RZ@*x#1aYSf9~3MdMM9_KD*lW% zJi@%FkN?ax(lB5FL~1HR0xsB0Kmf4%T;dg%08Rm7&CKG`^mNcPATB_F0488iC7@6n z;7}8lP#4uu85L0-RZ$yNQXh3vBb8Dw)lv@?Qz4a8E45QM)l)+iR7W*aKebdz)l^ee zR8JLES5;M86;@eQR$p~iUDZ}&l~!~0R%;bkcU4z=6<B#ySU+_SuH?Lq#|92HN%7}W zXrNgMl|NewG_^87afeEAM;%U+SvqlBr41C5OB9LgP8$nCGcru2qzWZb5`j<&9gqom z&<67kUdwI}ZSf8d{_tHDkP+`ST_f>l?6nagQ4j}!7zv>343_H<b^#QYVGq_}6ZT;j zHewqVVk=f+EtX<0R%0_3T`_iIKh|SIc4I+yWJQ)^N%mwzHf2keWmnc^Q5I%RHfCFP zW?xojZ<c0p)@E~-XLr_TYZhpAHfVcxXn$5@3Gh<B4gdz=0q`zAy)H?U76PR<VWZU| z0klk%^txgbOlPxW<5gXy5G%N@UFk3i1=b1owHC|n0RgrV=}-~pwO<9cZEw&IBhd+m z(P)WQX#19L|MqY9R&a}!aBp^S{q}GJw{R5~aThmnAJ=gsw{a(zaVr;cDOX{kkZLb* zbIa~&7a&Rgn-+8pwn@FtGyBsgP!hw+^^(ptbMJO-F%S`nFmA8!ZhLX;Xz>uwVeG2K z?0OJ)7l0Cl*AjV=cc<`dE3v?U(QX5_cbRwZF4uA;*K(!Tda3t%rx$avmwUI@d$kvQ zvp0OZcYMEBeD@Y<MOSmx_jeJNeYwuKHurR;l@`uXbuWx-8^v`wvn&aLf7$YU$v1!n zn1Ic<a0j@26BvOPxPTS7ff?9<BiMm0R&%lBeQi-cP1kDuvahNYviLD}ST`wLw;)T{ zgy&HPP#}c|)Js;_g<BYgRXB!OScYquhHv<VZ8(Q<c!phghkv+-gII@!n1_ehh>IAB zf%yK2Q&@?Un24Jgil11Dmw1Yun2N91inBP1v3QHMxQe?NjK5fnxp<7dn2gW(h6{3j zbH;wH(sn%fu|ODf@^^&2c7;zEkMmfM_n43S*pL4hkONte2bqx5WP_(wKnd%PTUcu+ zCLcqXs?61nYm<^I*^)0A_tuz$6M3)z({=<?KvQN~K4u&txsqhFN-$ZKSDBSt+3hqL zk*!sc;TSlTl$19{lsm;_yu_4^vP7zcH1to^hMAa)nHXR>N;`O#r<6dug<DeKnM=l* z)x~3wN?i(Inr|5edU+*(nMyJM)1m^?wBYZ!!I;k(ozwXOkU4eT7_mHAK$|(2MgCcu z=ecBt;+A)Lo98%^Ti1@kIXSGu58^`)sw0rN0D2Emofn#+iJ6^i;eHPbo~>1$ce$QR z=9veenx9z#qM2jDgqu;BN+d;C5@uZpfnk!uVT8h2TE#_(4yI$;<{r94+*zV&`2^%y zAGtX~?U<w+<(}GOESkU(@@oa6APHnzsh66mKZT~Va<TL?cI5aRzf^yHx~a2TtGBvV zo;sp0i>gJKF{gy9%i65Z+El#Sronn2L%M6L1g+y*uIIW@)H?F|xsIzOk@lLe``WMn z8n6Rfum_v43)`>{8?h5xu@{@M8{4rT8?qx?vL~CeE8DU!8?!T8vp1Ww{yW>VKO3^` zn(gR#tfhpmPaCzjTC~?rti746QyaEpTh>TuX(45WXyvvcWwlq=s`GlJWgEDIo37G< z;7U3vN~i^R!(dwAU|OdI_^AL~N4X=T1qvXyE#;7td9?Amv{weSzZ<;6TfE1cyvy6X z&l|ndTfNttz1usHj=NE=o1k8RJ&YT^ABRC2hQ1$XEa)46l47reV7KwNx4S!Ggd4#V z{Hut&bsohI>U%8sNxoU2zmvtk<(oYOJiFWZu1CqA;X1<=T*OBlsAzk-kK4LyySS_S zwq1z9g}`-QoVKm|QRsWXEsV5X+q6TR#CzPwt<A<a9HQ5n!dlz@br1Z<lU&JnEy$k~ zb%(sebIQO&oFuE<%C8*Dvs}x!oXc~hlA2sfpp?V6+rxePwduR5x@)}-zyaFa&EFi( z<6O??oX+dq&hH%0^IXsOoX`8*&;K0I16|MuozM&2&<`Ea6J604ozWZJ(H|YsBi+sw z6U+%f%%PmjrCi6odr=$?;LcIfLtWHIozzR+)K4ANQ(e_poz+|2)n9$iDP6;zoPNig zwYhuKkvz?nX3ZVI2=D^|;yeKS{MUO!*kK*nlU>=Do!Oh+*`FQSqy5xneQ1QdrlY)@ zkDSUq-DP@R0N(uD;k>|hBiQpi+}r%z-F)1YK-$~g-QWHl-s4@~=bhf=o!W1_!i*fN z(VW}Gji{X}QtW$GydB)@924ri5F8-g(>=})zTO`m;v-(-C!XRf-rjkt(l7nmGd<h) zz0)yHQkq*H2If_Iz1Z2^0fxQJfxrPGqX61m<>~z7%RS~<-sLSG=W|}?cb?~a-skE3 z-mM*~Z@tW?oZmq{Qbb<l10Gg-{m*0m=c8Wgr=IGo-s;~y=xP0GiG0FxT-%Rc<o-RS zAw}R-#p$me?bBZE*PiX$p478G%%6PYiQbpL{^Mb#>}Tcd-Jb9J-tYe&@Y^2lEnV)1 zp0`i=t$!QrZ)WcU-|-(G@*`jJ+kNmg-tY;$=>Btk+mW7M8h`RXAM`_C^hZC@E5Go+ zy2uM$^U)l_uGE!ZANFIv6p<X?Q$O?Ve#vuR_g^jJPoL{0%<DbgmoJI<7c(P^Kc|rY z_>Euro1gigKl!15`J<our{DUWzxuKN`lVm{yPx~NKl{Od`@^67$KU+Dzx>hv{Ka4W z+n@d4KmFl<{o|kh=imO_zy9(6{^ei)`=9^+9|9PAa#kv_&A0Qvh1%#<Od=7gdOR+J z004T_?v3aA&iDQg?6owZ+DausnLs3FsVRC&oz!R*ik(`w*{in8{ffQha2bpqo7d^H zy3Kye-Sl`Jj-T82`MbW)_b2E_7+44h{<WlZL@-1VGs95h7*uI_iODkrB4Kotv=k~j zN?K}qikhmr%G&Ds3L7gsOIvGui<_&v%iHVw3mhywT)TMW2qQydliBS2yt7&2xdJLV ziJh&z&E4(&4IVB&PF`+)j-IZ*&ff0+4j(T+PhW3;kDsr<&o6Gxhz!y)O3(@#3?hxl zHAdEI5bJd?VnKouD^{XtF{2QT9DiW!2=XIHjv_~9ENQYL%9IgTvNU+0(~A=+j@?KW zY0}G`(Po06hJ@y|hrARLrPbmnuB1wBF>Q*}DAZR^r^c#km8#UNtFUrq<@Kwon3851 zTM<y%HJ3a|&Qu7LKtr-(Uu~`aYbq?>ym$AG((Ctc-@r=%6D~aXuuR3L3^OkKxD;7w zXIb17xVEk42hx5XU?|jK<Wh-aAsCP#!2krj9McN$%XKW&5(*4xT^iPE38!)YIt|-5 zui&wBze*s0b%FxIe>2}DTlaI@&2<%+&KkN*43!&QR;zO}&xD0Pe+o@^v?$}Q1ekCS zo&77~)qAO~&#QNQUj<mx&lTN#0tAp;F9Eh!fB*tKx8E<z87Lns159^YgS)^d0fqo> z_m534U>2TuFLhRgdDWn&9w`y7qM?RMt;ouMx*T|%07_Wsi+(f0m<nwS$ao8lupo!u zEiD#_%Y6{`GGT2M?#TY1f(W=6B!@x)^j%KOh{zIoCd4&Yilgv|%4w<XhT|?kzSzr@ zHzHX}0ZLGp=5R~$BIgNB)_Ds8(=iAjnW!jv=PrBF2_>Gq+1Q$u-KC`^TU;JQq6T21 zC}t>vW+fzEo!0fKSfPG3Dp#d)x~WyCs+#IllbKZ$T3HS>X_s%62bvP1WokmJP_62! zuT25_tFTHDJFKxsA$zQ{L@~Rpqp@1{A$VKX8j?>X=GsYmyms5qJZps;&$#5iWA3@_ zq^s^a?6#ZEyYQYP@4V&IYwtMt<{OT*3}9Jpq}ZC|Wd)b&+CpjyFU)Ym4nGWW#1cDv z?ZC|xpg_hNZ~n}2#~yzSa>ycoT!6*Ulx(syD5sps$}C^v^2?RPEHe-^*X+a1IP<_D ztp9rWh_#aL?2)#Xniv$q4@}H-#0f6gbkqwo6F}8IOqrY|RR?gjlm%g&@c{}R;PKW~ zkG!$i0+8LY*#V#(^2%5vu!Cv@GUtNb3r?`~0C)4PH{N~!owwh6&wRt&bsJtl11`9I ziQ+m0?)U?c>maw+lQUj<5R&IW`N@fEp2OBUFfah=sH>iO)-SY9_v@tpkh$xnTfKwd zH-t_>>$g2__6=?efVc~v<6t@tykB0)@s`vc`|VsKkGt^I13$g>*<+6h^gARU`Rv*s z^c-c={(2WMtwh6g>z7E&h2GLq&!1dsZqDEIwcF=_TH%-MkAwd8?PM8SO~y{sHn?%j zfC@k$$KIwuk)ce0s_{Vs0C+(iWH1LC#6bsd@WGt9fPw?CAn`2M3=*2~akLv@0{qv5 z2AmKGDvaR_FXloR>R^BU!Cdri(8C-A5eGwjUJeKNJq@}CfDe%2<0`j7CI*miHalVk zlgI-nqA(LRgqamfh{e){5qV=2UiZGp11a{Pi()iL`FsYjpb2b!6ET{>-ZvNd;jd`{ zSXdwTI5GY8V0&sTBN@&346s2kV~gzAY1lThMmCaTkBnf)P)5i!5{QbH+a&ee^GQzr zRt=QaBV`r0$U+_v33L;xL?%^f%EHkMlZUKZEPe1wTXM2xsqAGNh4-h&1=DiF3_vl5 zdCFY=5{3X=B?SiLN#(V2ibh1GHERjFTdGE9JH%%3l<7@hB66AI3}*|iDaAsLF`eY| zR~&JoJ~}?5VBO-@HS`EbiPbLw_sb{51jz$70zr)e?Op*9aKHhGOaO3WpaVh1P)lxX zf-gj37H{YREqV~6P2{LXLC8^_p>c9H;sFCfn!Ac#F`E@V9Y-muL*<p!5(Qn_OhxyA znqKpF&m1A&PU_PaoRFtL&FN23in)<?lX}6++f6M8MF1A{h{e-sGoOlsss8>zry|U$ zO2N2Pa*A`T*CcCKt%?b>y3w6LgJ)*sIZr#*^E^uPC)1uLC%gW02+Z5w=hWF(95}CK z8dDo27b!LZ9d=}f)fi$2v^I%y%zM4$on<*!xVu&EvYPE>_OeG=RJttyj?-K8)cJtF zYHw?$HQWz@I6B*X)@_bMt!lBy+C;|ow5#>2f*KcF;2Q6@ox_@NQ%hXWjx)5g1z#;k z>$uekfTyMnU`v(TOXl{rx1=4MY=LlF(Jptl<LqlwI|)AXruV#`9q(|zi`(}8AiT^| z8CzwQzPG})8T4ETUAvV4eywA_Jd><}4-7YM!d1cYWH22aygmpMP5#0=qcF}aT(dWF z=X{WgCxI_fsXN-UVjm{)hcPTO6u(TxEMsxXTx>EJTTI3SqcOH@Tq_NC2)}al@P{wb zJ`w--o_Z~^T}@1298XHgTw=0_oGczFZ$`?Rp>kWS%o8l{M8{WZD~OQ%<BbsEXd_OT zks<cSGdGMG`dLj_BUd3ei&eL&jq_{0+JFX}#)Wy_GoJMfM%45<&<Pkcp8;LGHs4v$ z46uz^=WAyjFxt^&6{7JX&A&o78my3(fSj${fKB66(P_-VNi~hKQ-?a$Hd?h%QT=LH z2LWZIHss)LFzG-ez|fUeo`h+Q={Mt9)V=OB>h65XS__-3{!DDPt-Gr1IbSW<(mrFI zJ$>g|U#B*)(>8W4jcpffyV1zr0hb>&EB%HUAwiz&iR!9mhow2*3S))<DyP-Q4d~NA zxVKPuxbJdT)89SKqMt!{@QfzBi3>-x2N&K0lo34Ntkib^`2B;4BNd4Rx;U~Wj_-}H zG~*%n!N_^}0F9R#2gylp%8gsOmM<vgFn58>k9$s)8?uK?ykdX)jLCpcj=~`S_p>=J z^h)U5izzR;t)0E>N~6}&O*eYfeX#UuGd=4zcRDGHs`Ia(FziR2*?p<iV|A~)Xk!-g zySs%?dZ%XIa<{kBO}cT@aoyA#Kjq#L&S{1R-r<1$=U~GjF4C$OoTS&iN#3pYcbhie z>abNj#hp5N=~*77$jSWH?@ms!+q~>4C%x!PPx-D!et$gOU*~bXYr;ETnx9X7Id_Lu z+cS0ZC|~{4e}DCecU||z-o5YLIQ+%mK<5p3+LV%C)aFZR>`s?{%gtVv4gZYCZVl1e z+0F_7zMaT&_hb3%J%n0=)&kR1r}}}&ei#Eg+^EDGvF%T3{DV>d;<iBk{SQX+Re#`i zY`rukyJUauXKZqoJqIX6@t0KBR)DDFfaWAvnAU)|q%s7^faK&SwLxpc^nr~Qf*@!- z#Z+FE_kI>gfw#08yq16~b8-R*S{xWV`?LN(6$pR5Lrc>}e>CV`D|ma%27KfMgyLp{ z;)H_>c!OG3O+u(`4A2116operN>%7gNB98e#$oB^ecvZ$z2bIB19#_Vh71E{K`?bJ zp=B=tWou{=Fb0Pv(S~dYhILqc<a3A6Foz?7hje&$=#zF|2!=g@0dRpw=pkV1a2<{4 zh>r+~ktm6iNQsqbiI<3pnW%}I$cdfkiJu6Hp(u)@NQ$Lsil>N*si=yp$cnA#imwQZ zlo*H|rgnv>10m*aWJrEzc!p`%V0-v|f9Qw6xOTz_62{mO$jA}Os1aPaeQ4KxzF3I; zbueXke!wwZ=eH$;<z0dkIDo@A;QmNB-6)QSGi&6vIh}=G*8^VSlUXwtU*?96tfY>f z<Br|fIXLuQ$mU$KV>{9nJpg$VurrYND3AO2J?w~U*~2^1)q3n@Hhoul$YndbCr#>D zU+q|t)kKl}$d3I8k{>COeHdC689p1yJ;;ZUJMcNy_d_k|e1b(fF3FMH*pUS}UFGPF z;rNa_IgU0ni(V*=V2F$2r;9egiwZ+`XT}U%)kGKsl^PV49JGcoWl=kzMSZALSb0L~ zWR)k!K@z!@N`zBt1w?mNMB;{)Yl(7Z2~t18l~0sW)1yv2bxLj-mXGEGPMLUkNrX`c zb=BmTTlG~d_cdebRWS5*{)DEOKgF0xw3t;@n3={*Q6-g^NtKuhl-ajq(D-FT6K2$y zi12oXQ-T2HM>Q5WN}?1^z!OWW8A`Evj_A|_&a^wzG*B#Po3a#v;3QMKG)TIsflTL{ zN_b2rNKD9SOv;H&=Tw~6bVXNnRIn*cF=Lm>q?)j_OWWj1EGT@mS%l^knBIAi&)J9O zsfXZ+P17@O1wfaF)SmB&Nb&hi;q*!;MV+q6ny=ZKoiv>07L@r#h_;9WMOlrzSbhm| zn%X!7PWe$8<xvqzmU%gwHT8NsaCs>eQ@H1O6!(>Zhk2J^p}E(VlPPROHB_XBqCr(~ zD_Wr@YJ|*lp>p~DlrCjmOy`#~%6FB(mj^kR<2i&<XKjPImoQ46lDVOYd6g%Kn10fv zzlNAK)Lk`7p&K=!Ryu0|I*3F0eWF=raTjl+g_@2KUYrGw_}G)1g;{7SUfDH|&6Qi# zrI#xyTD0X|ANQu*Ra+X#Tk$oe`goI`bzT&CTs8Mzh3ayIS~*&%UGW%J(A7H%LS3$P zqtE4Dfm)~QWj3uvAbr|3@YSXRNT+a0s@vtL>sYGTC90U3s(Hw&rb=h8I*(Iwao6=Z zv?@S!imNTfsChb%4yluBs*YwVT#Txv(FmG9W}u@9V2hXz#JFzEm=V$%htL{k)LIeP zDiPWm5#0XTPMq0g-1nIT8ipZ|i}0f~YsaM(X01QwttwfJ?RsMJnq+r)tm8_qhA4*T znny|5i^;lT^txZ~dYHi|unTsuzZi`7Dm3}3ujXo$=xUASw;HKo8)KS;nYL;H*bLFe zXT;`}84GH}242+mv6Qxf5{PlPr)rHBYcBgtuoknj*0NBlvgOuux^uF+#%t=d9EoPK zx0h>k$^#xdn@0PWa|WbDOLa)=syX1aT6vI0>uubYvF+Jyn1;1LOMe$ge=q28f)=)g zHnzMbwum>iL2I->JF_smwZlefXA7b{!?1JYumvht5!<gxd9igj1d+!~Nf!iwi*z1$ zkp6@VmOOyCb31UL=S??9b1nzDHfN}lJDbNxM0At0Y6)}}x3X+|xsDs8for%*C%S%j zx<U$=;TgKb*N2#kQ+r2tMmM_!SGyS+fwJp#Kc{CgBYT2pc!P(wxhuL{2fCJfdbIa& zjEgyS>tlE8r9+04e2W@>3w@kNqPUA*yk|?9=b_gNOP)7Ak*A~j2)n3fyQZhUv`c!Z z>$?2IH?DV6U<+|tmAKz|z9DL(g9Lf0t9q%sw5qGL)|S5s$u$Msd&OIQ&PRPl=aLOv za1UHOx@&awE4&vRycyiO$_Ku;$#SG8!5peV!~1~Xif#hBuIsj}UuuRGdm7bC{%>rC zfun<f5SWKhcuO%DfjGQN?l;531)TaQgyJ-U*KvZ#S;Qqsf=9f>p<07CtAxP`#QYO` zex}14+N)4}O*xpuHtfScn1XJq#Y?!lWo(3DyoDJ{#-p1!Kg`BV2ZdTIg;XfVR>(J4 zNJ?28$6s5;`v-viM*;sA$bLM?s`|uxJb_D^#QkT-Q%ppE?8NYA!d-~M083!eJATva zuNZ3)QVXxm;K|LHun)!(p<F!i`V6LAtp>}=#pt}X=&&m+z0Rr*v1rS;jLW&K%e&0W zz3j`s49vkS%)?B~kod|2ny;9AlxDWc*4xU_I?53i%@a1w?E1{tddw;Q9GZE1pqkvm zOUaaEdXKzgk0{BeDT}EliIV|Ikp9TdtYeT7n9k#qI}Ev|z<R2~bIRP<kIOTtd<v-i zJd&EH&f8nir!>zgd6FKf%1B73P&<<`Igd0s(GoqI2~DT?OpZONlO4U!2KiSJxy~77 z(Zm|W2Z_<FD$)imY4)s+8oin3^NfOstaqCO$}F)6T7Jm^&PLXhMp~tWnWR{nvUr)6 zTga9|l+-v1)MF(>a5<q<8bw=m(h*3c9mTYg*3{?pqpX{>NV=qNtf66@)m#nKH58dj zdPQlC){Y6*U@4`RnVEC#K~s5`XMNUubJS@~)F4!qQJUA`^wj=Ll$CNF({SWv4Qtak zAj_G|%yM@>JzZp}$;P{hOTt;zvx%O_bDT}QoSxm;My%P{sl3uDRQbu9)Ks6ND@B&g zooaZby-C^wY{s!oM83pM|1;Y-<(scWpWyl1zC9g(t)AsHJ^0z5sIA<|-Jf06+umfC zpDo(IjeGl<*|%NXxDCJL6rChBoy{F_*=))Ch0}Xmeq!p(BPzaQow((zk9g^#@vWls zO?+0p)i@}lPbJk-bwj!OqC;I)?p@!MN!Df!-~o)_N7tA4omLF4k_A;zPO8=*D&Y}s zvk*R+`+cP#wc!x@)*#A8{_UcIC4(f2)&CvVAwGHdeg0lLs^M8W$umuh$V{${z06>G z-VExisM=k&MLMMVrzj_@s4A0Zs;I$LT#0I^Sv=$!DX5zosX6(MKjhz$?9fdPkx(wD zu1a5M+^9sJ&sknQ>NVzGzT<&4Uq243=p0^{x~oMVJ#Rj%`^KwgKB{nzlao5EYf7w( zYUe|q&Q*To$W^O)%BD=-=EqazPM)c`rP0P3<L`v8i_IDBrnl%3V6<!w)qG)`o?)Jz zVxS(d*X*w1P3bOR<2kL(FSF{Sj>@kN&7?lBvR<#WKCq^K>g63j&CJQLeyyV3V!-}l z!X9J9K4Z8}>8WnsawpkpCc|AzPBr_otR}P4{@%7W8?}p*JwuyQeLSb2drsLtvXmyZ zP77^8`_)03;BbuYPwTab?1Wqk?^pY6TKu+EOK$SMvbAQxhvsK;>+gidYssdzLCdz* z{<ho>@JCzl(4IFgle627YCQY9-$}KYX6+f@YA~zuGJDwCC+Uiv?3Uimk4<kK2Q{30 zzQ|j-Eibu->b@L2ggm!6oZGveduQODa{yeM0Vufr=D+yM^R=z+3VyglZwUxYzzWR1 zwCi=I_w)mf-4tAKyer{_x4asR^)XLz#=A1e+rA#0^&8yvj;r$kMDv?t^H;y~wik9T zf2J(|@`*}2$iCRhzUxWZPw{4V@B6|2O`pDi4|ZYyzkZKv@yk-MH}JD3_7jZsGUdIG zcfdiPcm=%fwB5Ldk4ycl`E*A0(Z|3Y9Ky^O`ee^hI@j-Gzx4`-!KSYRSvPX07sC3h z`fV@sun(fFhxm}M_!VFIOdt5cKlp+_c7z|^k)GIgd-o*ItU4WUE<6A*%=hW%#zZWF z*N?>5@7Z=f#Ko3@yOjD@yrg<u{nw;}fQ&jl*aJdH{$MPgUM;|etb*mg#zjm2>50g4 z#vF}o?{j>GbqoPXu=hf>_|{U<V3+}^w&x4SYHXU1qnKiVADMoNc&_hK19Chd5h*iF z86OE82BDL6a*T$Aa++Xzykq{?Bt;c}NO6U!4Wh@`D~9F5ndBHSQPmCKpP>R6+V}YL z;~R`xn}cCEs0c|2A%KKjWJCY}a6HIV+vM!@`~(g4oD2!E3CWCvY(!m5c!iCXou#d{ zy~Ul?6y@#p{gnC@9_9@u7BxBcG*)g_X7){XK8BvQF22t8t=8^V<Ms~E5hq83B4u!7 zP$ihOsk~I&EvNrPKP^pFWtGqL{|6XQ;6Q=}4HoQ^P~k#`4IMs&7*XOxiWMzh#F$az zMvfglegqj(<VcbwO`b%VQsqjP5K%Eo_ehmH2JkS=tCv#^I#c-$u;ZsG*HEHGjlROE zQyVy?&6qZ&2DRH$ss6&KR^5j6+EuNhxON5g73{K?r_`~dX|vzha`#HL0vc1^lA}jI z1Q1Z5F41`i1PD-~CBR@#157Z%Tc81>0e?Z17)%&)WC32C{0*!@G6uwX7h8xd?UP@? zRxg)U6B&(a!J8+qb`4rIu+y1e`yy>ywzFWlIr*;8pa5nqz#j-FUK;~))S-GOS7Tg+ z<13wYm@%BUdiLu}xVNG00XiDvq48pvj+(g=>z}QIU#5Ih`OuYjZ!*l8`{(qJoy(7I zy!4b95WU8lE0D4A;uEhq@&r84i1sFnZJKjnI%_8M)bfWda(IdXu6^VZYA>P&(96W4 zb{QZ6>^Kpv{(%W*ld*))8rWbqE&K~>1sA=8v6C9R=%vLMvwFZtAUBz<NhY664M`=> zfRdLfn^=uEACKJeM=G<h0f#Fq08@l5mn;)Y2FFB$%>ZtUF-|pboKwed&ZM$SF5S4I z0Nc`>(>^We{4Yr_)iiX^6~K#e#!ZHNGdMfV!4pw7{rtu@OwG|$xA551bIDCPfs~vJ z&5|jSv@E&F9S^&4>n(jE3Ca}}a|xhDCUm{EmR*w3Q5q^mvw{%;1_M(+W_aw8$Y41s zRtqM$0`?avs;yG6Y}3#-%3iMR)>~e%6*nqp%iW+4K630<T^ZO7!rcNU)dE~@*EoWK zWAS4Cl;75p1r1<i;p@a+eDRfr#{dN3;16L1_P0=SxlI^8VdGFET73t$?A$DaSeTF! z7`A}qkPRtWWQoJ2cx0711K2yUyef+yv^HdQtyjGP5uaICA<@=eZ4n@rqKBr1#Y2N^ zEi{;av{6zu^bNIRP(PNoXilgO<!Y;!-u7UziM)60HNh^oYqYU8*Xu-=EKUlypQtoC zuH;_3ZVTWHv~M(q98}Ox^L{(ew-+_m?x#r={A@)F$5Kg_Wn*m#tu^4gP`BCikL%9u z`y6o_K)-_PW;X{rbTclWx^doWmg{Cpa_(?vSYg6PL|TKU3+Y;h6M$FWW9bVzIh_6$ z*Stvk+YYb5BtImL=bAgda^)B6ObL3ipgs*m#%WM{&8$bB^6ED;Z+!H<rvt$Azwe|x z^|ud?d+`DED_5xPzfAw)pv#}+zV`+9{SQG0^3R04lNk5x2RjUm-WsNNzwlM<G>Q?A z|0pyV>^aa2(kmc<4!6N7T`x@3iC`Bd$Svb=4}%SQp$k7F!wY_9fbFXwzFJ6{7n(48 zWy2c$=m5J6sS0QDsGaSy(KDaKMID()z(J%qMJiVDA4;?q7LlaIj&ZS2UThW^>nFy7 zk#US>yb~HZaVnc>Xm&_UiW+$lt0s=<iLR1k1>%^;nPIVyO8lek0=ct6zW%C^9y%mj z5;>Mdf`yH-bK^T`x5qr%113Gu9nkLByWw?`FL<nEBfBC>t&kEcrmPAorJ_oxu(Bzx zR0=G0^2j1e;)t!Pq%Y{0NdnZ0lb)2t6M%`7N#wy(2}?i&d_Xo)7&D6}u){IK#!PBD z>04yDL)t*$%xON8o5hJ{!}wqXZpw?C;mpAwJe9_8Isu(;c!8l>5YHCPQ%uQ(gOBXl zv36q9HVKe|zLF^eamv65^;~8>^I52VLNf^&GsbKX<A#B90iK9jqd%jAPJt>jphfej zu_`LkkdAJo3`Hk6A8OK)B9sgCGgm{oIh%{F00H}yXF~BA&#(|N{#CYoBQA3}$4iDt z9!_k<FNc|c4=go`lMn!ZTxYf=;U!n=R6|q~ha|0zj%jQIqBX(Vtgf1Fte+_BRH^yQ zv}RSRWGbC~+WLSp$@Q&tjcdo|s)f1wX`akj=yG@(oWUNauw|s{UHPh5GXw)Le|ceD zGp44%hG(xBxa<;`AsS3c6bF8F!QIHj*wJqFv7R9l4?lC-$f`EAke#7rNt;;Hx)u$? zI#xEfQd+N~VXjuqCTQuFgPVqSvVS}58hyHsNY-SMpd4x^c6n4gCe^91XsT0pky#*0 zV1MXQ>ub16*4d6ttdlb@SIfIr*j!+|?7b#!d%M`JuE(za^#z_^c*`~W>UFkrt?8f& zTUo<WHo2RS0(UQKILRI-zk>7boDQ5u1vA!g2lLOvpcY{AZk4{hZE%S5idWN)xWn_s z>|GnYy6<|JNfnZ<i}&j~&lQ)RHpWtfk&96TFIRSwgd=pLy9p-p(wEm|>d~}YOms>^ z8h(M#bwpsW-!z$h64WT!&KuTgI{C`rn<-f76=w9B#-U{}K{=@qEj3pGOl^+V4TW&# zJEZxYINri%cc5n*a6!s1{c;^vYUh>0Ef6Zxft#-YWpnH~g#@It5eQudLj!RQjNTER zKZxZ>UmBfzKpCh3aRQQwx)7t5GnlQMWmAhM$}#>x5mg_J;4Dx24pNSFk~AG)Ob@Zo z7)v27f7*^)>TJlCJVoxPySq&y+1XF62TX|}%bXNa+U1~jJ+e)YZDae`-8L0gu6@Q( z`nb6qqU5o)P~>H6vK3Z@cf93&fR@zT-uK3LzV*Fte)rqo{|0!#1wL?wnA?a!HMY7r zG2Nf_l6TKWa=Ot7ZgEgN92WNm#;5Xajt^PmOZm3JfeK`yDtzHZ_1RCVJD2Nzz%d9| zYVjO2zV_C<X*Q>MeEF9e7>{N_w|RyL4XA|m41GEIHF|K6-o+Ls{e;15o^~E6^#xM> zJxu?34$UX11QmViFTDB}o9@2)0KH9z@%|w5us(AUCg|w|ijaYz<M#TEUF&cEx(&;> z+q*kGeF}m);1lS<u!qs^J2&6_6i-6NJ4fWiMtBlUz7yQNrQHs1wo?NTi_H&HX^%^< zHmli9#JWw^_#xcszxFsb0Sa<(WsKfje>%dsDRYC8)8BGWd)C*!Pk$;Mq5!uXnBuMX ztY=i97(gTYMCv%j*ITHlG%7lDDpS{&QtiP;eZP(SnW#HblQQRgLIti)tFQFyM=$y< z(@YT<8uqF#@c_wBe%&aqBeSjR)ciRw=98G1GHbu}J1YhWKuha{15~}0DI08&7kC+i z2Sk{HnwO3_Ky2BVh=B}<;lO^0{u#u&z{yb<l99ly@j!yfD34*8iUF%($(Xw07zPv@ zY&jWJGnpGynHm%=6>OQ5K^d5Vk7)rp9-KgF$+ZlGK@D7;6SP3|3P2$=LS}$MWx1_f z>#6rc73MlPlA}L~Q#giGIsIcf&EvfPi@viOz%<N0(1S13V?*zwHT9D|L>aERF~0O0 zF5XK&+mo8Vah>4{jz0`9N|8f~+7sweow!+?M;XL=Im0uAo`X@IMU0x~L%$1CKh@F1 z5!*x=D?RNyl0?+8))_+ZOFyn5zZ|)}QS`%0v=qWID^H9?6!S#o!ooJ%Lg$*l`m;Yh z!apN>IQ}z?t1?5ad!NPrOT5PWyJXxn6$&9{+#dO9ykS@&9Ri@-+8^TxplrNG4QfV6 zL%d{=4g`uiup_*4grIN4p>TA%5rU!5*v9#Z6a`APY9x{tio|6M7ZXxDb(}MH6d~K9 zp?9pIg7io50m!?vEg#Ab3i>J``bTNhx*rm};6owvnMV(*$7e)5imW>JXh{8V9+B*x zV0%3IE4g0805C)<d3dUMgUOf-h>nY{kIM#|<hz=r$)UnYYv@S}3AtN@q+CQdA)7pQ zYdBwuE-xgbpVShM+sRyVN~eg*P^wBkI>}<=#a?8|l`9d5D@L7!%CQv7vgFCDl%uH( zI*-fB$YaW5L(2Xzbhj~FssYdy6e-4rT0oVev;ssDnL;OX$}++1v~(ILeo85KTB!h2 zD0<qdd%~&4<S}+i%q)|Zk2)#BbScOrFvJ4PLgS2x+JuSHr-ah9X8FuA=qQ3>Db`#~ z%q*$bL`=}kGTF>2K&v>*R7^C(k6qifisH-(BgE9Kvo14EZ>r3~q$%JeDb|$CG9wG2 ztR=`Jxr9?m`l~!ykxR_mzok+#@A5pTTCZ_?FF2&I|5C9Lo2w8@&svPb3fn5-0;~a} zPXn7wLWECR1Srh1trgRc9rG_w%&h%Hug)6H&$6){%ulXit<~z!2ZgQILajnsP`MJw z|FnSI!v3mhn=!ZAD&SHA`Q(5EJuU&ePe+umS^O;x)c_VP0T<2Ct7=gh4NzK4(dL6u zBb3k>A}lN!H(}Glk<-PLyw136$*-(S$okGs+OF{o1=T3er>sz=!;Jgtt^T@EDt)XP zRnfcRupR|a6AjZ7>rxkEuo1&j7L%|O#i1$1P!YTW4ReFjj4>NsPxZvp64O%~O+h#H zPgVpkPpBL&bsg@ivB;!TKrJvr^-vp~#Yd&FNM%&tnk};$MgLq>1hhqTm`;?mPL=FR zF+@^e8UcU;jJ#a6dl|D--OKPGv%*X?HVe!(d$U<pv{{?8SLLPv`!haUre4i6TvfF} z{@cxLy0BK8f?H*SM*B5k6*S(oOiX02G>e%4+eqasR&pw}ZB-d$Mb>N8)lVz6n87tF zD<L3y*5VvYSd$yy#Hn5j)`Lt}Nz2wA$hC5{S1G{PDF_pT`JQ?8w3XUdcqKJNb2L>A zw6?6yt+YQQ^-7@uOJI7-1XLunR3o)C7qgT~aq~%wow%Un)cDiNiCxO<%)hym$&^*u zOdz<Hb=jAN*_f5tnWfp9wb`5X2!$QePvy#Ayu4aj*`SrTeh}KCHQJ*^+N4$5rDfWt zb=s$e+NhP<sioSgwc3@1zaXW~on_dgG+AI&hwBvEu_fEGHQTdA+qBgpTols&qAXj7 z6+_FD+qtFNy0zQ8#oN5q+r8!6zV+L`1>C?D+`%Q>!ZqB(Mcl+y+{I<w#&z7sh1|%M z+{vX}!tK<NHA<5URiLTd&h^~S1>MjU-O(l8(ly=FMcvd@-PL8?x&1M&CE1~ZTXvw` z+O^%=#ogT1-QDHg-u2zz1>WEl-r*(Q;x*pmMc(99-sNT9=5^lZh2H3u-sz>@>b2hL z_0+D_+^@a7>-FC61>f)$-|;2i@-^S{Mc?#Q-}PnR_T}B|jon-V5$)7k_qE^q#ozqZ z-~Hv^{`KGg1>gYQT`XMK13<y+q+0`4+XZIe26o^FhTsU6;Od;<3by{>3&!9K*5D21 z;A7a?)&=1Z7U2;l;Sx6C6Gq__R^b(9;nh_OS;>bOMg<vG1R6Hr8OGrn&fy!@;U4DU zAGYBj-eDp3VIl_NAx7dNPU0h0;wEO|CpO|JUScVBVk(B>DaPU|&f+W9;x6XmFSg<^ z-eNKKVloEfF-GGuPUACH<2GjFH#XxqUSm0SV>*W8ImY8U&f`1Q<38r%Qg~s@<KscL z<3iqJL-u1tCgekA<V0@dMRw#!hU7_B<Vt>IOO|9zrsPZJ<V^15P4?ta2IW!K<WkPz z4{qU9R^?S@<yLm(SBB+SmgQMSTn-W7TgK&F*5zI1<zDvXU;YN>V4mJZ;93GufMZ7H zWLD;7X69yg=4XcHXqM(_rsit4=4;00Y}V#&=H_np=5Ge)a2DrrCg*ZC=W|BqbXMne zX6JTx=XZwZW+rA^R)DLu=X=KIeAefE=I4I)=YIz1fEMV1Cg_1?fMcF#+A-z>Xn}@y z=!b^rh?eMyrs#^c=!?eajMnIl=ID<0=#K{JkQV8YCg}l~250$_HtCZ_>6BLKm1gOd zcIlUf>6n)3nWpKQZfFWX=!IQq9Es>R;pv~|XrG2?eW8LXKp=c#4#j9_;8E&{evcL) zYMYkosix|xw(6_K>a5o4t>)^k_Uf+&>y%!EoG#{i#*uAo0sfy>YaS}<nt-gL4uJlP z42V`J9BFHxRwyQDfxI37jMkTi*6Xkq?7=4N!Zz%~M(o5^?8Rp6#2)K<)@id2fV)oX z%El3Vl7JQfDP@W(yKZQqe(1@*>3x}iw5Dv&RwmGP>dj8=#&+%3hV9sv?b)X7+P3Z6 zmgvV$Xe8Qc&{k{9-n<sbY#Bl7&vt0lZs@ue?bdDpzTRuh?rVzni{dtF+_vuP#_sIa z?(OF8?rv<|j%<eZZQxF8wub8b*=*3xpP?>oqjn6WW@?9yZ_5sC^PqyL_U`=F@BQZQ z{`T+x=5FvVYla5!0yppjNALt!@C9e^iWYEsCh!KA{_qK>@Cvu^3&-%OesG00>#&vp z<nCw?x9ATC>e~JQihhF(SMe2R@fLS+j^1$I?r@Ek0M>SC5SIW1-~)%gi=L)v5f^F^ z|M9IB@h3=bb(n#rE-sK(?d(eNjBfI~T=EyE@+!CTE2rrg$Aq2MXrjjNT!Cv4cy2-v zj^YaJrH&^0cIdo#Yb7u7&rb8726E#TjyP`-GB0tpzVj2W^AtdCFfW1@n1Lh!?mdrg z5pVB@KJ&j0Y1M9TMhEN(5CQ5|a~Wyt&c^ag*Yr*2bQpgCvWD=Dz9=;ZZ6??kyuPVK zH*Ki?ZHRvK&Q_=(=b+_YKJxZ);680rpX)sSC-qfl?zyIQwLa}uw{=%9>PRncNdIU@ zzjTPMaZ0cBSMT&`r}k>+bS+l|F2`sick}UX=%Psn!$|JBM)MR<Y7jRyhz4;$e*>Sk zaXSC$L7y_sb_B$rcRKfWc^CK4j&prK_g*LNh~{<|c=s}|bd8SkS9o$ooAg_6XeXa? zYnS+mr+5m-_Me#XnI8B9AMWFJX+D?e6c_S}C;5^$`IDdMi`Vv;9*l_pZ}Sdql$ZIL zr}>(vYL(A;o7efB=lP!Z`H;r>2*>DL@pz9Od5|u6iT3teKl-9~>G}TosF(VwulS(f z=*??yVwZE$NV4>XdAHV%@;;V_mVy2@4-G{pM;rn6U61RrU+XsK^|Mzdn9m-Mc5bT2 z`@GltDz|!s=WHs_dSOrP`&Mhr?(EHG^yPl&%^vQfPkg6GZ8<l5fCp{FKYSfmdgb}^ z)86~e_x#Ui@V<v~sGbpDuXUgf^0c1(fRFizX8g_ualj~rz=!<87jD=e_BDt0+gEqZ zr)%g2{ooh=;n#1`A9{!0_v|6|)IV#ucWb=BClN3Euz&O0cOIW!Z-S@&=BIw;pQo{( z`(xQ`wDxGIC;szC|MZV-<5&OocmMZ?f2(Hy_^1E+xBvSG`cH@K0muLT_y7NgfF>uc z_TsEJ@BV|KI8w)>C?<mdK>orUgu*<vedD>l^S%EAgTf)PXgng5$|bYud_tqrDYa_7 zVzb&Ux9h#3R4f-vhB~9u>NUIVe#7JPIlXSb<MaAGzwiG81_}-m78)KRCMqs6Hab4C ziM6~OFu+)1W@>J7c6xq-W)hr0CK80ArmC*8wz|H;#>&pp*4p0U=IZY9_WJ$;2MZ4q zXFjY<UMd&Uj+UA-M@vsrS6g3WXKQbBcY9kOk5O7yQYQ!mv^qluAfA51$IH*t*W2IY z=j;E5CzqFdN;gYjCxWcjtqCvy&46}o9L|B5u+O`F7B6DVsBt65jvll1Bl8cGK?Vf| zSQ=2k(#e!183<JP{sI9?n3!5JPzeg5nsx$EEOdBap#qgCBnm))qCkN#L>r<=y0obR zq)HVqom256*REc_f(<Kn?68pImKD@wFlMHgY%#sH33n%{i3kJ?Q0g!M-@ZrF3_!>C z?-_`J{Z<7mSmK+lWFJG0EO|2J%G}H@1IVc)PMs!eHry0fF2T+#KgFn<hH>H)M2kj6 zBekf|rw(UBRq*o+Zq{uaTMjOKIPv1fCu7!(HrBad<#;{|1pGMl>ejDg&mJu~C*`Z| z3OEf+GxX>bwWCk3em(p4G?0mOFMmG$`u6W*$NXhK|Nj2}0~nxy0}@!Efd?X(pn?km z0UrVnLKvYngcDL&p@kP>n4yLna@e7VAA%U7h$E6%qKPM>n4*d+ve=@FFTxn3j5E?$ Oqm4J>*dYKB0029iY``i2 literal 0 HcmV?d00001 diff --git a/modules/hdf/include/opencv2/hdf.hpp b/modules/hdf/include/opencv2/hdf.hpp new file mode 100644 index 00000000..4ca6d11b --- /dev/null +++ b/modules/hdf/include/opencv2/hdf.hpp @@ -0,0 +1,54 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2015 + * Balint Cristian <cristian dot balint at gmail dot com> + * + * 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. + * * Neither the name of the copyright holders 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. + *********************************************************************/ + +#ifndef __OPENCV_HDF_HPP__ +#define __OPENCV_HDF_HPP__ + +#include "opencv2/hdf/hdf5.hpp" + +/** @defgroup hdf Hierarchical Data Format I/O routines + +This module provides storage routines for Hierarchical Data Format objects. + + @{ + @defgroup hdf5 Hierarchical Data Format version 5 + +Hierarchical Data Format version 5 +-------------------------------------------------------- + + + @} +*/ + +#endif diff --git a/modules/hdf/include/opencv2/hdf/hdf5.hpp b/modules/hdf/include/opencv2/hdf/hdf5.hpp new file mode 100644 index 00000000..504cca10 --- /dev/null +++ b/modules/hdf/include/opencv2/hdf/hdf5.hpp @@ -0,0 +1,681 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2015 + * Balint Cristian <cristian dot balint at gmail dot com> + * + * 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. + * * Neither the name of the copyright holders 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. + *********************************************************************/ + +#ifndef __OPENCV_HDF5_HPP__ +#define __OPENCV_HDF5_HPP__ + +#include <vector> + +#include <hdf5.h> + + + +using namespace std; + +namespace cv +{ +namespace hdf +{ + +//! @addtogroup hdf5 +//! @{ + + +/** @brief Hierarchical Data Format version 5 interface. + +Notice that module is compiled only when hdf5 is correctly installed. + + */ +class CV_EXPORTS_W HDF5 +{ +public: + + CV_WRAP enum + { + H5_UNLIMITED = -1, H5_NONE = -1, H5_GETDIMS = 100, H5_GETMAXDIMS = 101, + }; + + virtual ~HDF5() {} + + /** @brief Close and release hdf5 object. + */ + CV_WRAP virtual void close( ) = 0; + + /** @brief Create a group. + @param grlabel specify the hdf5 group label. + + Create a hdf5 group. + + @note Groups are useful for better organise multiple datasets. It is possible to create subgroups within any group. + Existence of a particular group can be checked using hlexists(). In case of subgroups label would be e.g: 'Group1/SubGroup1' + where SubGroup1 is within the root group Group1. + + - In this example Group1 will have one subgrup labeled SubGroup1: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create Group1 if does not exists + if ( ! h5io->hlexists( "Group1" ) ) + h5io->grcreate( "Group1" ); + else + printf("Group1 already created, skipping\n" ); + // create SubGroup1 if does not exists + if ( ! h5io->hlexists( "Group1/SubGroup1" ) ) + h5io->grcreate( "Group1/SubGroup1" ); + else + printf("SubGroup1 already created, skipping\n" ); + // release + h5io->close(); + @endcode + + @note When a dataset is created with dscreate() or kpcreate() it can be created right within a group by specifying + full path within the label, in our example would be: 'Group1/SubGroup1/MyDataSet'. It is not thread safe. + */ + CV_WRAP virtual void grcreate( String grlabel ) = 0; + + /** @brief Check if label exists or not. + @param label specify the hdf5 dataset label. + + Returns **true** if dataset exists, and **false** if does not. + + @note Checks if dataset, group or other object type (hdf5 link) exists under the label name. It is thread safe. + */ + CV_WRAP virtual bool hlexists( String label ) const = 0; + + /* @overload */ + CV_WRAP virtual void dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel = HDF5::H5_NONE, + const vector<int>& dims_chunks = vector<int>() ) const = 0; + /** @brief Create and allocate storage for two dimensional single or multi channel dataset. + @param rows declare amount of rows + @param cols declare amount of cols + @param type type to be used + @param dslabel specify the hdf5 dataset label, any existing dataset with the same label will be overwritten. + @param compresslevel specify the compression level 0-9 to be used, by default H5_NONE means none at all. + @param dims_chunks each array member specify chunking sizes to be used for block i/o, + by default NULL means none at all. + + @note If the dataset already exists an exception will be thrown. + + - Existence of the dataset can be checked using hlexists(), see in this example: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create space for 100x50 CV_64FC2 matrix + if ( ! h5io->hlexists( "hilbert" ) ) + h5io->dscreate( 100, 50, CV_64FC2, "hilbert" ); + else + printf("DS already created, skipping\n" ); + // release + h5io->close(); + @endcode + + @note Activating compression requires internal chunking. Chunking can significantly improve access + speed booth at read or write time especially for windowed access logic that shifts offset inside dataset. + If no custom chunking is specified default one will be invoked by the size of **whole** dataset + as single big chunk of data. + + - See example of level 9 compression using internal default chunking: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create level 9 compressed space for CV_64FC2 matrix + if ( ! h5io->hlexists( "hilbert", 9 ) ) + h5io->dscreate( 100, 50, CV_64FC2, "hilbert", 9 ); + else + printf("DS already created, skipping\n" ); + // release + h5io->close(); + @endcode + + @note A value of H5_UNLIMITED for **rows** or **cols** or booth means **unlimited** data on the specified dimension, + thus is possible to expand anytime such dataset on row, col or booth directions. Presence of H5_UNLIMITED on any + dimension **require** to define custom chunking. No default chunking will be defined in unlimited scenario since + default size on that dimension will be zero, and will grow once dataset is written. Writing into dataset that have + H5_UNLIMITED on some of its dimension requires dsinsert() that allow growth on unlimited dimension instead of dswrite() + that allows to write only in predefined data space. + + - Example below shows no compression but unlimited dimension on cols using 100x100 internal chunking: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create level 9 compressed space for CV_64FC2 matrix + int chunks[2] = { 100, 100 }; + h5io->dscreate( 100, cv::hdf::HDF5::H5_UNLIMITED, CV_64FC2, "hilbert", cv::hdf::HDF5::H5_NONE, chunks ); + // release + h5io->close(); + @endcode + + @note It is **not** thread safe, it must be called only once at dataset creation otherwise exception will occur. + Multiple datasets inside single hdf5 file is allowed. + */ + CV_WRAP virtual void dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel = HDF5::H5_NONE, const int* dims_chunks = NULL ) const = 0; + + /* @overload */ + CV_WRAP virtual void dscreate( const vector<int>& sizes, const int type, String dslabel, + const int compresslevel = HDF5::H5_NONE, const vector<int>& dims_chunks = vector<int>() ) const = 0; + /** @brief Create and allocate storage for n-dimensional dataset, single or mutichannel type. + @param n_dims declare number of dimensions + @param sizes array containing sizes for each dimensions + @param type type to be used + @param dslabel specify the hdf5 dataset label, any existing dataset with the same label will be overwritten. + @param compresslevel specify the compression level 0-9 to be used, by default H5_NONE means none at all. + @param dims_chunks each array member specify chunking sizes to be used for block i/o, + by default NULL means none at all. + @note If the dataset already exists an exception will be thrown. Existence of the dataset can be checked + using hlexists(). + + - See example below that creates a 6 dimensional storage space: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create space for 6 dimensional CV_64FC2 matrix + if ( ! h5io->hlexists( "nddata" ) ) + int n_dims = 5; + int dsdims[n_dims] = { 100, 100, 20, 10, 5, 5 }; + h5io->dscreate( n_dims, sizes, CV_64FC2, "nddata" ); + else + printf("DS already created, skipping\n" ); + // release + h5io->close(); + @endcode + + @note Activating compression requires internal chunking. Chunking can significantly improve access + speed booth at read or write time especially for windowed access logic that shifts offset inside dataset. + If no custom chunking is specified default one will be invoked by the size of **whole** dataset + as single big chunk of data. + + - See example of level 0 compression (shallow) using chunking against first + dimension, thus storage will consists by 100 chunks of data: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create space for 6 dimensional CV_64FC2 matrix + if ( ! h5io->hlexists( "nddata" ) ) + int n_dims = 5; + int dsdims[n_dims] = { 100, 100, 20, 10, 5, 5 }; + int chunks[n_dims] = { 1, 100, 20, 10, 5, 5 }; + h5io->dscreate( n_dims, dsdims, CV_64FC2, "nddata", 0, chunks ); + else + printf("DS already created, skipping\n" ); + // release + h5io->close(); + @endcode + + @note A value of H5_UNLIMITED inside the **sizes** array means **unlimited** data on that dimension, thus is + possible to expand anytime such dataset on those unlimited directions. Presence of H5_UNLIMITED on any dimension + **require** to define custom chunking. No default chunking will be defined in unlimited scenario since default size + on that dimension will be zero, and will grow once dataset is written. Writing into dataset that have H5_UNLIMITED on + some of its dimension requires dsinsert() instead of dswrite() that allow growth on unlimited dimension instead of + dswrite() that allows to write only in predefined data space. + + - Example below shows a 3 dimensional dataset using no compression with all unlimited sizes and one unit chunking: + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + int n_dims = 3; + int chunks[n_dims] = { 1, 1, 1 }; + int dsdims[n_dims] = { cv::hdf::HDF5::H5_UNLIMITED, cv::hdf::HDF5::H5_UNLIMITED, cv::hdf::HDF5::H5_UNLIMITED }; + h5io->dscreate( n_dims, dsdims, CV_64FC2, "nddata", cv::hdf::HDF5::H5_NONE, chunks ); + // release + h5io->close(); + @endcode + */ + CV_WRAP virtual void dscreate( const int n_dims, const int* sizes, const int type, + String dslabel, const int compresslevel = HDF5::H5_NONE, const int* dims_chunks = NULL ) const = 0; + + /** @brief Fetch dataset sizes + @param dslabel specify the hdf5 dataset label to be measured. + @param dims_flag will fetch dataset dimensions on H5_GETDIMS, and dataset maximum dimensions on H5_GETMAXDIMS. + + Returns vector object containing sizes of dataset on each dimensions. + + @note Resulting vector size will match the amount of dataset dimensions. By default H5_GETDIMS will return + actual dataset dimensions. Using H5_GETMAXDIM flag will get maximum allowed dimension which normally match + actual dataset dimension but can hold H5_UNLIMITED value if dataset was prepared in **unlimited** mode on + some of its dimension. It can be useful to check existing dataset dimensions before overwrite it as whole or subset. + Trying to write with oversized source data into dataset target will thrown exception. + */ + CV_WRAP virtual vector<int> dsgetsize( String dslabel, int dims_flag = HDF5::H5_GETDIMS ) const = 0; + + /** @brief Fetch dataset type + @param dslabel specify the hdf5 dataset label to be checked. + + Returns the stored matrix type. This is an identifier compatible with the CvMat type system, + like e.g. CV_16SC5 (16-bit signed 5-channel array), and so on. + + @note Result can be parsed with CV_MAT_CN() to obtain amount of channels and CV_MAT_DEPTH() to obtain native cvdata type. + It is thread safe. + */ + CV_WRAP virtual int dsgettype( String dslabel ) const = 0; + + /* @overload */ + CV_WRAP virtual void dswrite( InputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const = 0; + /** @brief Write or overwrite a Mat object into specified dataset of hdf5 file. + @param Array specify Mat data array to be written. + @param dslabel specify the target hdf5 dataset label. + @param dims_offset each array member specify the offset location + over dataset's each dimensions from where InputArray will be (over)written into dataset. + @param dims_counts each array member specify the amount of data over dataset's + each dimensions from InputArray that will be written into dataset. + + Writes Mat object into targeted dataset. + + @note If dataset is not created and does not exist it will be created **automatically**. Only Mat is supported and + it must to be **continuous**. It is thread safe but it is recommended that writes to happen over separate non overlapping + regions. Multiple datasets can be written inside single hdf5 file. + + - Example below writes a 100x100 CV_64FC2 matrix into a dataset. No dataset precreation required. If routine + is called multiple times dataset will be just overwritten: + @code{.cpp} + // dual channel hilbert matrix + cv::Mat H(100, 100, CV_64FC2); + for(int i = 0; i < H.rows; i++) + for(int j = 0; j < H.cols; j++) + { + H.at<cv::Vec2d>(i,j)[0] = 1./(i+j+1); + H.at<cv::Vec2d>(i,j)[1] = -1./(i+j+1); + count++; + } + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // write / overwrite dataset + h5io->dswrite( H, "hilbert" ); + // release + h5io->close(); + @endcode + + - Example below writes a smaller 50x100 matrix into 100x100 compressed space optimised by two 50x100 chunks. + Matrix is written twice into first half (0->50) and second half (50->100) of data space using offset. + @code{.cpp} + // dual channel hilbert matrix + cv::Mat H(50, 100, CV_64FC2); + for(int i = 0; i < H.rows; i++) + for(int j = 0; j < H.cols; j++) + { + H.at<cv::Vec2d>(i,j)[0] = 1./(i+j+1); + H.at<cv::Vec2d>(i,j)[1] = -1./(i+j+1); + count++; + } + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // optimise dataset by two chunks + int chunks[2] = { 50, 100 }; + // create 100x100 CV_64FC2 compressed space + h5io->dscreate( 100, 100, CV_64FC2, "hilbert", 9, chunks ); + // write into first half + int offset1[2] = { 0, 0 }; + h5io->dswrite( H, "hilbert", offset1 ); + // write into second half + int offset2[2] = { 50, 0 }; + h5io->dswrite( H, "hilbert", offset2 ); + // release + h5io->close(); + @endcode + */ + CV_WRAP virtual void dswrite( InputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const = 0; + + /* @overload */ + CV_WRAP virtual void dsinsert( InputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const = 0; + /** @brief Insert or overwrite a Mat object into specified dataset and autoexpand dataset size if **unlimited** property allows. + @param Array specify Mat data array to be written. + @param dslabel specify the target hdf5 dataset label. + @param dims_offset each array member specify the offset location + over dataset's each dimensions from where InputArray will be (over)written into dataset. + @param dims_counts each array member specify the amount of data over dataset's + each dimensions from InputArray that will be written into dataset. + + Writes Mat object into targeted dataset and **autoexpand** dataset dimension if allowed. + + @note Unlike dswrite(), datasets are **not** created **automatically**. Only Mat is supported and it must to be **continuous**. + If dsinsert() happen over outer regions of dataset dimensions and on that dimension of dataset is in **unlimited** mode then + dataset is expanded, otherwise exception is thrown. To create datasets with **unlimited** property on specific or more + dimensions see dscreate() and the optional H5_UNLIMITED flag at creation time. It is not thread safe over same dataset + but multiple datasets can be merged inside single hdf5 file. + + - Example below creates **unlimited** rows x 100 cols and expand rows 5 times with dsinsert() using single 100x100 CV_64FC2 + over the dataset. Final size will have 5x100 rows and 100 cols, reflecting H matrix five times over row's span. Chunks size is + 100x100 just optimized against the H matrix size having compression disabled. If routine is called multiple times dataset will be + just overwritten: + @code{.cpp} + // dual channel hilbert matrix + cv::Mat H(50, 100, CV_64FC2); + for(int i = 0; i < H.rows; i++) + for(int j = 0; j < H.cols; j++) + { + H.at<cv::Vec2d>(i,j)[0] = 1./(i+j+1); + H.at<cv::Vec2d>(i,j)[1] = -1./(i+j+1); + count++; + } + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // optimise dataset by chunks + int chunks[2] = { 100, 100 }; + // create Unlimited x 100 CV_64FC2 space + h5io->dscreate( cv::hdf::HDF5::H5_UNLIMITED, 100, CV_64FC2, "hilbert", cv::hdf::HDF5::H5_NONE, chunks ); + // write into first half + int offset[2] = { 0, 0 }; + for ( int t = 0; t < 5; t++ ) + { + offset[0] += 100 * t; + h5io->dsinsert( H, "hilbert", offset ); + } + // release + h5io->close(); + @endcode + */ + CV_WRAP virtual void dsinsert( InputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const = 0; + + + /* @overload */ + CV_WRAP virtual void dsread( OutputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const = 0; + /** @brief Read specific dataset from hdf5 file into Mat object. + @param Array Mat container where data reads will be returned. + @param dslabel specify the source hdf5 dataset label. + @param dims_offset each array member specify the offset location over + each dimensions from where dataset starts to read into OutputArray. + @param dims_counts each array member specify the amount over dataset's each + dimensions of dataset to read into OutputArray. + + Reads out Mat object reflecting the stored dataset. + + @note If hdf5 file does not exist an exception will be thrown. Use hlexists() to check dataset presence. + It is thread safe. + + - Example below reads a dataset: + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // blank Mat container + cv::Mat H; + // read hibert dataset + h5io->read( H, "hilbert" ); + // release + h5io->close(); + @endcode + + - Example below perform read of 3x5 submatrix from second row and third element. + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // blank Mat container + cv::Mat H; + int offset[2] = { 1, 2 }; + int counts[2] = { 3, 5 }; + // read hibert dataset + h5io->read( H, "hilbert", offset, counts ); + // release + h5io->close(); + @endcode + */ + CV_WRAP virtual void dsread( OutputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const = 0; + + /** @brief Fetch keypoint dataset size + @param kplabel specify the hdf5 dataset label to be measured. + @param dims_flag will fetch dataset dimensions on H5_GETDIMS, and dataset maximum dimensions on H5_GETMAXDIMS. + + Returns size of keypoints dataset. + + @note Resulting size will match the amount of keypoints. By default H5_GETDIMS will return actual dataset dimension. + Using H5_GETMAXDIM flag will get maximum allowed dimension which normally match actual dataset dimension but can hold + H5_UNLIMITED value if dataset was prepared in **unlimited** mode. It can be useful to check existing dataset dimension + before overwrite it as whole or subset. Trying to write with oversized source data into dataset target will thrown + exception. + */ + CV_WRAP virtual int kpgetsize( String kplabel, int dims_flag = HDF5::H5_GETDIMS ) const = 0; + + /** @brief Create and allocate special storage for cv::KeyPoint dataset. + @param size declare fixed number of KeyPoints + @param kplabel specify the hdf5 dataset label, any existing dataset with the same label will be overwritten. + @param compresslevel specify the compression level 0-9 to be used, by default H5_NONE means none at all. + @param chunks each array member specify chunking sizes to be used for block i/o, + by default H5_NONE means none at all. + @note If the dataset already exists an exception will be thrown. Existence of the dataset can be checked + using hlexists(). + + - See example below that creates space for 100 keypoints in the dataset: + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + if ( ! h5io->hlexists( "keypoints" ) ) + h5io->kpcreate( 100, "keypoints" ); + else + printf("DS already created, skipping\n" ); + @endcode + + @note A value of H5_UNLIMITED for **size** means **unlimited** keypoints, thus is possible to expand anytime such + dataset by adding or inserting. Presence of H5_UNLIMITED **require** to define custom chunking. No default chunking + will be defined in unlimited scenario since default size on that dimension will be zero, and will grow once dataset + is written. Writing into dataset that have H5_UNLIMITED on some of its dimension requires kpinsert() that allow + growth on unlimited dimension instead of kpwrite() that allows to write only in predefined data space. + + - See example below that creates unlimited space for keypoints chunking size of 100 but no compression: + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + if ( ! h5io->hlexists( "keypoints" ) ) + h5io->kpcreate( cv::hdf::HDF5::H5_UNLIMITED, "keypoints", cv::hdf::HDF5::H5_NONE, 100 ); + else + printf("DS already created, skipping\n" ); + @endcode + */ + virtual void kpcreate( const int size, String kplabel, + const int compresslevel = H5_NONE, const int chunks = H5_NONE ) const = 0; + + /** @brief Write or overwrite list of KeyPoint into specified dataset of hdf5 file. + @param keypoints specify keypoints data list to be written. + @param kplabel specify the target hdf5 dataset label. + @param offset specify the offset location on dataset from where keypoints will be (over)written into dataset. + @param counts specify the amount of keypoints that will be written into dataset. + + Writes vector<KeyPoint> object into targeted dataset. + + @note If dataset is not created and does not exist it will be created **automatically**. It is thread safe but + it is recommended that writes to happen over separate non overlapping regions. Multiple datasets can be written + inside single hdf5 file. + + - Example below writes a 100 keypoints into a dataset. No dataset precreation required. If routine is called multiple + times dataset will be just overwritten: + @code{.cpp} + // generate 100 dummy keypoints + std::vector<cv::KeyPoint> keypoints; + for(int i = 0; i < 100; i++) + keypoints.push_back( cv::KeyPoint(i, -i, 1, -1, 0, 0, -1) ); + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // write / overwrite dataset + h5io->kpwrite( keypoints, "keypoints" ); + // release + h5io->close(); + @endcode + + - Example below uses smaller set of 50 keypoints and writes into compressed space of 100 keypoints optimised by 10 chunks. + Same keypoint set is written three times, first into first half (0->50) and at second half (50->75) then into remaining slots + (75->99) of data space using offset and count parameters to settle the window for write access.If routine is called multiple times + dataset will be just overwritten: + @code{.cpp} + // generate 50 dummy keypoints + std::vector<cv::KeyPoint> keypoints; + for(int i = 0; i < 50; i++) + keypoints.push_back( cv::KeyPoint(i, -i, 1, -1, 0, 0, -1) ); + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create maximum compressed space of size 100 with chunk size 10 + h5io->kpcreate( 100, "keypoints", 9, 10 ); + // write into first half + h5io->kpwrite( keypoints, "keypoints", 0 ); + // write first 25 keypoints into second half + h5io->kpwrite( keypoints, "keypoints", 50, 25 ); + // write first 25 keypoints into remained space of second half + h5io->kpwrite( keypoints, "keypoints", 75, 25 ); + // release + h5io->close(); + @endcode + */ + virtual void kpwrite( const vector<KeyPoint> keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const = 0; + + /** @brief Insert or overwrite list of KeyPoint into specified dataset and autoexpand dataset size if **unlimited** property allows. + @param keypoints specify keypoints data list to be written. + @param kplabel specify the target hdf5 dataset label. + @param offset specify the offset location on dataset from where keypoints will be (over)written into dataset. + @param counts specify the amount of keypoints that will be written into dataset. + + Writes vector<KeyPoint> object into targeted dataset and **autoexpand** dataset dimension if allowed. + + @note Unlike kpwrite(), datasets are **not** created **automatically**. If dsinsert() happen over outer region of dataset + and dataset has been created in **unlimited** mode then dataset is expanded, otherwise exception is thrown. To create datasets + with **unlimited** property see kpcreate() and the optional H5_UNLIMITED flag at creation time. It is not thread safe over same + dataset but multiple datasets can be merged inside single hdf5 file. + + - Example below creates **unlimited** space for keypoints storage, and inserts a list of 10 keypoints ten times into that space. + Final dataset will have 100 keypoints. Chunks size is 10 just optimized against list of keypoints. If routine is called multiple + times dataset will be just overwritten: + @code{.cpp} + // generate 10 dummy keypoints + std::vector<cv::KeyPoint> keypoints; + for(int i = 0; i < 10; i++) + keypoints.push_back( cv::KeyPoint(i, -i, 1, -1, 0, 0, -1) ); + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // create unlimited size space with chunk size of 10 + h5io->kpcreate( cv::hdf::HDF5::H5_UNLIMITED, "keypoints", -1, 10 ); + // insert 10 times same 10 keypoints + for(int i = 0; i < 10; i++) + h5io->kpinsert( keypoints, "keypoints", i * 10 ); + // release + h5io->close(); + @endcode + */ + virtual void kpinsert( const vector<KeyPoint> keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const = 0; + + /** @brief Read specific keypoint dataset from hdf5 file into vector<KeyPoint> object. + @param keypoints vector<KeyPoint> container where data reads will be returned. + @param kplabel specify the source hdf5 dataset label. + @param offset specify the offset location over dataset from where read starts. + @param counts specify the amount of keypoints from dataset to read. + + Reads out vector<KeyPoint> object reflecting the stored dataset. + + @note If hdf5 file does not exist an exception will be thrown. Use hlexists() to check dataset presence. + It is thread safe. + + - Example below reads a dataset containing keypoints starting with second entry: + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // blank KeyPoint container + std::vector<cv::KeyPoint> keypoints; + // read keypoints starting second one + h5io->kpread( keypoints, "keypoints", 1 ); + // release + h5io->close(); + @endcode + + - Example below perform read of 3 keypoints from second entry. + @code{.cpp} + // open hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // blank KeyPoint container + std::vector<cv::KeyPoint> keypoints; + // read three keypoints starting second one + h5io->kpread( keypoints, "keypoints", 1, 3 ); + // release + h5io->close(); + @endcode + */ + virtual void kpread( vector<KeyPoint>& keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const = 0; + +}; + + /** @brief Open or create hdf5 file + @param HDF5Filename specify the HDF5 filename. + + Returns pointer to the hdf5 object class + + @note If hdf5 file does not exist it will be created. Any operations except dscreate() functions on object + will be thread safe. Multiple datasets can be created inside single hdf5 file, and can be accessed + from same hdf5 object from multiple instances as long read or write operations are done over + non-overlapping regions of dataset. Single hdf5 file also can be opened by multiple instances, + reads and writes can be instantiated at the same time as long non-overlapping regions are involved. Object + is released using close(). + + - Example below open and then release the file. + @code{.cpp} + // open / autocreate hdf5 file + cv::Ptr<cv::hdf::HDF5> h5io = cv::hdf::open( "mytest.h5" ); + // ... + // release + h5io->close(); + @endcode + +  + + - Text dump (3x3 Hilbert matrix) of hdf5 dataset using **h5dump** tool: + @code{.txt} + $ h5dump test.h5 + HDF5 "test.h5" { + GROUP "/" { + DATASET "hilbert" { + DATATYPE H5T_ARRAY { [2] H5T_IEEE_F64LE } + DATASPACE SIMPLE { ( 3, 3 ) / ( 3, 3 ) } + DATA { + (0,0): [ 1, -1 ], [ 0.5, -0.5 ], [ 0.333333, -0.333333 ], + (1,0): [ 0.5, -0.5 ], [ 0.333333, -0.333333 ], [ 0.25, -0.25 ], + (2,0): [ 0.333333, -0.333333 ], [ 0.25, -0.25 ], [ 0.2, -0.2 ] + } + } + } + } + @endcode + */ + CV_EXPORTS_W Ptr<HDF5> open( String HDF5Filename ); + +//! @} + +} // end namespace hdf +} // end namespace cv +#endif // _OPENCV_HDF5_HPP_ diff --git a/modules/hdf/src/hdf5.cpp b/modules/hdf/src/hdf5.cpp new file mode 100644 index 00000000..6e0fb52d --- /dev/null +++ b/modules/hdf/src/hdf5.cpp @@ -0,0 +1,1051 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2015 + * Balint Cristian <cristian dot balint at gmail dot com> + * + * 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. + * * Neither the name of the copyright holders 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. + *********************************************************************/ + +#include "precomp.hpp" + + + +using namespace std; + +namespace cv +{ +namespace hdf +{ + +class HDF5Impl : public HDF5 +{ +public: + + HDF5Impl( String HDF5Filename ); + + virtual ~HDF5Impl() { close(); }; + + // close and release + virtual void close( ); + + /* + * h5 generic + */ + + // check if object / link exists + virtual bool hlexists( String label ) const; + + /* + * h5 group + */ + + // create a group + virtual void grcreate( String grlabel ); + + /* + * cv::Mat + */ + + // get sizes of dataset + virtual vector<int> dsgetsize( String dslabel, int dims_flag = H5_GETDIMS ) const; + + // get data type of dataset + virtual int dsgettype( String dslabel ) const; + + // overload dscreate() + virtual void dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel = H5_NONE, + const vector<int>& dims_chunks = vector<int>() ) const; + + // create two dimensional single or mutichannel dataset + virtual void dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel = H5_NONE, const int* dims_chunks = NULL ) const; + + // overload dscreate() + virtual void dscreate( const vector<int>& sizes, const int type, String dslabel, + const int compresslevel = H5_NONE, const vector<int>& dims_chunks = vector<int>() ) const; + + // create n-dimensional single or mutichannel dataset + virtual void dscreate( const int n_dims, const int* sizes, const int type, + String dslabel, const int compresslevel = H5_NONE, const int* dims_chunks = NULL ) const; + + // overload dswrite() + virtual void dswrite( InputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const; + + // write into dataset + virtual void dswrite( InputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const; + + // overload dsinsert() + virtual void dsinsert( InputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const; + + // append / merge into dataset + virtual void dsinsert( InputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const; + + // overload dsread() + virtual void dsread( OutputArray Array, String dslabel, + const vector<int>& dims_offset = vector<int>(), + const vector<int>& dims_counts = vector<int>() ) const; + + // read from dataset + virtual void dsread( OutputArray Array, String dslabel, + const int* dims_offset = NULL, const int* dims_counts = NULL ) const; + + /* + * std::vector<cv::KeyPoint> + */ + + // get size of keypoints dataset + virtual int kpgetsize( String kplabel, int dims_flag = H5_GETDIMS ) const; + + // create KeyPoint structure + virtual void kpcreate( const int size, String kplabel, + const int compresslevel = H5_NONE, const int chunks = H5_NONE ) const; + + // write KeyPoint structures + virtual void kpwrite( const vector<KeyPoint> keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const; + + // append / merge KeyPoint structures + virtual void kpinsert( const vector<KeyPoint> keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const; + + // read KeyPoint structure + virtual void kpread( vector<KeyPoint>& keypoints, String kplabel, + const int offset = H5_NONE, const int counts = H5_NONE ) const; + +private: + + // store filename + String m_hdf5_filename; + + // hdf5 file handler + hid_t m_h5_file_id; + + // translate cvType -> h5Type + inline hid_t GetH5type( int cvType ) const; + + // translate h5Type -> cvType + inline int GetCVtype( hid_t h5Type ) const; + +}; + +inline hid_t HDF5Impl::GetH5type( int cvType ) const +{ + hid_t h5Type = -1; + + switch ( CV_MAT_DEPTH( cvType ) ) + { + case CV_64F: + h5Type = H5T_NATIVE_DOUBLE; + break; + case CV_32F: + h5Type = H5T_NATIVE_FLOAT; + break; + case CV_8U: + h5Type = H5T_NATIVE_UCHAR; + break; + case CV_8S: + h5Type = H5T_NATIVE_CHAR; + break; + case CV_16U: + h5Type = H5T_NATIVE_USHORT; + break; + case CV_16S: + h5Type = H5T_NATIVE_SHORT; + break; + case CV_32S: + h5Type = H5T_NATIVE_INT; + break; + default: + CV_Error( Error::StsInternal, "Unknown cvType." ); + } + return h5Type; +} + +inline int HDF5Impl::GetCVtype( hid_t h5Type ) const +{ + int cvType = -1; + + if ( H5Tequal( h5Type, H5T_NATIVE_DOUBLE ) ) + cvType = CV_64F; + else if ( H5Tequal( h5Type, H5T_NATIVE_FLOAT ) ) + cvType = CV_32F; + else if ( H5Tequal( h5Type, H5T_NATIVE_UCHAR ) ) + cvType = CV_8U; + else if ( H5Tequal( h5Type, H5T_NATIVE_CHAR ) ) + cvType = CV_8S; + else if ( H5Tequal( h5Type, H5T_NATIVE_USHORT ) ) + cvType = CV_16U; + else if ( H5Tequal( h5Type, H5T_NATIVE_SHORT ) ) + cvType = CV_16S; + else if ( H5Tequal( h5Type, H5T_NATIVE_INT ) ) + cvType = CV_32S; + else + CV_Error( Error::StsInternal, "Unknown H5Type." ); + + return cvType; +} + +HDF5Impl::HDF5Impl( String _hdf5_filename ) + : m_hdf5_filename( _hdf5_filename ) +{ + // save old + // error handler + void *errdata; + H5E_auto2_t errfunc; + hid_t stackid = H5E_DEFAULT; + H5Eget_auto( stackid, &errfunc, &errdata ); + + // turn off error handling + H5Eset_auto( stackid, NULL, NULL ); + + // check HDF5 file presence (err supressed) + htri_t check = H5Fis_hdf5( m_hdf5_filename.c_str() ); + + // restore previous error handler + H5Eset_auto( stackid, errfunc, errdata ); + + if ( check == 1 ) + // open the HDF5 file + m_h5_file_id = H5Fopen( m_hdf5_filename.c_str(), + H5F_ACC_RDWR, H5P_DEFAULT ); + else + // create the HDF5 file + m_h5_file_id = H5Fcreate( m_hdf5_filename.c_str(), + H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT ); +} + +void HDF5Impl::close() +{ + if ( m_h5_file_id != -1 ) + H5Fclose( m_h5_file_id ); + // mark closed + m_h5_file_id = -1; + + H5close( ); +} + +/* + * h5 generic + */ + +bool HDF5Impl::hlexists( String label ) const +{ + bool exists = false; + + hid_t lid = H5Pcreate( H5P_LINK_ACCESS ); + if ( H5Lexists(m_h5_file_id, label.c_str(), lid) == 1 ) + exists = true; + + H5Pclose(lid); + return exists; +} + +/* + * h5 group + */ + +void HDF5Impl::grcreate( String grlabel ) +{ + hid_t gid = H5Gcreate( m_h5_file_id, grlabel.c_str(), + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Gclose( gid ); +} + +/* + * cv:Mat + */ + +vector<int> HDF5Impl::dsgetsize( String dslabel, int dims_flag ) const +{ + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, dslabel.c_str(), H5P_DEFAULT ); + + // get file space + hid_t fspace = H5Dget_space( dsdata ); + + // fetch rank + int n_dims = H5Sget_simple_extent_ndims( fspace ); + + // fetch dims + hsize_t dsdims[n_dims]; + if ( dims_flag == H5_GETDIMS ) + H5Sget_simple_extent_dims( fspace, dsdims, NULL ); + else + H5Sget_simple_extent_dims( fspace, NULL, dsdims ); + + // fill with size data + vector<int> SizeVect( n_dims ); + for ( int d = 0; d < n_dims; d++ ) + SizeVect[d] = (int) dsdims[d]; + + H5Dclose( dsdata ); + H5Sclose( fspace ); + + return SizeVect; +} + +int HDF5Impl::dsgettype( String dslabel ) const +{ + hid_t h5type; + + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, dslabel.c_str(), H5P_DEFAULT ); + + // get data type + hid_t dstype = H5Dget_type( dsdata ); + + int channs = 1; + if ( H5Tget_class( dstype ) == H5T_ARRAY ) + { + // fetch channs + hsize_t ardims[1]; + H5Tget_array_dims( dstype, ardims ); + channs = ardims[0]; + // fetch depth + hid_t tsuper = H5Tget_super( dstype ); + h5type = H5Tget_native_type( tsuper, H5T_DIR_ASCEND ); + H5Tclose( tsuper ); + } + else + h5type = H5Tget_native_type( dstype, H5T_DIR_DESCEND ); + + // convert to CVType + int cvtype = GetCVtype( h5type ); + + H5Tclose( dstype ); + H5Dclose( dsdata ); + + return CV_MAKETYPE( cvtype, channs ); +} + +// overload +void HDF5Impl::dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel, + const vector<int>& dims_chunks ) const +{ + CV_Assert( &dims_chunks[0] == NULL || dims_chunks.size() == 2 ); + dscreate( rows, cols, type, dslabel, compresslevel, &dims_chunks[0] ); +} + +void HDF5Impl::dscreate( const int rows, const int cols, const int type, + String dslabel, const int compresslevel, const int* dims_chunks ) const +{ + // dataset dims + int dsizes[2] = { rows, cols }; + + // create the two dim array + dscreate( 2, dsizes, type, dslabel, compresslevel, dims_chunks ); +} + +// overload +void HDF5Impl::dscreate( const vector<int>& sizes, const int type, + String dslabel, const int compresslevel, + const vector<int>& dims_chunks ) const +{ + CV_Assert( &dims_chunks[0] == NULL || dims_chunks.size() == sizes.size() ); + + const int n_dims = (int) sizes.size(); + dscreate( n_dims, &sizes[0], type, dslabel, compresslevel, &dims_chunks[0] ); +} + +void HDF5Impl::dscreate( const int n_dims, const int* sizes, const int type, + String dslabel, const int compresslevel, const int* dims_chunks ) const +{ + // compress valid H5_NONE, 0-9 + CV_Assert( compresslevel >= H5_NONE && compresslevel <= 9 ); + + if ( hlexists( dslabel ) == true ) + CV_Error( Error::StsInternal, "Requested dataset already exists." ); + + int channs = CV_MAT_CN( type ); + + hsize_t chunks[n_dims]; + hsize_t dsdims[n_dims]; + hsize_t maxdim[n_dims]; + + // dimension space + for ( int d = 0; d < n_dims; d++ ) + { + CV_Assert( sizes[d] >= H5_UNLIMITED ); + + // dataset dimension + if ( sizes[d] == H5_UNLIMITED ) + { + CV_Assert( dims_chunks != NULL ); + + dsdims[d] = 0; + maxdim[d] = H5S_UNLIMITED; + } + else + { + dsdims[d] = sizes[d]; + maxdim[d] = sizes[d]; + } + // default chunking + if ( dims_chunks == NULL ) + chunks[d] = sizes[d]; + else + chunks[d] = dims_chunks[d]; + } + + // create dataset space + hid_t dspace = H5Screate_simple( n_dims, dsdims, maxdim ); + + // create data property + hid_t dsdcpl = H5Pcreate( H5P_DATASET_CREATE ); + + // set properties + if ( compresslevel >= 0 ) + H5Pset_deflate( dsdcpl, compresslevel ); + + if ( dims_chunks != NULL || compresslevel >= 0 ) + H5Pset_chunk( dsdcpl, n_dims, chunks ); + + // convert to h5 type + hid_t dstype = GetH5type( type ); + + // expand channs + if ( channs > 1 ) + { + hsize_t adims[1] = { channs }; + dstype = H5Tarray_create( dstype, 1, adims ); + } + + // create data + H5Dcreate( m_h5_file_id, dslabel.c_str(), dstype, + dspace, H5P_DEFAULT, dsdcpl, H5P_DEFAULT ); + + if ( channs > 1 ) + H5Tclose( dstype ); + + H5Pclose( dsdcpl ); + H5Sclose( dspace ); +} + +// overload +void HDF5Impl::dsread( OutputArray Array, String dslabel, + const vector<int>& dims_offset, + const vector<int>& dims_counts ) const +{ + dsread( Array, dslabel, &dims_offset[0], &dims_counts[0] ); +} + +void HDF5Impl::dsread( OutputArray Array, String dslabel, + const int* dims_offset, const int* dims_counts ) const +{ + // only Mat support + CV_Assert( Array.isMat() ); + + hid_t h5type; + + // open the HDF5 dataset + hid_t dsdata = H5Dopen( m_h5_file_id, dslabel.c_str(), H5P_DEFAULT ); + + // get data type + hid_t dstype = H5Dget_type( dsdata ); + + int channs = 1; + if ( H5Tget_class( dstype ) == H5T_ARRAY ) + { + // fetch channs + hsize_t ardims[1]; + H5Tget_array_dims( dstype, ardims ); + channs = ardims[0]; + // fetch depth + hid_t tsuper = H5Tget_super( dstype ); + h5type = H5Tget_native_type( tsuper, H5T_DIR_ASCEND ); + H5Tclose( tsuper ); + } else + h5type = H5Tget_native_type( dstype, H5T_DIR_ASCEND ); + + int dType = GetCVtype( h5type ); + + // get file space + hid_t fspace = H5Dget_space( dsdata ); + + // fetch rank + int n_dims = H5Sget_simple_extent_ndims( fspace ); + + // fetch dims + hsize_t dsdims[n_dims]; + H5Sget_simple_extent_dims( fspace, dsdims, NULL ); + + // set amount by custom offset + if ( dims_offset != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + dsdims[d] -= dims_offset[d]; + } + + // set custom amount of data + if ( dims_counts != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + dsdims[d] = dims_counts[d]; + } + + // get memory write window + int mxdims[n_dims]; + hsize_t foffset[n_dims]; + for ( int d = 0; d < n_dims; d++ ) + { + foffset[d] = 0; + mxdims[d] = (int) dsdims[d]; + } + + // allocate persistent Mat + Array.create( n_dims, mxdims, CV_MAKETYPE(dType, channs) ); + + // get blank data space + hid_t dspace = H5Screate_simple( n_dims, dsdims, NULL ); + + // get matrix write window + H5Sselect_hyperslab( dspace, H5S_SELECT_SET, + foffset, NULL, dsdims, NULL ); + + // set custom offsets + if ( dims_offset != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + foffset[d] = dims_offset[d]; + } + + // get a file read window + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + foffset, NULL, dsdims, NULL ); + + // read from DS + Mat matrix = Array.getMat(); + H5Dread( dsdata, dstype, dspace, fspace, H5P_DEFAULT, matrix.data ); + + H5Tclose( dstype ); + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +// overload +void HDF5Impl::dswrite( InputArray Array, String dslabel, + const vector<int>& dims_offset, + const vector<int>& dims_counts ) const +{ + dswrite( Array, dslabel, &dims_offset[0], &dims_counts[0] ); +} + +void HDF5Impl::dswrite( InputArray Array, String dslabel, + const int* dims_offset, const int* dims_counts ) const +{ + // only Mat support + CV_Assert( Array.isMat() ); + + Mat matrix = Array.getMat(); + + // memory array should be compact + CV_Assert( matrix.isContinuous() ); + + int n_dims = matrix.dims; + int channs = matrix.channels(); + + int dsizes[n_dims]; + hsize_t dsdims[n_dims]; + hsize_t offset[n_dims]; + // replicate Mat dimensions + for ( int d = 0; d < n_dims; d++ ) + { + offset[d] = 0; + dsizes[d] = matrix.size[d]; + dsdims[d] = matrix.size[d]; + } + + // pre-create dataset if needed + if ( hlexists( dslabel ) == false ) + dscreate( n_dims, dsizes, matrix.type(), dslabel ); + + // set custom amount of data + if ( dims_counts != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + dsdims[d] = dims_counts[d]; + } + + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, dslabel.c_str(), H5P_DEFAULT ); + + // create input data space + hid_t dspace = H5Screate_simple( n_dims, dsdims, NULL ); + + // set custom offsets + if ( dims_offset != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + offset[d] = dims_offset[d]; + } + + // create offset write window space + hid_t fspace = H5Dget_space( dsdata ); + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + offset, NULL, dsdims, NULL ); + + // convert type + hid_t dstype = GetH5type( matrix.type() ); + + // expand channs + if ( matrix.channels() > 1 ) + { + hsize_t adims[1] = { channs }; + dstype = H5Tarray_create( dstype, 1, adims ); + } + + // write into dataset + H5Dwrite( dsdata, dstype, dspace, fspace, + H5P_DEFAULT, matrix.data ); + + if ( matrix.channels() > 1 ) + H5Tclose( dstype ); + + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +// overload +void HDF5Impl::dsinsert( InputArray Array, String dslabel, + const vector<int>& dims_offset, + const vector<int>& dims_counts ) const +{ + dsinsert( Array, dslabel, &dims_offset[0], &dims_counts[0] ); +} + +void HDF5Impl::dsinsert( InputArray Array, String dslabel, + const int* dims_offset, const int* dims_counts ) const +{ + // only Mat support + CV_Assert( Array.isMat() ); + + // check dataset exists + if ( hlexists( dslabel ) == false ) + CV_Error( Error::StsInternal, "Dataset does not exist." ); + + Mat matrix = Array.getMat(); + + // memory array should be compact + CV_Assert( matrix.isContinuous() ); + + int n_dims = matrix.dims; + int channs = matrix.channels(); + + hsize_t dsdims[n_dims]; + hsize_t offset[n_dims]; + // replicate Mat dimensions + for ( int d = 0; d < n_dims; d++ ) + { + offset[d] = 0; + dsdims[d] = matrix.size[d]; + } + + // set custom amount of data + if ( dims_counts != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + { + CV_Assert( dims_counts[d] <= matrix.size[d] ); + dsdims[d] = dims_counts[d]; + } + } + + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, dslabel.c_str(), H5P_DEFAULT ); + + // create input data space + hid_t dspace = H5Screate_simple( n_dims, dsdims, NULL ); + + // set custom offsets + if ( dims_offset != NULL ) + { + for ( int d = 0; d < n_dims; d++ ) + offset[d] = dims_offset[d]; + } + + // get actual file space and dims + hid_t fspace = H5Dget_space( dsdata ); + int f_dims = H5Sget_simple_extent_ndims( fspace ); + hsize_t fsdims[f_dims]; + H5Sget_simple_extent_dims( fspace, fsdims, NULL ); + H5Sclose( fspace ); + + CV_Assert( f_dims == n_dims ); + + // compute new extents + hsize_t nwdims[n_dims]; + for ( int d = 0; d < n_dims; d++ ) + { + // init + nwdims[d] = 0; + // add offset + if ( dims_offset != NULL ) + nwdims[d] += dims_offset[d]; + // add counts or matrixsize + if ( dims_counts != NULL ) + nwdims[d] += dims_counts[d]; + else + nwdims[d] += matrix.size[d]; + + // clamp back if smaller + if ( nwdims[d] < fsdims[d] ) + nwdims[d] = fsdims[d]; + } + + // extend dataset + H5Dextend( dsdata, nwdims ); + + // get the extended data space + fspace = H5Dget_space( dsdata ); + + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + offset, NULL, dsdims, NULL ); + + // convert type + hid_t dstype = GetH5type( matrix.type() ); + + // expand channs + if ( matrix.channels() > 1 ) + { + hsize_t adims[1] = { channs }; + dstype = H5Tarray_create( dstype, 1, adims ); + } + + // write into dataset + H5Dwrite( dsdata, dstype, dspace, fspace, + H5P_DEFAULT, matrix.data ); + + if ( matrix.channels() > 1 ) + H5Tclose( dstype ); + + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +/* + * std::vector<cv::KeyPoint> + */ + +int HDF5Impl::kpgetsize( String kplabel, int dims_flag ) const +{ + vector<int> sizes = dsgetsize( kplabel, dims_flag ); + + CV_Assert( sizes.size() == 1 ); + + return sizes[0]; +} + +void HDF5Impl::kpcreate( const int size, String kplabel, + const int compresslevel, const int chunks ) const +{ + // size valid + CV_Assert( size >= H5_UNLIMITED ); + + // valid chunks + CV_Assert( chunks == H5_NONE || chunks > 0 ); + + // compress valid -1, 0-9 + CV_Assert( compresslevel >= H5_NONE && compresslevel <= 9 ); + + if ( hlexists( kplabel ) == true ) + CV_Error( Error::StsInternal, "Requested dataset already exists." ); + + hsize_t dchunk[1]; + hsize_t dsdims[1]; + hsize_t maxdim[1]; + + // dataset dimension + if ( size == H5_UNLIMITED ) + { + dsdims[0] = 0; + maxdim[0] = H5S_UNLIMITED; + } + else + { + dsdims[0] = size; + maxdim[0] = size; + } + + // default chunking + if ( chunks == H5_NONE ) + if ( size == H5_UNLIMITED ) + dchunk[0] = 1; + else + dchunk[0] = size; + else + dchunk[0] = chunks; + + // dataset compound type + hid_t dstype = H5Tcreate( H5T_COMPOUND, sizeof( KeyPoint ) ); + H5Tinsert( dstype, "xpos", HOFFSET( KeyPoint, pt.x ), H5T_NATIVE_FLOAT ); + H5Tinsert( dstype, "ypos", HOFFSET( KeyPoint, pt.y ), H5T_NATIVE_FLOAT ); + H5Tinsert( dstype, "size", HOFFSET( KeyPoint, size ), H5T_NATIVE_FLOAT ); + H5Tinsert( dstype, "angle", HOFFSET( KeyPoint, angle ), H5T_NATIVE_FLOAT ); + H5Tinsert( dstype, "response", HOFFSET( KeyPoint, response ), H5T_NATIVE_FLOAT ); + H5Tinsert( dstype, "octave", HOFFSET( KeyPoint, octave ), H5T_NATIVE_INT32 ); + H5Tinsert( dstype, "class_id", HOFFSET( KeyPoint, class_id ), H5T_NATIVE_INT32 ); + + // create dataset space + hid_t dspace = H5Screate_simple( 1, dsdims, maxdim ); + + // create data property + hid_t dsdcpl = H5Pcreate( H5P_DATASET_CREATE ); + + // set properties + if ( compresslevel >= 0 ) + H5Pset_deflate( dsdcpl, compresslevel ); + + // if chunking or compression + if ( dchunk[0] > 0 || compresslevel >= 0 ) + H5Pset_chunk( dsdcpl, 1, dchunk ); + + // create data + H5Dcreate( m_h5_file_id, kplabel.c_str(), dstype, + dspace, H5P_DEFAULT, dsdcpl, H5P_DEFAULT ); + + H5Tclose( dstype ); + H5Pclose( dsdcpl ); + H5Sclose( dspace ); +} + +void HDF5Impl::kpwrite( const vector<KeyPoint> keypoints, String kplabel, + const int offset, const int counts ) const +{ + CV_Assert( keypoints.size() > 0 ); + + hsize_t dsddims[1]; + hsize_t doffset[1]; + + // replicate vector dimension + doffset[0] = 0; + dsddims[0] = keypoints.size(); + + // pre-create dataset if needed + if ( hlexists( kplabel ) == false ) + kpcreate( dsddims[0], kplabel ); + + // set custom amount of data + if ( counts != H5_NONE ) + dsddims[0] = counts; + + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, kplabel.c_str(), H5P_DEFAULT ); + + // create input data space + hid_t dspace = H5Screate_simple( 1, dsddims, NULL ); + + // set custom offsets + if ( offset != H5_NONE ) + doffset[0] = offset; + + // create offset write window space + hid_t fspace = H5Dget_space( dsdata ); + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + doffset, NULL, dsddims, NULL ); + + // memory compound type + hid_t mmtype = H5Tcreate( H5T_COMPOUND, sizeof( KeyPoint ) ); + H5Tinsert( mmtype, "xpos", HOFFSET( KeyPoint, pt.x ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "ypos", HOFFSET( KeyPoint, pt.y ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "size", HOFFSET( KeyPoint, size ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "angle", HOFFSET( KeyPoint, angle ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "response", HOFFSET( KeyPoint, response ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "octave", HOFFSET( KeyPoint, octave ), H5T_NATIVE_INT32 ); + H5Tinsert( mmtype, "class_id", HOFFSET( KeyPoint, class_id ), H5T_NATIVE_INT32 ); + + // write into dataset + H5Dwrite( dsdata, mmtype, dspace, fspace, H5P_DEFAULT, &keypoints[0] ); + + H5Tclose( mmtype ); + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +void HDF5Impl::kpinsert( const vector<KeyPoint> keypoints, String kplabel, + const int offset, const int counts ) const +{ + CV_Assert( keypoints.size() > 0 ); + + // check dataset exists + if ( hlexists( kplabel ) == false ) + CV_Error( Error::StsInternal, "Dataset does not exist." ); + + hsize_t dsddims[1]; + hsize_t doffset[1]; + + // replicate vector dimension + doffset[0] = 0; + dsddims[0] = keypoints.size(); + + // set custom amount of data + if ( counts != H5_NONE ) + dsddims[0] = counts; + + // open dataset + hid_t dsdata = H5Dopen( m_h5_file_id, kplabel.c_str(), H5P_DEFAULT ); + + // create input data space + hid_t dspace = H5Screate_simple( 1, dsddims, NULL ); + + // set custom offsets + if ( offset != H5_NONE ) + doffset[0] = offset; + + // get actual file space and dims + hid_t fspace = H5Dget_space( dsdata ); + int f_dims = H5Sget_simple_extent_ndims( fspace ); + hsize_t fsdims[f_dims]; + H5Sget_simple_extent_dims( fspace, fsdims, NULL ); + H5Sclose( fspace ); + + CV_Assert( f_dims == 1 ); + + // compute new extents + hsize_t nwdims[1] = { 0 }; + // add offset + if ( offset != H5_NONE ) + nwdims[0] += offset; + // add counts or matrixsize + if ( counts != H5_NONE ) + nwdims[0] += counts; + else + nwdims[0] += keypoints.size(); + + // clamp back if smaller + if ( nwdims[0] < fsdims[0] ) + nwdims[0] = fsdims[0]; + + // extend dataset + H5Dextend( dsdata, nwdims ); + + // get the extended data space + fspace = H5Dget_space( dsdata ); + + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + doffset, NULL, dsddims, NULL ); + + // memory compound type + hid_t mmtype = H5Tcreate( H5T_COMPOUND, sizeof( KeyPoint ) ); + H5Tinsert( mmtype, "xpos", HOFFSET( KeyPoint, pt.x ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "ypos", HOFFSET( KeyPoint, pt.y ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "size", HOFFSET( KeyPoint, size ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "angle", HOFFSET( KeyPoint, angle ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "response", HOFFSET( KeyPoint, response ), H5T_NATIVE_FLOAT ); + H5Tinsert( mmtype, "octave", HOFFSET( KeyPoint, octave ), H5T_NATIVE_INT32 ); + H5Tinsert( mmtype, "class_id", HOFFSET( KeyPoint, class_id ), H5T_NATIVE_INT32 ); + + // write into dataset + H5Dwrite( dsdata, mmtype, dspace, fspace, H5P_DEFAULT, &keypoints[0] ); + + H5Tclose( mmtype ); + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +void HDF5Impl::kpread( vector<KeyPoint>& keypoints, String kplabel, + const int offset, const int counts ) const +{ + CV_Assert( keypoints.size() == 0 ); + + // open the HDF5 dataset + hid_t dsdata = H5Dopen( m_h5_file_id, kplabel.c_str(), H5P_DEFAULT ); + + // get data type + hid_t dstype = H5Dget_type( dsdata ); + + // get file space + hid_t fspace = H5Dget_space( dsdata ); + + // fetch rank + int n_dims = H5Sget_simple_extent_ndims( fspace ); + + CV_Assert( n_dims == 1 ); + + // fetch dims + hsize_t dsddims[1]; + H5Sget_simple_extent_dims( fspace, dsddims, NULL ); + + // set amount by custom offset + if ( offset != H5_NONE ) + dsddims[0] -= offset; + + // set custom amount of data + if ( counts != H5_NONE ) + dsddims[0] = counts; + + // get memory write window + hsize_t foffset[1] = { 0 }; + + // allocate keypoints vector + keypoints.resize( dsddims[0] ); + + // get blank data space + hid_t dspace = H5Screate_simple( 1, dsddims, NULL ); + + // get matrix write window + H5Sselect_hyperslab( dspace, H5S_SELECT_SET, + foffset, NULL, dsddims, NULL ); + + // set custom offsets + if ( offset != H5_NONE ) + foffset[0] = offset; + + // get a file read window + H5Sselect_hyperslab( fspace, H5S_SELECT_SET, + foffset, NULL, dsddims, NULL ); + + // read from DS + H5Dread( dsdata, dstype, dspace, fspace, H5P_DEFAULT, &keypoints[0] ); + + H5Tclose( dstype ); + H5Sclose( dspace ); + H5Sclose( fspace ); + H5Dclose( dsdata ); +} + +CV_EXPORTS Ptr<HDF5> open( String HDF5Filename ) +{ + return makePtr<HDF5Impl>( HDF5Filename ); +} + +} // end namespace hdf +} // end namespace cv diff --git a/modules/hdf/src/precomp.hpp b/modules/hdf/src/precomp.hpp new file mode 100644 index 00000000..3069155f --- /dev/null +++ b/modules/hdf/src/precomp.hpp @@ -0,0 +1,43 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2015 + * Balint Cristian <cristian dot balint at gmail dot com> + * + * 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. + * * Neither the name of the copyright holders 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. + *********************************************************************/ + +#ifndef __OPENCV_HDF_PRECOMP_H__ +#define __OPENCV_HDF_PRECOMP_H__ + +#include "opencv2/core.hpp" + +#include <vector> + +#include "opencv2/hdf.hpp" +#endif -- 2.18.0