From 4a3eac47435fc7bcfeb8999373fbf801ed0efaf1 Mon Sep 17 00:00:00 2001 From: Cadene Date: Tue, 16 Apr 2024 12:53:31 +0000 Subject: [PATCH] fix unit tests, stats was missing, visualize_dataset was broken --- lerobot/common/datasets/factory.py | 6 ++- .../scripts/download_and_upload_dataset.py | 39 ++++++++++++------ lerobot/scripts/visualize_dataset.py | 8 ++-- .../data/aloha_sim_insertion_human/stats.pth | Bin 0 -> 3922 bytes .../aloha_sim_insertion_scripted/stats.pth | Bin 0 -> 3922 bytes .../aloha_sim_transfer_cube_human/stats.pth | Bin 0 -> 3922 bytes .../stats.pth | Bin 0 -> 3922 bytes tests/data/pusht/stats.pth | Bin 0 -> 3922 bytes tests/data/xarm_lift_medium/stats.pth | Bin 0 -> 3922 bytes 9 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 tests/data/aloha_sim_insertion_human/stats.pth create mode 100644 tests/data/aloha_sim_insertion_scripted/stats.pth create mode 100644 tests/data/aloha_sim_transfer_cube_human/stats.pth create mode 100644 tests/data/aloha_sim_transfer_cube_scripted/stats.pth create mode 100644 tests/data/pusht/stats.pth create mode 100644 tests/data/xarm_lift_medium/stats.pth diff --git a/lerobot/common/datasets/factory.py b/lerobot/common/datasets/factory.py index 3bf684c9..ecb28e26 100644 --- a/lerobot/common/datasets/factory.py +++ b/lerobot/common/datasets/factory.py @@ -53,7 +53,11 @@ def make_dataset( stats["action"]["max"] = torch.tensor([511.0, 511.0], dtype=torch.float32) elif stats_path is None: # load stats if the file exists already or compute stats and save it - precomputed_stats_path = Path("data") / cfg.dataset_id / "stats.pth" + if DATA_DIR is None: + # TODO(rcadene): clean stats + precomputed_stats_path = Path("data") / cfg.dataset_id / "stats.pth" + else: + precomputed_stats_path = DATA_DIR / cfg.dataset_id / "stats.pth" if precomputed_stats_path.exists(): stats = torch.load(precomputed_stats_path) else: diff --git a/lerobot/scripts/download_and_upload_dataset.py b/lerobot/scripts/download_and_upload_dataset.py index 267b619d..0ff86697 100644 --- a/lerobot/scripts/download_and_upload_dataset.py +++ b/lerobot/scripts/download_and_upload_dataset.py @@ -5,6 +5,7 @@ useless dependencies when using datasets. import io import pickle +import shutil from pathlib import Path import einops @@ -44,7 +45,7 @@ def download_and_extract_zip(url: str, destination_folder: Path) -> bool: return False -def download_and_upload_pusht(root, dataset_id="pusht", fps=10): +def download_and_upload_pusht(root, root_tests, dataset_id="pusht", fps=10): try: import pymunk from gym_pusht.envs.pusht import PushTEnv, pymunk_to_shapely @@ -197,12 +198,12 @@ def download_and_upload_pusht(root, dataset_id="pusht", fps=10): dataset = dataset.with_format("torch") num_items_first_ep = ep_dicts[0]["frame_id"].shape[0] - dataset.select(range(num_items_first_ep)).save_to_disk(f"tests/data/{dataset_id}/train") + dataset.select(range(num_items_first_ep)).save_to_disk(f"{root_tests}/{dataset_id}/train") dataset.push_to_hub(f"lerobot/{dataset_id}", token=True) dataset.push_to_hub(f"lerobot/{dataset_id}", token=True, revision="v1.0") -def download_and_upload_xarm(root, dataset_id, fps=15): +def download_and_upload_xarm(root, root_tests, dataset_id, fps=15): root = Path(root) raw_dir = root / f"{dataset_id}_raw" if not raw_dir.exists(): @@ -308,12 +309,12 @@ def download_and_upload_xarm(root, dataset_id, fps=15): dataset = dataset.with_format("torch") num_items_first_ep = ep_dicts[0]["frame_id"].shape[0] - dataset.select(range(num_items_first_ep)).save_to_disk(f"tests/data/{dataset_id}/train") + dataset.select(range(num_items_first_ep)).save_to_disk(f"{root_tests}/{dataset_id}/train") dataset.push_to_hub(f"lerobot/{dataset_id}", token=True) dataset.push_to_hub(f"lerobot/{dataset_id}", token=True, revision="v1.0") -def download_and_upload_aloha(root, dataset_id, fps=50): +def download_and_upload_aloha(root, root_tests, dataset_id, fps=50): folder_urls = { "aloha_sim_insertion_human": "https://drive.google.com/drive/folders/1RgyD0JgTX30H4IM5XZn8I3zSV_mr8pyF", "aloha_sim_insertion_scripted": "https://drive.google.com/drive/folders/1TsojQQSXtHEoGnqgJ3gmpPQR2DPLtS2N", @@ -453,16 +454,30 @@ def download_and_upload_aloha(root, dataset_id, fps=50): dataset = dataset.with_format("torch") num_items_first_ep = ep_dicts[0]["frame_id"].shape[0] - dataset.select(range(num_items_first_ep)).save_to_disk(f"tests/data/{dataset_id}/train") + dataset.select(range(num_items_first_ep)).save_to_disk(f"{root_tests}/{dataset_id}/train") dataset.push_to_hub(f"lerobot/{dataset_id}", token=True) dataset.push_to_hub(f"lerobot/{dataset_id}", token=True, revision="v1.0") if __name__ == "__main__": root = "data" - download_and_upload_pusht(root, dataset_id="pusht") - download_and_upload_xarm(root, dataset_id="xarm_lift_medium") - download_and_upload_aloha(root, dataset_id="aloha_sim_insertion_human") - download_and_upload_aloha(root, dataset_id="aloha_sim_insertion_scripted") - download_and_upload_aloha(root, dataset_id="aloha_sim_transfer_cube_human") - download_and_upload_aloha(root, dataset_id="aloha_sim_transfer_cube_scripted") + root_tests = "{root_tests}" + + download_and_upload_pusht(root, root_tests, dataset_id="pusht") + download_and_upload_xarm(root, root_tests, dataset_id="xarm_lift_medium") + download_and_upload_aloha(root, root_tests, dataset_id="aloha_sim_insertion_human") + download_and_upload_aloha(root, root_tests, dataset_id="aloha_sim_insertion_scripted") + download_and_upload_aloha(root, root_tests, dataset_id="aloha_sim_transfer_cube_human") + download_and_upload_aloha(root, root_tests, dataset_id="aloha_sim_transfer_cube_scripted") + + dataset_ids = [ + "pusht", + "xarm_lift_medium", + "aloha_sim_insertion_human", + "aloha_sim_insertion_scripted", + "aloha_sim_transfer_cube_human", + "aloha_sim_transfer_cube_scripted", + ] + for dataset_id in dataset_ids: + # assume stats have been precomputed + shutil.copy(f"{root}/{dataset_id}/stats.pth", f"{root_tests}/{dataset_id}/stats.pth") diff --git a/lerobot/scripts/visualize_dataset.py b/lerobot/scripts/visualize_dataset.py index 4b7b7d6c..10ed98d5 100644 --- a/lerobot/scripts/visualize_dataset.py +++ b/lerobot/scripts/visualize_dataset.py @@ -62,12 +62,12 @@ def render_dataset(dataset, out_dir, max_num_episodes): ) dl_iter = iter(dataloader) - num_episodes = len(dataset.data_ids_per_episode) - for ep_id in range(min(max_num_episodes, num_episodes)): + for ep_id in range(min(max_num_episodes, dataset.num_episodes)): logging.info(f"Rendering episode {ep_id}") frames = {} - for _ in dataset.data_ids_per_episode[ep_id]: + end_of_episode = False + while not end_of_episode: item = next(dl_iter) for im_key in dataset.image_keys: @@ -77,6 +77,8 @@ def render_dataset(dataset, out_dir, max_num_episodes): # add current frame to list of frames to render frames[im_key].append(item[im_key]) + end_of_episode = item["index"].item() == item["episode_data_id_to"].item() + out_dir.mkdir(parents=True, exist_ok=True) for im_key in dataset.image_keys: if len(dataset.image_keys) > 1: diff --git a/tests/data/aloha_sim_insertion_human/stats.pth b/tests/data/aloha_sim_insertion_human/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..a7b9248f0ac2945776d87ce2e7cf885c933480dd GIT binary patch literal 3922 zcmbW4dr%Wc9LFypuLuD|Emp9lMINn&ydgsNG-5FA_|5T=n1{377F!(S~$MWYIQlC zHlHVMVO~|Kt;$xK?XXfp7@lf?5fl^|V5HB2n<|A-c1GJCWM^$lVYJ<6bAsPy46-?~ z&W_MY6r5~;&-l<4=9MrOLuGIZi$3c|ry?|tMdK+r%>bWcQAY)Q9z*3Yfkj{Nqc0*f zkwvFdFv$RuJ;rDzW|zfntEx7mQ5K__vB8uYn3`e_#Ox{HORT+@{q|-cdox*kVhW}i zpu~;`u7pw+k@*ohLKG~bq@c2^8$6OV3veiOt~tdt_IHMN1Fk27^8s{T!4%Q>IHhiCb$q?(2bro z{t5*b8DK6iYBkJb(R>bCFn}6Zw2*?Y8ekD8`T)j3sRS-&*(Ds-IKaNfvc(j9-2h8C z!{B43@C}w-%3)0d>@t=$Q_y07R*y%VuMY{0y&O$hm}QT8{)ibHNhJ6)*0PGSxzOU* zY2$~bESAAe2Sqt;NTXJBa|%jh)JN6`CuVc1LYhRU&3du`rZ)UbdwGAR(=dzP+nh;W z@4ecw@sFQD(;P=f;=^sA$$X*n!pcJ0awC!KYt03DbK}Uw&KO$Q{5C)yS0uOnlfih4 z`jb2saXpsmv_g)@L~O67?`{bw{vj7c#Qvy>xnu@Q>P6u8(RvUcIstr?)dr5NZUAfZ zvVnMO3#i}L2)(S@xhl2Lf1SEX z?^tH^W`)*vqOmeBDtdFw`U+hC)aJi47d#|sNrHx4bN;uEFxU&)FK+3uJeWpqxOKL( zw6cW0H$RDN4A6q*+IVv6`3dyWq$|GlcWqnS<1ygY!D{$2sueYnmy|J}q1 z(U|qqas9~Z273ERkGCXbH@Nj+Bi&j4FRip+qft`ydG6CuO*P&}MJF`hMVHYx^*_;n zwivyih;_%&{J)#L=hrdoXW;trrX(QVJ{^R-mFyeSrv8&t$DDuar_VpSx&VCJmj|LZ z7y9()_hiaO*B`1s)wU`(a~^r>`Xd*jk-W60tO)ImsE=Ir;s}Mm>WI;iG>th8Im}MS zYTt>-aJ6?~Fg9C5M>CzX7XTh?$ghj~roXsY-mw;JCet#;vsZ>guc*Wwe{!RD(R_UnIxG`W%m+QsKKmaSx}Qb> literal 0 HcmV?d00001 diff --git a/tests/data/aloha_sim_insertion_scripted/stats.pth b/tests/data/aloha_sim_insertion_scripted/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..990d46470be60bb7dd02c8bca98c2702316f2060 GIT binary patch literal 3922 zcmbW4drVVT9LFz|mjx;?WPteMG3ylCLR(s-=PGKgRun3a0=imCL#vf~=q=7E#OY9R zx@127fH@_^Wy##;;+#W-cE-4nC@ynx8jah0Wcx$i4AX3LqC4l_0{2{o1W$W%Z+q_N z_x=6O<9E(2GKxYdDk6d!Q6yA6tIkL43?UaQEI*2S_efo#$9i7WLdn7)8!Fc>h0BDr>n-o*y}v*dP_sL7=}ot=5X}9 z2R&)6wZl*j5$j!UE3=aOQw+n*LFgGd3^$9=)waP4Ba9LTPAE1~MoBXi8-to*WQ)z_ zcDd}VCm!*N`Wkz^y=I})#)x6mEE9}opu_|xvKBl{4UBOhZAYksv#o%!4%TK|z~(F1 zW_*hSqmvkzV1lo*=z8m$Fp)sBVG@TX2hhnFP2tcf44i6$(>T;w2d5Kg4xGWEsR1+% zqv;%)!NAu{aHiKBi(+3cx3EQ+6ZS!9U{V>628van;oz>2iwc!>}4@f zW`c4D8Mq26I3zoOjsupz||c429LE2vTHcj%D`$9w0XT*MaJ-m#2;drYBcR}EgE&jPNZf8R&4bq#_qkIN}3ZR)Om_nGghzKp&;`K$bE772YTHL?e{VUK@$ys=4x zyv6lJ9!vNhEA$0op2vUg$pEKy;q;}n9pLKHcfdip7W|QM4y4I?LB+sy5d3){wfv9;Z78`f_reqTMdel!(L=%X&1Fzf(DZKWUFTSwz7wz;cz@98KM53Zxf z6isOfnPy_!=Et3KTJf(hxVF!q{OB^6*Hgi^EjZr6w&A|onRc~r z-1e*WZ`Zf|Eg9pBQ_eb5!`zmQ?3w_R<|r%ELB z{n*GJ@3sMU{=1fE9UZs6iq!w>n{?3nD1!d*NHh3u(smH{bRj64a}Jyjss#hO>0p5+ zf=;U34L08140cT}1WW3=K}i1&R=;cK@3L|0t4aM+U5~+K?E#%~X)9e4T1NLjUqb7u z+v)vZ@1TF)(ht6T@2Kv_!dCkB2UT?X?&b8gsw4E7J)79I;!M5w+qm^}N&WJ(cYTXe z`ur{BH-RI*iXG#Gx7+-N#*4aX&z|`7#y1@ducA%#+|P^uJa zWsX*rqf*EvG6pNLXw{x zNwSwDRYH=V)=1KTUekD*Y9Yx_NF>=ylDR^XpLR&nFoDq22uXgzAjv}{sTGp^MNdwJ z6!D;DAiW4t{z^20(XPgPW+OIiYAh~A`ZWzQV#=* U8^4Pd8AI`5nTkOl{5<#Ue{4&SH2?qr literal 0 HcmV?d00001 diff --git a/tests/data/aloha_sim_transfer_cube_human/stats.pth b/tests/data/aloha_sim_transfer_cube_human/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..1ae356e3ad25b2c9ddf300914909ef60a8f08614 GIT binary patch literal 3922 zcmbW43rtg27{@PG9+tN{gsmv}TA5l}`k+;Mu0yS8any^C4(6bgN~?u(=q+0?bSXQWsYOvh=pd#9M=e?=CDQ>-DJ0W z+-^tE6R)(Orrc5ED4*@Jvr-t7!N6D+#xXEHXu->t!vrVNc7{87+bWpo4B8wSvN;Od z9Npx^=nE`NV&Iq{T4`GalL<5nrts*CA@n7T%6K%Dg<}~wjz?WpFpWUfFr7!ohtLTa zoyen;SU8!1FZ-;CC}vNY*HKez!*5xEXT|~LbudHjlo0kb@D<+Pl#sot*xoeWo`Qv$ z3{*PFz_n1tBUvFtjS&rxXj!OZV78MCSO@hyVhADAF*1Wka#(0&AaIg_W<#1sW`>Yg zF_Oz8vsgHrfpdJ;BvGKQO0*=r3f5B%O?7atI9ff-Lq;VmoQI7{%wguR^>9AE;2Se< zJfDRN82FkfY6C3bQHuaw*n=`WTFAmh3|uUTei(94rG!g(cBz21_OP$>Y!M5K8Tf|a zH3V4|T*k9+3fPh!wv=aWEG%Q7-RDzS%#l&a7ZceswCr)W44AR;RC*|5%id-k9=!NX zxvBk9mdIeYi)GyotkGz^mV(n5_klHHsF{MQkf}7~<~&<~Q0xCs+quUlF5XQGT#r~w zpTBAJUu(M0J+W=`@A)SaEXxw^PTqfI0+gkSn)BO*?xyDvHIlLZpeJP_0&CPV_zHPzzXVOOgIQaXI zp@{iclk%4sxE;GSFUpn4{7f1CJKn14)0j1=|09 zFMyr}F6Ac#=N~M&esIY88dCpY-GdXF8|i`BTi4Pnj_3Oy7v14z9oXjo=H>(N^run& z^0Y6wkJePsiCJZwsXItk6*Zz>!zn0YRd^|TtzTy+G!!-qTADC$@ zo|X(MR~$sf{&l)v_4Rq<{&q2zljext=O8ZRjf;%@-={2@y`su?h z!T7sp)I|?jKbzD)dugkG(>x>%m=tbOv3-K*E-#$WRP_@T?k^`Un~Mmv_QxOAuNt0*+G(8F)1u-B$qxmc1@FyyDFO=N)Thw9>$A@Jq0sZ0e{Mi2hn4y|N literal 0 HcmV?d00001 diff --git a/tests/data/aloha_sim_transfer_cube_scripted/stats.pth b/tests/data/aloha_sim_transfer_cube_scripted/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..71547f092c9780c04aa78d105298b4a0dca41ef1 GIT binary patch literal 3922 zcmbuCeM}Q)9LEo9d8tr95JB;UiCU*ZOJ8V#-T^|b)*+m-b-Jy# zMP}3}&S==wY=6vvQJGVO+Pdgm#h2*RWyv(#WSNPXMl*GZn{M~?3fyxH3GQj0-nI99 ze&65kdHFrhl^X;jF)TV78&JepGUleuv^%@XOq;W6S2%RV3D^=|Sv$fDFo`je!Od3M z?yLb3UNAypWQ<}fDRT`FSZSxrYRfWtXuHELGPx*=$L^>y(NvAw=`z*jh`>mR#2Cf2 zcQY-`)f5o2h)Cyfn(0OCpCS-xj9}VGL6lLzT&=Yp5N#0CAg02A8N>}hWQb@0qc&Tu zPKSdEdEyo=bX8FYk zOx|pR=olKLkYH>GU1@#~q#|ezNMq4)VRSr1(^+%^4JMLc5{uevz+?oKgA5jZC5%pi zXeNtJrNJ~3O!pWQnV6jxH|44`!(PsUBSQg+7tD~@1c*Hac$Kv`Gi+}bv^SfzmqmkY z5=d=m;7TB4k(@9hhlqkjlr&J0AlHTltO9Bl(S(sah~%?K0S&Yyz-?%txqx7i!Z1<< zkzy8^LxU0$lzNOQyg=X<#9N)#J%3H$+9JUQ47c%(REKe9#pek7a~ctYsBVIpO5j z6;BwjWRVr@u+y}If*K9lo;0|OVI5o}7Axdrg>0#=xZuSCh}ys(#*;_8ev~}%ADOp~ zpfpWg8FW;jsb?GU+qo3tdh`B*GspM)>xB>T3$cFR`VFOo@@|@TgE^Ie9$%qK@2MGY zVSSOuVy?$BU9pJc@xyzre6dDIO!3XfGnyL;nh$p1)d^`t)9oMdf;&Ch%PXGY_bYnv z)3=iGJZd+-VY3~tYyAd?`ki^prw^?khs7cKOqa#psz`D^labmk|v z64btSZPB%}xVgETfc>)e%SZUlRZaNSw!MT&n@UV;-b9EWH-(--U!Ql)xjl6M$70C% z%hBaMEg@t!;6@Z+W*9c1qXze_2=3)?w=_QT;!k)#O)|MG-$m81Ms)8w;*HKY@QT zF^!1)Wj(&@>d)Gx4>3aE{S`m?)oA>?<`#VJ$9BBY=?%^QWY33NhOMtc_0M>+^M!hS zUc#nUqT${lZBlX&Z~VNQ7~N4r+@JXfuPposFa2UKVW>+b{;q5!R{zyRTt8SJioY&c zeQVhIxv2iG_Q}5QTe<^BFKLLKXSe(QIa?9%K0DUAZ{4@r@sp4Hm6;a;iKAjpts&*1 z@t>D%3Fy5SwQ&4%qc*ySt*=J)O{=HkrYjNk2$;`RH1&< zcFWn$L1~-gty-XZCxR?c34$Fn1G#1J_?tm@|04aJH0BaWdlUH`wNP;@X!IO+;orno6ekBIFaV?{pBWVlnkOIGtB6) za#Jiy>X|ndj;4Z7a+4!U22oPUC%I`2CH2f}8b?#bC%Fj;C4(rL%O|;M2PO3}h^CrP zauWthwxFbjPjVMMIu+7Iy_(_l!biC)F&&wJJNnT7&JpE?$jDh?VxnMnADa-q>%Dfs jmrSOpN3mCigRiLAgYe>p@1o@fAv`Q&3Cst!XWRY@MY)z4 literal 0 HcmV?d00001 diff --git a/tests/data/pusht/stats.pth b/tests/data/pusht/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..636985fd3116a40c9f712f531347fcd44eee4db5 GIT binary patch literal 3922 zcmbuCO>7%Q6vxMQ^HC>hk~X9%H7NurE;O;%UVnu+A9gWN)7IUR)(tJju~+dHC+@Sy z5e-BNjiOROJw&BMYLr7blnVzWs5n%jCxqw$Dh@~$;=&o!0|%%OFymP#GiyR{ywPYK zdw=i$-psuBW@AyyHiQ}*QN^_bb)W)H;({Zc#L2$%uV*8nHZ*3q=FGEz?D2YnCsSC< zO@V0^%=S1jiYC?M6j)L?mrrH-5=E?K3)Vzl9WQFxbONhWgE)xMa>IfjhYsq>2fNS%VyP* zCk5-Vd|J(`=|fryTcP=Y0xcMJD9~E6P{3(u%Mk5MU543~pgmKvxl^~fi`wkCoT2DL z7&;Z$T|y_5Z$OuTx?m4OAJ);m6tyw*5e(f5>|>}l1&<1-8y;h5kB&Z0(I*)CB!>M8 zJXMUhlbCbk1vNjNq_f;dS4IW4=*mGDAEVybAa-V!&STGbErR{S4Ac)V5PKF3FH(D!s3~f?1c&L3euy%=FJU;Mz)?fM0XW9c7>6D&qY6V$ zV0c-9Q7&*ZSp>-mCmDN+W8-D^G-JmwyrRGvZU#!M1ZNrhD#s?u>}!loVi;E-RV?$+9lwCp9NqVkP)|j+8j_d%o7FJRs#Itp`12l`#&m2f z)QpC?s^D-&LIeLprBb*2&1f(kzWUccS7AA zw?kBaVC>@$HrH=KK|!BPL160+AT&2r9@K}cN8kFfnZ!@W#&ghu0)h`@5K>&80v|?IrRON znIXCd?|*saZq@l0*n0l`wCmg8z3#=}H;$Dc)&EoZ_4?-e>-A?^%@@ddWXtu(ZKbMr zS0+YjgQR2Qs%Jy8UUmBDk<>v>Ls~Mc&6G|=2`wE7IjgQhlI)aZkK~oz0gu}wIsG2l zC;0+iuOvAG9@!(yE-4^81Jui#z1lrH@sN&K)I^xvYtH6Ci znm%LK+PV?un;JCR%xNy$j^O{!&GkbLT@6Evj?QNF*3=&}K= zQ03c$=&y~OS-41DMt}LjE|Nkvh(+t`59&m>rFV^O(?6B(U(lCKa`~IsopAIO72Vg9o4$+2qILAJjM~T_eV#r0 E52~JH)c^nh literal 0 HcmV?d00001 diff --git a/tests/data/xarm_lift_medium/stats.pth b/tests/data/xarm_lift_medium/stats.pth new file mode 100644 index 0000000000000000000000000000000000000000..3ab4e05b9b8e692f112f688b996aaa2974664cbe GIT binary patch literal 3922 zcmbW4ZERCj7{_ndy>{ITIzi?(2P*4ubiHkFUpCeYl~v>_baxC3id|o>wA!tE+S3W@ zgqSRb_(3DYXhyVtG-_h_AehLEl9=#GOe7`*lw=7SKM+iei60=I)6;I}+&X7_l9PMa zo!|3+o^#IgoO6SUu~rZo8-=pgDs%{06hYa+cmzePGcTqB{&wMzuAQiv1Hlri$9N=$ zlIdwM%t4JMgpCSQT4WlGF_g~464r1IB~w{bIHN^#$y7XywCQX*6P~r3pw?muHQ@1C zJTh`bgF1?s0;zNajnf}ZP#-ejF*axj8SyhVlY>U37D3aPA}F=)Y#%N+Du5=PFR6Sh)&j~49>5PRFGJu8Ai6>JGUa0x_; z*o%mRAWn)%2wW<-6MVof@KD5CM1}}5Op)yfMpW=6_(0ttQ)EXG*+~#TMIJ-2ONG($ zK$BBsO9bZNaebg3*p2NO5j;Wc85M(Kcmwv32YFGb-6s(|rNUlaz+Tu#(IA74l~9$U z;|TVvFu?>~gBL-x!2!xX&9I>odyuk+5Im#8v&;+>SP>3W_BnlQnt_X=$?W1OClr*{Vxk{AVhOP)Z|0^CL?eNg9*B zx{z7e!BmAoTfo2lf2fq}mVTN1^-bU9=!V7kQ1{}`pXL^cCo>@jt~7!``hR)iVm%Q9 zetQ+3nBUzb@4T{AzS-F)zjd%rCi*vz-gsec{T4yu^zl@Pl{+8^C*)Em+Nbxvdd-aE zCVlnTTLdqsjf0me*CuO#WtJP!Pk(pim9_QF!Vs%(5BOcx$z%PM_{lr*;Xp^?x3%Zr zEO6`ZsBZnS=GSZ|`d`&FWh&NpR;LfwFUg^AyM0T4cgVw?V?LsvkN(+IvA$HD{^tWV zzJ=qzj12XRj$A*LFX*>?^6~A8^g1v3A`H=6HMtnaB#|Mx$x`cCy;^Ie|0TA2G-`|iIg&OdMU z`PaAbrSIb1bH48{oiFH*zVywVwe?r(&#aco;`>O|^~Yf%GycN|M<&PtN&4zluS;+i ztB#diNeA(5C>cp5UoPB;!pV5RZ>zWpiB6m7lth=);guYcX!A%;x6N*s9B#KmwA(!{ zo8%E)Za4ArT=;Ci1$#((P%#i^mv7GOG2Kf3!8QIKt!LSjw~gaI!-qa)mydNl%pR6x z;a1vudG`eDWh!YF%ZnamGqH=?fbT4$|6U`4N`3u6 vk?1jQTV_eUC$026Pd+kn^EA^Z=Ex_iaJ!h?Id literal 0 HcmV?d00001