From 76a6941e1cfc8b16d52a2c353973cf0a125efad5 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Thu, 5 Dec 2024 17:19:06 +0100 Subject: [PATCH 1/9] Add MMFlood dataset --- docs/api/datamodules.rst | 5 + docs/api/datasets.rst | 4 + docs/api/datasets/geo_datasets.csv | 1 + tests/data/mmflood/activations.json | 1 + .../data/mmflood/activations.tar.000.gz.part | Bin 0 -> 12515 bytes .../data/mmflood/activations.tar.001.gz.part | Bin 0 -> 12515 bytes tests/data/mmflood/data.py | 127 ++++++ tests/datasets/test_mmflood.py | 123 +++++ torchgeo/datamodules/__init__.py | 2 + torchgeo/datamodules/mmflood.py | 101 +++++ torchgeo/datasets/__init__.py | 2 + torchgeo/datasets/mmflood.py | 422 ++++++++++++++++++ 12 files changed, 788 insertions(+) create mode 100644 tests/data/mmflood/activations.json create mode 100644 tests/data/mmflood/activations.tar.000.gz.part create mode 100644 tests/data/mmflood/activations.tar.001.gz.part create mode 100644 tests/data/mmflood/data.py create mode 100644 tests/datasets/test_mmflood.py create mode 100644 torchgeo/datamodules/mmflood.py create mode 100644 torchgeo/datasets/mmflood.py diff --git a/docs/api/datamodules.rst b/docs/api/datamodules.rst index fdcef5450d1..3569bbfef8c 100644 --- a/docs/api/datamodules.rst +++ b/docs/api/datamodules.rst @@ -26,6 +26,11 @@ L8 Biome .. autoclass:: L8BiomeDataModule +MMFlood +^^^^^^^^ + +.. autoclass:: MMFloodDataModule + NAIP ^^^^ diff --git a/docs/api/datasets.rst b/docs/api/datasets.rst index b8f2137c920..d0615e1a944 100644 --- a/docs/api/datasets.rst +++ b/docs/api/datasets.rst @@ -142,6 +142,10 @@ Landsat .. autoclass:: Landsat2 .. autoclass:: Landsat1 +MMFlood +^^^^^^^ +.. autoclass:: MMFlood + NAIP ^^^^ diff --git a/docs/api/datasets/geo_datasets.csv b/docs/api/datasets/geo_datasets.csv index 4bb5788609e..6f8d60bbf2e 100644 --- a/docs/api/datasets/geo_datasets.csv +++ b/docs/api/datasets/geo_datasets.csv @@ -20,6 +20,7 @@ Dataset,Type,Source,License,Size (px),Resolution (m) `L8 Biome`_,"Imagery, Masks",Landsat,"CC0-1.0","8,900x8,900","15, 30" `LandCover.ai Geo`_,"Imagery, Masks",Aerial,"CC-BY-NC-SA-4.0","4,200--9,500",0.25--0.5 `Landsat`_,Imagery,Landsat,"public domain","8,900x8,900",30 +`MMFlood`_,"Imagery,DEM,Masks","Sentinel, MapZen/TileZen, OpenStreetMap",CC-BY-4.0,"2,147x2,313",20 `NAIP`_,Imagery,Aerial,"public domain","6,100x7,600",0.3--2 `NCCM`_,Masks,Sentinel-2,"CC-BY-4.0",-,10 `NLCD`_,Masks,Landsat,"public domain",-,30 diff --git a/tests/data/mmflood/activations.json b/tests/data/mmflood/activations.json new file mode 100644 index 00000000000..f9f2e3e901a --- /dev/null +++ b/tests/data/mmflood/activations.json @@ -0,0 +1 @@ +{"EMSR000": {"title": "Test flood", "type": "Flood", "country": "N/A", "start": "2014-11-06T17:57:00", "end": "2015-01-29T12:47:04", "lat": 45.82427031690563, "lon": 14.484407562009336, "subset": "train", "delineations": ["EMSR000_00"]}, "EMSR001": {"title": "Test flood", "type": "Flood", "country": "N/A", "start": "2014-11-06T17:57:00", "end": "2015-01-29T12:47:04", "lat": 45.82427031690563, "lon": 14.484407562009336, "subset": "train", "delineations": ["EMSR001_00"]}, "EMSR003": {"title": "Test flood", "type": "Flood", "country": "N/A", "start": "2014-11-06T17:57:00", "end": "2015-01-29T12:47:04", "lat": 45.82427031690563, "lon": 14.484407562009336, "subset": "val", "delineations": ["EMSR003_00"]}, "EMSR004": {"title": "Test flood", "type": "Flood", "country": "N/A", "start": "2014-11-06T17:57:00", "end": "2015-01-29T12:47:04", "lat": 45.82427031690563, "lon": 14.484407562009336, "subset": "test", "delineations": ["EMSR004_00"]}} \ No newline at end of file diff --git a/tests/data/mmflood/activations.tar.000.gz.part b/tests/data/mmflood/activations.tar.000.gz.part new file mode 100644 index 0000000000000000000000000000000000000000..2f34189f45603ddb1b4c368042fd565342e23d40 GIT binary patch literal 12515 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

X_jjlmJU z0joEt=qtt5X~@KUmgQk7q_*n@vGV6e3oHzcv1sBC^si%)6qIB7CVwNtq0qjHp$h#O zlUk1s=Qb4X`UBHkCwZSrMO3z5cHeeuFiaQpe%$4~om+Xy z+!(Y=aXxZAScJ{Vyv`xzE+vTh&=PH&`-NnD;<25Q4B=#(0ZVAgE~9!G!^X!~{BcOc zb9NyaHrD-R-BU0%t?fY#Lq&i4AjSm_KbhBhwE7^|$imyA<`Ln~TnN-67t3EJV8N`P zd`g>UEuu60b>E%!54BBa(T5F#c-P4QCmW0XV?~eD->ilxY^@H%B=D2xCSFEXea-(e`K}FI&`rf&hqdT6#>!g)ci_v&KPZ67MYad zj7;2`NyG`m4sHc$_iA`}|5OnR^C~h=TbfvRe#THn6vn+#Hg61+lFne!k9^Z(l4$&s zj$?2@a~Y?V4|{@-pemq3TcWJkBX8u@Gc$*%x9fXS-NU=mW3098O{Q#>f#6>bC7x&> zXMBmCq52U<>YDOLi$@jb^VQHzZr0Q*ev>mlN>qMz8a75g5|Dzck^ZXLphRnapakg& zliP0buv{71exKOHew}w38qneSZ(}7}rYDkPxyn^95-)t@R;}KP&aD0%q#a9!x8R^D_m$YueVJtAT=8{4N>{(xS6_AE%{Zz5m5jzyA)!c9ebGu$A zpjm~a7i3If!0s@-Pe7Ik2`YjVf5Zj%s6rt8pAHZ&u*{pmJS6cGf(71r3V=l*mJ7TI z`EggH1OJtmKi+D_GJsg4@%B{hhpYl`-Br1Dwml#IE1N+ePXIe%hwdOhYkq>UM@6c) zvFai=eeM=XP)Rx)C8Dr1xa$-&c{l(Xv)UPn-pmhoSs_gLfgq}B+d*~T$ z?KVXAz(?beDp{b%tDJyj2KfV2f?OIxSb zWqeeqOWIzURLkM(kQi=5Es9F$@U3tT(#lL^;qyFWc3HcS`{fzYVC(h7YR=x~a~GTp z_2ZG2AfVeVGXjQp^5uR}n}xEDU}r|&_`;&*bnpAM^$&TxEtO<{{h0Y2HRkoz2%-Cn zp$P&Z+gX4BT$||tAHAAo8plxzy!5FSse~Sz*fgF-u7WJa>c6hK`X)lHBFS5B zU7qk6X2Pk%z*w5T(c`nCJ55{QAJ_(n61To<%YJ7y+hD>sjVBZ-94E?UM8~ZFk7FHF zthC8JLH2U}5$V;eAvRgcz?E9O2G-ReIabEqR}G1EPJ*v#GOc&UGc1-bmdf?C89l&H zviz^^`Cf?G~79-p!iB2x%GN`_pD!b3e*$ghM^JS8X2+IA}w_MEd!`2;UfvcaX zzUjexBPRp)Zg}|cghK}z=eBy@T6{FNqo98gFVzPY(Sp^J8d}=f%o4EL*IB4_?-be| zKaTW^a}b@?gtG*G__k8wj8(5d!c&fgpEx3%HnfdAgjt_5xc0?Ht~=-LQ6CwfjLogS zF0ozIZ_C{G%fiMI@}a{Mapvnz1>w7Jkj|R@^qqjeo7`BLy|^{mG^p)eF5se_M(H(! z^^pJfM;$;snlHvvr8N&X6{kgJ*+htSTpe&))?fDsjK9c12rFWx81XREFDF`-$Z{4x zl(v0o!rV}Rznyqg^As#5pIDM$@pxeygJ{h|03n}g=RKcU7m%JE$dtS`1YG)O9}w|B zgFFa{{f4B}U5Zc>NuAb~a9CylcVw9gw+tNOag{?bE20WBd5>I_{lmQh8>EKiy9|o?%U-*S2I=SWnJoK6PH< z_1!#`Y=VdWB2e2DmKOd<^Z#v>(?wu+66V-h!?g7{zw6q7gL31z&*J6BmtI7v+;nj; zN=|M6jXmIKVrG=TsPD?e+Zj}#sJ)YopvHnQIv1hKH?GJZQ3Cnwb9HG=ch z9)Jei@DNvx;E?Ow+4J2MdqrG(joz)WH3{MzfJq&#jb7imu=XQW9Djw zk7MDDf>9BoT=nDy)&j)0VKS2U2c=N5*ks5PyOt{-WXyHMJiolkH^L$983E z=(3^4_HZFF3v1x4ctg!npc5H1pu)WR4=YIg#mTa3LNOPkuNm~aT$^YZ)kDsSdm@m! z-^41#W`;2(;4DJvv5LAy(V_66LISKx??TaZIC)IAVB}6i#RB74Fj_v?|lxZ~)4dH;fp5{#_mD`wFF7Wau zC;tK~3CrzMsXyc-=6||Tb0{<&%|{O4a*ZUCz_}k6VOi|9^~Jh#yJvgPB*;a26uwM| zZ`$g7#hU5PZtWo6B3Ny3?sMalSrkjjev7-Hh(vovTD?%kCkAdT*nYV^+aDKG;XC&5X)3y>i%1y4VqYcYqn<(HsE}^c8pPEPCI)qGL z=u*2IjtKeC&|O7MT-__`%Ldt^qm1EP3Zv^AgG!Ni@vcvX;8G1-1br^)6UxFNI9rYp zdq_lcWi@7)*vHAM9O}Zri#p5om0$n#xv;O96fp{aNZjrGc4J%J)rKm}&J8g6;Ky`J zQv(g3J2mWZgLS6NgWE3<{6wJYS?R_`Q(-r>b!lU~7$EEEOB~dziBwY*U!A@aS%IX` zw;xJiPtaN-UWB7CH9vxnLyiot{>4U4{#tS5!tT(6fT7G`bn7B9E^lr)_W4A(=uWz_ z273=n@G$D2UuC}&UNR-0Ul&Hl$N&iM4X|)Dy5((qo-^~Gv^lh=MK!?EO|MuB0rzOv zJA-LT02lBv6|{>wE;3~ZF54n0nbzj7BOh6<+KOj>%g#PWh2KE)OCxg zk%>suf75)SN*G9MXJaVcAJ>EYwFGtBMBI@{QtAF{*(FvJj%%}`ixfQrp&qSHYWz8K zMr58mKK&u&v%E`4!U@*I~0>u5GBjDx4tba%jC7ERbuZ4-XF_M_1HK)XtIj%$c)!Rv7z_kA+T&AIwoeJC5SloK>%7 zf0G-tP@C8@mZYEf)D2?%5Z%f~o*Z1|@Sg)pw_Tz_$wZcJ$(?FK&P-9lx zhnh&Ir3k*CDEKV{Q8 z$VZ*fXTO(1^L>2oa-RNNpO*$s=^TolQtNc*ko27*@wmobjDKfJ^d##-5Q%P@oAPZx zZRz+WXfJJ~z3$6b&PSiL5BT+&cHe{k8Thhu?+^|tev4E7U>brzVqSV)BUVn2dq@oM zMiQHjb$!_IuP~Q}K3BFhIk`igp46iq@ZxpQ+Xai+l`8&_N;fv*0hALlB_U#j>lT3G%ZW2Q2hg(Y>{fhek30&MZ zL#PsJHUo9!saWV1vk%@wCl*y0G>^A4TjaXdxK}goy6-#Yb>z(so8#ESmY(i~r4BlHL6i2tjeCM&pre%q2tI@1=1OfAOJ za1})bJ#+sQhtvL{cEpRMNi@DRdIEi65Lb&fal>9HF7AF+!yvAj!6IrStg7An#m=d8 zGX?=s++?rKJKFaSU4ao#W$W;3@;Z|!Oq5?bD)Ha|?Hm0PFscFVwG0CUt)Ftg$#cmP z?NEICCeACb$QRWFZ$i4U4m@}i*!w8?8ePF^)+eEsIYd1C2oE%Euu;fcs!`Nk-$-Fw z;F}$~{zCt>{ii<>SKwQPOm$3tl}i(52@;cuCy-ik4Py-NyOJ~p1z#Ba+ET(q}s%!P5ijeSUjgid<6rT^}!Ci>U-L>xb z{Rh9cnmI<<80+i%yC5V`G{NZca{aMKH75RA%6IOrZ-YRRh_iuE<RKin0l;SG!aSR29Vg1sByWijj!wHHaS5x)bD3CHCN z&N)z?n_f!BFqv?YFGfJ%Bi^02Aost?=&zaS8nc$blwGAo-n5g+GWkct(!szcBX@|p zAQ_!~GD(c}*4A*&K^w+hc$q|p!r(A4C@fMl<6bnrYjcah9&Eg6mHZEbw?Tqsb2|q% zNZk;e#LMNBO%ua1m@?J(*jX=W$;riVxZ^(?%{2Ubd>+e z5so|+6!yT$9>a3bENZbFbvC0KMRVREJ~rHXGoS~v=;d5rTsA$`pD(M|9cR-kCKvd^ zQOOVnQZPDqQZx6aa=IxSUj^)`Q9!_lZQ2``wV8g~&R7=*&azF%W3BtWg4zXt zLks3TC#W$-k>t2(6%h&@&GOLC%@=^sCH`?yI$z`r-|9Z#>v?wuxht_U^z%4voLSwr zaPGItoaEiFg^q#Kc7qfgD&EIgc$1@{*8N$%?YN`(K%&7X)^}C80=b$yLEw7Fr(fU0mIGhYZB}FD2i|Vq!>DL> zy1qX=|K3Y%GhSPuF2@yk`n}1}yyh}ZN3V7k`5!yJ_Kjty^&qbE3^#BVZNpE7j)!u~Ve7DON&CRf?JvMmryj0BW0{7ip{cQ7XVM-(>#O1DMy zONw*$0kDv|v!sgWiZH?QZ)a}?Gbx8ElFetp#3bRZ>PBE^sD%et`|Jet`bVL2lSXz4wo9 z@#B1{VLTSse7K8eZt4&3FqcR;Uti)S+&euK*oZMH}=mXzKh%Nwej8U?k)YWaq5kRkp8?;{(x8iAdqJOAPy3g()9T}qYFvOfqdrPemuW_Bs}sEj;l`~p?CGStz zSlNixUdX!&c%!@`aYQ2LO6;Gfvq>^!(_U}chN@nCYl&Fi%?sK(JGmTbyIg)roC~TQ zuMs%`@=qu{9`~gH%gn6@aLl2nYCBZSs(E9MjCVEGVyX80CsmX0UQ$u^-%F1Q3g)GADkqj^3D)^NYSv5NhXvhC*8j91UAMb}~VrhamL*D9*exMQl z>f!&WXY>={<}4yi4!KavQbaWS$qalHL;8n^ zKOp?vbW+ADC)?kT@cuf+^hFdSe@PVM;6z?j$XY_HSC-Q~GU237n1J@T`(6?oSyiVC zq8}~M>cp8;A<*0_M&??(BN)<~iS;`P1F5@L2qT!Vrucb(w`M~z8 zX|?@0;rf+?er1j=8jeB?k9HH_OlU^jp^l{isk(`rYhQ}fn)3pFY1|Rp*C03*=)Pz2 zeqB{IDwbs=j#(*B*3=U%ppbR9S7xPYg865uIG>RD4lz-63$Nhb#6OePd8$~B2EjkT z${-21dp$Ix^(O^{QwjpXdNZhiJUZ0;k5Wfc^bwZ~Nq8|Bhe*B221W6UIFXYL)6+n`nze=Uvv_6rV^wEGb`p}A7_?b%cuv}szzz6aoaw^ zcY7jS3td>F@k5p^F3dgL>GKObA>`+o;Yj2vxNlTB1}2GvZP=Vj+kI*9&+a96Dt$?C z6~49gXbw$8me2ptPeQMU{^6->mMtU#M^e7T&LIL023$C&$~b%k=Y4A;{rpvC+qg8c zXCn7vXuZ1CR|?#_TK6B?8Mrt&h>ytm+MU7dgjdf{<9tL$!g z)9l(oK5o%#H!&PZelTTPWiwjuY=#R_s&=;1(Gx7$g*I>pUa*=Ey&_L_T3=ICddAQj zYWub=QFG%)|NDm2)#H(b8+*C;-?~6`Wk6Mqs4%`i;YCxg~yw`JKgL@C;>I zJ7YXEY-gGN`E5@_u)}p;#?-M@R0-q-9crg^Ja={@x~9rWWuEb{R3chMzi?PICsZ|B zXJ+I>xotqn+;D-^?PdaHgIc$*PtUWdz}R56?t%WG?>v*JpI3 zZN#GZ{ZG(=5GF^2`D$CarvLNT9OO>eHm~4IMA_}@1aBaZ>NX+yk|A0zi{R5pPLRNO{(Azx}M5@A++0kfRosN>bs=^P1q`6;X2{Vk~Qs7#kq#5)BE*wJLxC4svLnYmf^IWj5F3+SphLZyMc87SyNWtMJvT zsz8j|adz^TX~H$|71C_pVARF7^LW))qe0voaKp3CZgiwgecTS`m1urB_hBpez+6A8 zO4#Bu948u2^^C_Dfh&wFNYUVw_p6tWj^nf_{9Ch`y^wR?y|s38i!q4e8@but6M?qv zExThq%x%)!awahdMgW~!+LL{hde+lyqau(8u4JdUqw9)QdoRy%U3f!*mbyjYU%9waB{)#dSxy8sO0ozXQf_woqRO0#>#= zz2DlSx}=d}!RZ?WRbgyeL+LrwubvJ>!*ylG!NBY|CaYui+fRzf`zggbR+23?wRFcv zers&r2eW69mt>{C=o)~U)AyifVF43OSCyymmpk4fi?}_cMp5;JlWz-d#nncFd#zMD z**$AGz>TJTjRC?J2LYz->1?Q0qCUiRywN%(1GF77yvoL-_>5L43^`#go1~w$ECEXd zHG7kIOxH-XN!-(yGy;nhdvi7($Hb#(1dPae<{fa8o&_^Ut%qLz!(^LVo$y{*Dx@97 z(RMc8_1={{=durm;77D3W!j(*o8Qy;#6wSM@ None: + MAX_VALUE = 1000.0 + MIN_VALUE = 0.0 + RANGE = MAX_VALUE - MIN_VALUE + FOLDERS = ['s1_raw', 'DEM', 'mask'] + profile = { + 'driver': 'GTiff', + 'dtype': 'float32', + 'nodata': None, + 'crs': CRS.from_epsg(4326), + 'transform': Affine( + 0.0001287974837883981, + 0.0, + 14.438064999669106, + 0.0, + -8.989523639880024e-05, + 45.71617928533084, + ), + 'blockysize': 1, + 'tiled': False, + 'interleave': 'pixel', + 'height': height, + 'width': width, + } + data = { + 's1_raw': np.random.rand(2, height, width).astype(np.float32) * RANGE + - MIN_VALUE, + 'DEM': np.random.rand(1, height, width).astype(np.float32) * RANGE - MIN_VALUE, + 'mask': np.random.randint(low=0, high=2, size=(1, height, width)).astype( + np.uint8 + ), + } + + os.makedirs(os.path.join(path, 'hydro'), exist_ok=True) + + for folder in FOLDERS: + folder_path = os.path.join(path, folder) + os.makedirs(folder_path, exist_ok=True) + filepath = os.path.join(folder_path, filename) + profile2 = profile.copy() + profile2['count'] = 2 if folder == 's1_raw' else 1 + with rasterio.open(filepath, mode='w', **profile2) as src: + src.write(data[folder]) + + return + + +def generate_tar_gz(src: str, dst: str) -> None: + with tarfile.open(dst, 'w:gz') as tar: + tar.add(src, arcname=src) + return + + +def split_tar(path: str, dst: str, nparts: int) -> None: + fstats = os.stat(path) + size = fstats.st_size + chunk = size // nparts + + with open(path, 'rb') as fp: + for idx in range(nparts): + part_path = os.path.join(dst, f'activations.tar.{idx:03}.gz.part') + + bytes_to_write = chunk if idx < nparts - 1 else size - fp.tell() + with open(part_path, 'wb') as dst_fp: + dst_fp.write(fp.read(bytes_to_write)) + + return + + +def generate_folders_and_metadata(datapath: str, metadatapath: str) -> None: + folders_splits = [ + ('EMSR000', 'train'), + ('EMSR001', 'train'), + ('EMSR003', 'val'), + ('EMSR004', 'test'), + ] + num_files = {'EMSR000': 3, 'EMSR001': 2, 'EMSR003': 2, 'EMSR004': 1} + metadata = {} + for folder, split in folders_splits: + data = {} + data['title'] = 'Test flood' + data['type'] = 'Flood' + data['country'] = 'N/A' + data['start'] = '2014-11-06T17:57:00' + data['end'] = '2015-01-29T12:47:04' + data['lat'] = 45.82427031690563 + data['lon'] = 14.484407562009336 + data['subset'] = split + data['delineations'] = [f'{folder}_00'] + + dst_folder = os.path.join(datapath, f'{folder}-0') + for idx in range(num_files[folder]): + generate_data( + dst_folder, filename=f'{folder}-{idx}.tif', height=16, width=16 + ) + + metadata[folder] = data + + generate_tar_gz(src='activations', dst='activations.tar.gz') + split_tar(path='activations.tar.gz', dst='.', nparts=2) + os.remove('activations.tar.gz') + shutil.rmtree('activations') + with open(os.path.join(metadatapath, 'activations.json'), 'w') as fp: + json.dump(metadata, fp) + + return + + +if __name__ == '__main__': + datapath = os.path.join(os.getcwd(), 'activations') + metadatapath = os.getcwd() + + generate_folders_and_metadata(datapath, metadatapath) diff --git a/tests/datasets/test_mmflood.py b/tests/datasets/test_mmflood.py new file mode 100644 index 00000000000..3b004a194f8 --- /dev/null +++ b/tests/datasets/test_mmflood.py @@ -0,0 +1,123 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +from itertools import product +from pathlib import Path + +import matplotlib.pyplot as plt +import pytest +import torch +import torch.nn as nn +from _pytest.fixtures import SubRequest +from pytest import MonkeyPatch +from rasterio.crs import CRS + +from torchgeo.datasets import ( + BoundingBox, + DatasetNotFoundError, + IntersectionDataset, + MMFlood, + UnionDataset, +) + + +class TestMMFlood: + @pytest.fixture(params=product([True, False], ['train', 'val', 'test'])) + def dataset( + self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest + ) -> MMFlood: + dataset_root = os.path.join('tests', 'data', 'mmflood/') + url = os.path.join(dataset_root) + + monkeypatch.setattr(MMFlood, 'url', url) + monkeypatch.setattr(MMFlood, '_nparts', 2) + + include_dem, split = request.param + root = tmp_path + return MMFlood( + root, + split=split, + include_dem=include_dem, + transforms=nn.Identity(), + download=True, + checksum=True, + ) + + def test_getitem(self, dataset: MMFlood) -> None: + x = dataset[dataset.bounds] + assert isinstance(x, dict) + assert isinstance(x['crs'], CRS) + assert isinstance(x['image'], torch.Tensor) + assert isinstance(x['mask'], torch.Tensor) + + # If DEM is included, check if 3 channels are present, 2 otherwise + if dataset.include_dem: + assert x['image'].size(0) == 3 + else: + assert x['image'].size(0) == 2 + return + + def test_len(self, dataset: MMFlood) -> None: + if dataset.split == 'train': + assert len(dataset) == 5 + elif dataset.split == 'val': + assert len(dataset) == 2 + else: + assert len(dataset) == 1 + + def test_and(self, dataset: MMFlood) -> None: + ds = dataset & dataset + assert isinstance(ds, IntersectionDataset) + + def test_or(self, dataset: MMFlood) -> None: + ds = dataset | dataset + assert isinstance(ds, UnionDataset) + + def test_already_downloaded(self, dataset: MMFlood) -> None: + MMFlood(root=dataset.root) + + def test_not_downloaded(self, tmp_path: Path) -> None: + with pytest.raises(DatasetNotFoundError, match='Dataset not found'): + MMFlood(tmp_path) + + def test_plot(self, dataset: MMFlood) -> None: + x = dataset[dataset.bounds] + dataset.plot(x, suptitle='Test') + plt.close() + + def test_plot_prediction(self, dataset: MMFlood) -> None: + x = dataset[dataset.bounds] + x['prediction'] = x['mask'].clone() + dataset.plot(x, suptitle='Prediction') + plt.close() + + def test_invalid_query(self, dataset: MMFlood) -> None: + query = BoundingBox(0, 0, 0, 0, 0, 0) + with pytest.raises( + IndexError, match='query: .* not found in index with bounds:' + ): + dataset[query] + + def test_check_folders(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + class MockMMFlood(MMFlood): + def _load_folders( + self, check_folders: bool = False + ) -> list[dict[str, str]]: + return super()._load_folders(check_folders=False) + + dataset_root = os.path.join('tests', 'data', 'mmflood/') + url = os.path.join(dataset_root) + + monkeypatch.setattr(MMFlood, 'url', url) + monkeypatch.setattr(MMFlood, '_nparts', 2) + + _ = MockMMFlood( + tmp_path, + split='train', + include_dem=True, + transforms=nn.Identity(), + download=True, + checksum=True, + ) + return diff --git a/torchgeo/datamodules/__init__.py b/torchgeo/datamodules/__init__.py index 6dd7231e3df..be49fa97463 100644 --- a/torchgeo/datamodules/__init__.py +++ b/torchgeo/datamodules/__init__.py @@ -29,6 +29,7 @@ from .landcoverai import LandCoverAI100DataModule, LandCoverAIDataModule from .levircd import LEVIRCDDataModule, LEVIRCDPlusDataModule from .loveda import LoveDADataModule +from .mmflood import MMFloodDataModule from .naip import NAIPChesapeakeDataModule from .nasa_marine_debris import NASAMarineDebrisDataModule from .oscd import OSCDDataModule @@ -87,6 +88,7 @@ 'LandCoverAI100DataModule', 'LandCoverAIDataModule', 'LoveDADataModule', + 'MMFloodDataModule', 'MisconfigurationException', 'NAIPChesapeakeDataModule', 'NASAMarineDebrisDataModule', diff --git a/torchgeo/datamodules/mmflood.py b/torchgeo/datamodules/mmflood.py new file mode 100644 index 00000000000..e669f3c19db --- /dev/null +++ b/torchgeo/datamodules/mmflood.py @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""MMFlood datamodule.""" + +from typing import Any, Literal + +import kornia.augmentation as K +import torch +from kornia.constants import DataKey, Resample + +from ..datasets import MMFlood +from ..samplers import GridGeoSampler, RandomBatchGeoSampler +from ..samplers.utils import _to_tuple +from .geo import GeoDataModule + + +class MMFloodDataModule(GeoDataModule): + """LightningDataModule implementation for the MMFlood dataset.""" + + # Computed over train set + mean = torch.tensor([0.1785585, 0.03574104, 168.45529]) + median = torch.tensor([0.116051525, 0.025692634, 86.0]) + std = torch.tensor([2.405442, 0.22719479, 242.74359]) + + def __init__( + self, + batch_size: int = 32, + patch_size: int | tuple[int, int] = 512, + length: int | None = None, + num_workers: int = 0, + normalization: Literal['mean', 'median'] = 'median', + **kwargs: Any, + ) -> None: + """Initialize a new MMFloodDataModule instance. + + Args: + batch_size: Size of each mini-batch. + patch_size: Size of each patch, either ``size`` or ``(height, width)``. + length: Length of each training epoch. + num_workers: Number of workers for parallel data loading. + normalization: Either 'mean' or 'median', used to normalize the dataset + **kwargs: Additional keyword arguments passed to + :class:`~torchgeo.datasets.MMFlood`. + """ + super().__init__( + MMFlood, + batch_size=batch_size, + patch_size=patch_size, + length=length, + num_workers=num_workers, + **kwargs, + ) + assert ( + normalization in {'mean', 'median'} + ), f'Invalid normalization parameter specified {normalization}, must be either "mean" or "median".' + avg = self.mean if normalization == 'mean' else self.median + # Using median for normalization for better stability, + # as stated by the original authors + self.train_aug = K.AugmentationSequential( + K.Normalize(avg, self.std), + K.RandomResizedCrop(_to_tuple(self.patch_size), p=0.8, scale=(0.5, 1.0)), + K.RandomHorizontalFlip(p=0.5), + K.RandomVerticalFlip(p=0.5), + K.RandomRotation90((0, 3), p=0.5), + K.RandomElasticTransform(sigma=(50, 50)), + keepdim=True, + data_keys=None, + extra_args={ + DataKey.MASK: {'resample': Resample.NEAREST, 'align_corners': None} + }, + ) + + self.aug = K.AugmentationSequential( + K.Normalize(avg, self.std), keepdim=True, data_keys=None + ) + + return + + def setup(self, stage: str) -> None: + """Set up datasets. + + Args: + stage: Either 'fit', 'validate', 'test', 'predict'. + """ + if stage in ['fit']: + self.train_dataset = MMFlood(**self.kwargs, split='train') + self.train_batch_sampler = RandomBatchGeoSampler( + self.train_dataset, self.patch_size, self.batch_size, self.length + ) + if stage in ['fit', 'validate']: + self.val_dataset = MMFlood(**self.kwargs, split='val') + self.val_sampler = GridGeoSampler( + self.val_dataset, self.patch_size, self.patch_size + ) + if stage in ['test']: + self.test_dataset = MMFlood(**self.kwargs, split='test') + self.test_sampler = GridGeoSampler( + self.test_dataset, self.patch_size, self.patch_size + ) + return diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index f55ef3af22c..7b1ee3a7d6c 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -87,6 +87,7 @@ from .mapinwild import MapInWild from .millionaid import MillionAID from .mmearth import MMEarth +from .mmflood import MMFlood from .naip import NAIP from .nasa_marine_debris import NASAMarineDebris from .nccm import NCCM @@ -243,6 +244,7 @@ 'Landsat9', 'LoveDA', 'MMEarth', + 'MMFlood', 'MapInWild', 'MillionAID', 'NASAMarineDebris', diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py new file mode 100644 index 00000000000..2e6fd747ba1 --- /dev/null +++ b/torchgeo/datasets/mmflood.py @@ -0,0 +1,422 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""MMFlood dataset.""" + +import os +import pathlib +from collections.abc import Callable +from glob import glob +from typing import ClassVar, Literal, cast + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import torch +from matplotlib.figure import Figure +from rasterio.crs import CRS +from torch import Tensor + +from .errors import DatasetNotFoundError +from .geo import RasterDataset +from .utils import BoundingBox, Path, download_url, extract_archive + + +class MMFlood(RasterDataset): + """MMFlood dataset. + + `MMFlood `__ dataset is a multimodal flood delineation dataset. + + Dataset features: + + * 1,748 Sentinel-1 acquisitions + * multimodal dataset + * 95 flood events from 42 different countries + * hydrography maps (not available for all Sentinel-1 acquisitions) + * flood delineation maps (ground truth) is obtained from Copernicus EMS + + Dataset classes: + + 0. no flood + 1. flood + + If you use this dataset in your research, please cite the following paper: + + * https://doi.org/10.1109/ACCESS.2022.3205419 + """ + + url = 'https://huggingface.co/datasets/links-ads/mmflood/resolve/24ca097306c9e50ad0711903c11e1ba13ea1bedc/' + _name = 'mmflood' + _categories: ClassVar[dict[int, str]] = {0: 'background', 1: 'flood'} + _palette: ClassVar[dict[int, tuple[int, int, int]]] = { + 0: (0, 0, 0), + 1: (255, 255, 255), + 255: (255, 0, 255), + } + _ignore_index = 255 + _nparts = 11 + _mean = (0.1785585, 0.03574104, 168.45529) + _median = (0.116051525, 0.025692634, 86.0) + _std = (2.405442, 0.22719479, 242.74359) + + metadata: ClassVar[dict[str, str]] = { + 'part_file': 'activations.tar.{part}.gz.part', + 'filename': 'activations.tar.gz', + 'directory': 'activations', + 'metadata_file': 'activations.json', + } + _splits: ClassVar[set[str]] = {'train', 'val', 'test'} + _md5: ClassVar[dict[str, str]] = { + 'activations.json': 'de33a3ac7e55a0051ada21cbdfbb4745', + 'activations.tar.gz': '3cd4c4fe7506aa40263f74639d85ccce', + 'activations.tar.000.gz.part': 'a8424653edca6e79999831bdda53d4dc', + 'activations.tar.001.gz.part': '517def8760d3ce86885c7600c77a1d6c', + 'activations.tar.002.gz.part': '6797b97121f5b98ff58fde7491f584b2', + 'activations.tar.003.gz.part': 'e69d2a6b1746ef869d1da4d22018a71a', + 'activations.tar.004.gz.part': '0ccf7ea69ea6c0e88db1b1015ec3361e', + 'activations.tar.005.gz.part': '8ef6765afe20f254b1e752d7a2742fda', + 'activations.tar.006.gz.part': '3f330a44b66511b7a95f4a555f8b793a', + 'activations.tar.007.gz.part': '1d2046b5f3c473c3681a05dc94b29b86', + 'activations.tar.008.gz.part': 'f386b5acf78f8ae34592404c6c7ec43c', + 'activations.tar.009.gz.part': 'dd5317a3c0d33de815beadb9850baa38', + 'activations.tar.010.gz.part': '5a14a7e3f916c5dcf288c2ca88daf4d0', + } + + def __init__( + self, + root: Path = 'data', + crs: CRS | None = None, + split: str = 'train', + include_dem: bool = False, + transforms: Callable[[dict[str, Tensor]], dict[str, Tensor]] | None = None, + download: bool = False, + checksum: bool = False, + cache: bool = False, + ) -> None: + """Initialize a new MMFlood dataset instance. + + Args: + root: root directory where dataset can be found + crs: coordinate reference system to be used + split: train/val/test split to load + include_dem: If True, DEM data is concatenated after Sentinel-1 bands. + transforms: a function/transform that takes input sample and its target as + entry and returns a transformed version + download: if True, download dataset and store it in the root directory + checksum: if True, check the MD5 of the downloaded files (may be slow) + cache: if True, cache file handle to speed up repeated sampling + + Raises: + DatasetNotFoundError: If dataset is not found and *download* is False. + + """ + assert split in self._splits + + self.root = root + self.split = split + self.include_dem = include_dem + self.transforms = transforms + self.download = download + self.checksum = checksum + # Verify integrity of the dataset, initializing + # self.image_files, self.label_files, self.dem_files attributes + self._verify() + self.metadata_df = self._load_metadata() + self.folders = self._load_folders(check_folders=True) + paths = [x['s1_raw'] for x in self.folders] + + # Build the index + super().__init__(paths=paths, crs=crs, transforms=transforms, cache=cache) + return + + def _merge_tar_files(self) -> None: + """Merge part tar gz files.""" + dst_filename = self.metadata['filename'] + dst_path = os.path.join(self.root, dst_filename) + + print('Merging separate part files...') + with open(dst_path, 'wb') as dst_fp: + for idx in range(self._nparts): + part_filename = f'activations.tar.{idx:03}.gz.part' + part_path = os.path.join(self.root, part_filename) + print(f'Processing file {part_path!s}') + + with open(part_path, 'rb') as part_fp: + dst_fp.write(part_fp.read()) + return + + def __getitem__(self, query: BoundingBox) -> dict[str, Tensor]: + """Retrieve image/mask and metadata indexed by query. + + Args: + query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index + + Returns: + sample containing image, mask and metadata at that index + + Raises: + IndexError: if query is not found in the index + """ + hits = self.index.intersection(tuple(query), objects=True) + indexes = cast(list[int], [hit.id for hit in hits]) + + if not indexes: + raise IndexError( + f'query: {query} not found in index with bounds: {self.bounds}' + ) + + image = self._load_image(indexes, query) + mask = self._load_target(indexes, query) + + sample = {'image': image, 'mask': mask, 'crs': self.crs, 'bounds': query} + + if self.transforms is not None: + sample = self.transforms(sample) + + return sample + + def __len__(self) -> int: + """Return the number of data points in the dataset. + + Returns: + length of the dataset + """ + return len(self.folders) + + def _load_metadata(self) -> pd.DataFrame: + """Load metadata. + + Returns: + dataframe containing metadata + """ + df = pd.read_json( + os.path.join(self.root, self.metadata['metadata_file']) + ).transpose() + return df + + def _load_folders(self, check_folders: bool = False) -> list[dict[str, str]]: + """Load folder paths. + + Args: + check_folders: if True, verify pairings of all s1, dem and mask data across all the folders + + Returns: + list of dicts of s1, dem and masks folder paths + """ + # initialize tif file lists containing masks, DEM and S1_raw data + folders = self.metadata_df[ + self.metadata_df['subset'] == self.split + ].index.tolist() + + image_files = [] + mask_files = [] + dem_files = [] + for f in folders: + path = os.path.join(self.root, self.metadata['directory'], f'{f}-*') + image_files += glob(os.path.join(path, 's1_raw', '*.tif')) + mask_files += glob(os.path.join(path, 'mask', '*.tif')) + dem_files += glob(os.path.join(path, 'DEM', '*.tif')) + + image_files = sorted(image_files) + mask_files = sorted(mask_files) + dem_files = sorted(dem_files) + + # Verify image, dem and mask lengths + assert ( + len(image_files) > 0 + ), f'No images found, is the given path correct? ({self.root!s})' + assert ( + len(image_files) == len(mask_files) + ), f'Length mismatch between tiles and masks: {len(image_files)} != {len(mask_files)}' + assert len(image_files) == len( + dem_files + ), 'Length mismatch between tiles and DEMs' + + res_folders = [ + {'s1_raw': img_path, 'mask': mask_path, 'dem': dem_path} + for img_path, mask_path, dem_path in zip(image_files, mask_files, dem_files) + ] + + if not check_folders: + return res_folders + + # Verify image, dem and mask pairings + for image, mask, dem in zip(image_files, mask_files, dem_files): + image_tile = pathlib.Path(image).stem + mask_tile = pathlib.Path(mask).stem + dem_tile = pathlib.Path(dem).stem + assert ( + image_tile == mask_tile == dem_tile + ), f'Filenames not matching: image {image_tile}; mask {mask_tile}; dem {dem_tile}' + + return res_folders + + def _load_image(self, index: list[int], query: BoundingBox) -> Tensor: + """Load a either a single image or a set of images, merging them. + + Args: + index: indexes to return + query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index + + Returns: + the merged image + """ + image = self._load_tif(index, modality='s1_raw', query=query).float() + if self.include_dem: + dem = self._load_tif(index, modality='dem', query=query).float() + image = torch.cat([image, dem], dim=0) + return image + + def _load_tif( + self, + index: list[int], + modality: Literal['s1_raw', 'dem', 'mask'], + query: BoundingBox, + ) -> Tensor: + """Load either a single geotif or a set of geotifs, merging them. + + Args: + index: indexes to return + modality: s1_raw, dem or mask + query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index + + Returns: + the merged image + """ + assert query is not None, 'Query must be specified.' + paths = [self.folders[idx][modality] for idx in index] + tensor = self._merge_files(paths, query) + return tensor + + def _load_target(self, index: list[int], query: BoundingBox) -> Tensor: + """Load the target mask for either a single image or a set of images, merging them. + + Args: + index: indexes to return + query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index + + Returns: + the target mask + """ + tensor = self._load_tif(index, modality='mask', query=query).type(torch.uint8) + return tensor.squeeze(dim=0) + + def _download(self) -> None: + """Download the dataset.""" + + def _check_and_download(filename: str, url: str) -> None: + path = os.path.join(self.root, filename) + if not os.path.exists(path): + md5 = self._md5[filename] if self.checksum else None + download_url(url, self.root, filename, md5) + return + + filename = self.metadata['filename'] + filepath = os.path.join(self.root, filename) + if not os.path.exists(filepath): + for idx in range(self._nparts): + part_file = f'activations.tar.{idx:03}.gz.part' + url = self.url + part_file + + _check_and_download(part_file, url) + + _check_and_download( + self.metadata['metadata_file'], self.url + self.metadata['metadata_file'] + ) + return + + def _extract(self) -> None: + """Extract the dataset. + + Args: + filepath: path to file to be extracted + """ + filepath = os.path.join(self.root, self.metadata['filename']) + if str(filepath).endswith('.tar.gz'): + extract_archive(filepath) + return + + def _verify(self) -> None: + """Verify the integrity of the dataset.""" + dirpath = os.path.join(self.root, self.metadata['directory']) + metadata_filepath = os.path.join(self.root, self.metadata['metadata_file']) + # Check if both metadata file and directory exist + if os.path.isdir(dirpath) and os.path.isfile(metadata_filepath): + return + if not self.download: + raise DatasetNotFoundError(self) + self._download() + self._merge_tar_files() + self._extract() + return + + def plot( + self, + sample: dict[str, Tensor], + show_titles: bool = True, + suptitle: str | None = None, + ) -> Figure: + """Plot a sample from the dataset. + + Args: + sample: a sample returned by :meth:`__getitem__` + show_titles: flag indicating whether to show titles above each panel + suptitle: optional suptitle to use for figure + + Returns: + a matplotlib Figure with the rendered sample + """ + show_mask = 'mask' in sample + image = sample['image'][[0, 1]].permute(1, 2, 0).numpy() + ncols = 1 + mask_offset = 1 + show_predictions = 'prediction' in sample + if self.include_dem: + dem = sample['image'][-1].squeeze(0).numpy() + ncols += 1 + if show_mask: + mask = sample['mask'].numpy() + ncols += 1 + if show_predictions: + pred = sample['prediction'].numpy() + ncols += 1 + mask_offset = 2 + + # Compute False Color image, from biomassters plot function + co_polarization = image[..., 0] # transmit == receive + cross_polarization = image[..., 1] # transmit != receive + ratio = co_polarization / cross_polarization + + # https://gis.stackexchange.com/a/400780/123758 + co_polarization = np.clip(co_polarization / 0.3, a_min=0, a_max=1) + cross_polarization = np.clip(cross_polarization / 0.05, a_min=0, a_max=1) + ratio = np.clip(ratio / 25, a_min=0, a_max=1) + + image = np.stack((co_polarization, cross_polarization, ratio), axis=-1) + + # Generate the figure + fig, axs = plt.subplots(ncols=ncols, figsize=(4 * ncols, 4)) + axs[0].imshow(image) + axs[0].axis('off') + if self.include_dem: + axs[1].imshow(dem, cmap='gray') + axs[1].axis('off') + if show_mask: + axs[ncols - mask_offset].imshow(mask, cmap='gray') + axs[ncols - mask_offset].axis('off') + if show_predictions: + axs[ncols - 1].imshow(pred, cmap='gray') + axs[ncols - 1].axis('off') + + if show_titles: + axs[0].set_title('Image') + if self.include_dem: + axs[1].set_title('DEM') + if show_mask: + axs[ncols - mask_offset].set_title('Mask') + if show_predictions: + axs[ncols - 1].set_title('Prediction') + + if suptitle is not None: + plt.suptitle(suptitle) + return fig From 19ee181de3582c414bb9fd8d14661b261a111bc4 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Fri, 6 Dec 2024 11:11:13 +0100 Subject: [PATCH 2/9] Added tests for MMFloodDataModule --- tests/datamodules/test_mmflood.py | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/datamodules/test_mmflood.py diff --git a/tests/datamodules/test_mmflood.py b/tests/datamodules/test_mmflood.py new file mode 100644 index 00000000000..df96fc1525e --- /dev/null +++ b/tests/datamodules/test_mmflood.py @@ -0,0 +1,72 @@ +import os +from itertools import product +from pathlib import Path + +import pytest +from _pytest.fixtures import SubRequest +from pytest import MonkeyPatch +from torch import nn + +from torchgeo.datamodules import MMFloodDataModule +from torchgeo.datasets import MMFlood + + +class TestMMFloodDataModule: + @pytest.fixture(params=product([True, False], ['mean', 'median'])) + def datamodule( + self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest + ) -> MMFloodDataModule: + dataset_root = os.path.join('tests', 'data', 'mmflood/') + # url = os.path.join(dataset_root) + + # monkeypatch.setattr(MMFlood, 'url', url) + monkeypatch.setattr(MMFlood, '_nparts', 2) + + include_dem, normalization = request.param + # root = tmp_path + return MMFloodDataModule( + batch_size=2, + patch_size=8, + normalization=normalization, + root=dataset_root, + include_dem=include_dem, + transforms=nn.Identity(), + download=True, + checksum=True, + ) + + def test_fit_stage(self, datamodule: MMFloodDataModule) -> None: + datamodule.setup(stage='fit') + datamodule.setup(stage='fit') + if datamodule.trainer: + datamodule.trainer.training = True + batch = next(iter(datamodule.train_dataloader())) + batch = datamodule.on_after_batch_transfer(batch, 0) + nchannels = 3 if datamodule.kwargs['include_dem'] else 2 + assert batch['image'].shape == (2, nchannels, 8, 8) + assert batch['mask'].shape == (2, 8, 8) + return + + def test_validate_stage(self, datamodule: MMFloodDataModule) -> None: + datamodule.setup(stage='validate') + datamodule.setup(stage='validate') + if datamodule.trainer: + datamodule.trainer.validating = True + batch = next(iter(datamodule.val_dataloader())) + batch = datamodule.on_after_batch_transfer(batch, 0) + nchannels = 3 if datamodule.kwargs['include_dem'] else 2 + assert batch['image'].shape == (2, nchannels, 8, 8) + assert batch['mask'].shape == (2, 8, 8) + return + + def test_test_stage(self, datamodule: MMFloodDataModule) -> None: + datamodule.setup(stage='test') + datamodule.setup(stage='test') + if datamodule.trainer: + datamodule.trainer.testing = True + batch = next(iter(datamodule.test_dataloader())) + batch = datamodule.on_after_batch_transfer(batch, 0) + nchannels = 3 if datamodule.kwargs['include_dem'] else 2 + assert batch['image'].shape == (2, nchannels, 8, 8) + assert batch['mask'].shape == (2, 8, 8) + return From 41c118d7412f8a9ffcb5419c91bc308ab2c0f5f4 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Tue, 10 Dec 2024 12:00:10 +0100 Subject: [PATCH 3/9] added uncompressed test data folder and datamodule test. added versionadded and fixed _verify --- tests/conf/mmflood.yaml | 18 +++ .../activations/EMSR000-0/DEM/EMSR000-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR000-0/DEM/EMSR000-1.tif | Bin 0 -> 1486 bytes .../activations/EMSR000-0/DEM/EMSR000-2.tif | Bin 0 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-1.tif | Bin 0 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-2.tif | Bin 0 -> 1486 bytes .../EMSR000-0/s1_raw/EMSR000-0.tif | Bin 0 -> 2522 bytes .../EMSR000-0/s1_raw/EMSR000-1.tif | Bin 0 -> 2522 bytes .../EMSR000-0/s1_raw/EMSR000-2.tif | Bin 0 -> 2522 bytes .../activations/EMSR001-0/DEM/EMSR001-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR001-0/DEM/EMSR001-1.tif | Bin 0 -> 1486 bytes .../activations/EMSR001-0/mask/EMSR001-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR001-0/mask/EMSR001-1.tif | Bin 0 -> 1486 bytes .../EMSR001-0/s1_raw/EMSR001-0.tif | Bin 0 -> 2522 bytes .../EMSR001-0/s1_raw/EMSR001-1.tif | Bin 0 -> 2522 bytes .../activations/EMSR003-0/DEM/EMSR003-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR003-0/DEM/EMSR003-1.tif | Bin 0 -> 1486 bytes .../activations/EMSR003-0/mask/EMSR003-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR003-0/mask/EMSR003-1.tif | Bin 0 -> 1486 bytes .../EMSR003-0/s1_raw/EMSR003-0.tif | Bin 0 -> 2522 bytes .../EMSR003-0/s1_raw/EMSR003-1.tif | Bin 0 -> 2522 bytes .../activations/EMSR004-0/DEM/EMSR004-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR004-0/mask/EMSR004-0.tif | Bin 0 -> 1486 bytes .../EMSR004-0/s1_raw/EMSR004-0.tif | Bin 0 -> 2522 bytes tests/data/mmflood/data.py | 2 - tests/datamodules/test_mmflood.py | 72 ------------ tests/datasets/test_mmflood.py | 23 ---- tests/trainers/test_segmentation.py | 1 + torchgeo/datamodules/mmflood.py | 8 +- torchgeo/datasets/mmflood.py | 111 ++++++++++++------ 31 files changed, 96 insertions(+), 139 deletions(-) create mode 100644 tests/conf/mmflood.yaml create mode 100644 tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-0.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-1.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-2.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-0.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-1.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-2.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-0.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-1.tif create mode 100644 tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-2.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-0.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-1.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-0.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-1.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-0.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-1.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-0.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-1.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-0.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-1.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-0.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-1.tif create mode 100644 tests/data/mmflood/activations/EMSR004-0/DEM/EMSR004-0.tif create mode 100644 tests/data/mmflood/activations/EMSR004-0/mask/EMSR004-0.tif create mode 100644 tests/data/mmflood/activations/EMSR004-0/s1_raw/EMSR004-0.tif delete mode 100644 tests/datamodules/test_mmflood.py diff --git a/tests/conf/mmflood.yaml b/tests/conf/mmflood.yaml new file mode 100644 index 00000000000..43f3366dcdb --- /dev/null +++ b/tests/conf/mmflood.yaml @@ -0,0 +1,18 @@ +model: + class_path: SemanticSegmentationTask + init_args: + loss: 'ce' + model: 'unet' + backbone: 'resnet18' + in_channels: 3 + num_classes: 2 + num_filters: 1 +data: + class_path: MMFloodDataModule + init_args: + batch_size: 1 + dict_kwargs: + root: 'tests/data/mmflood' + patch_size: 8 + normalization: 'median' + include_dem: True diff --git a/tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-0.tif b/tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..77d718e69f3410709f67b7eb271fd87f316c8fa0 GIT binary patch literal 1486 zcmaiydr(wW9LMi2BBIOkPC!5qVnS#UC0IaVxxX)nrZ9rM#TN>gFu}GWjEEJBE9kDF zWkaTpkW3=#;-gXsA3UrS@C8g#h=Q6AG-byKP&;&gY)b?{|LZanHT;=1u1& za2#jGaRLKQ@Y*=R7#F^AC)Q{1#`SgM$GEv*oPGxGF~fRH{;e@SnYq5Em0>?7AN|G+ zSYBKx;`FVAE%2#Q#EoYj#{8(tfOBG=!(6KpaYE+bF|SbxM|a=MTz|>_=Wb@p0p9ivH1n;ZP@X#mTiuG3ITW5OfaEF#d^8%qp`-tc!E}? z@1Hc|o#V&_$NqzFUCtenm5M_llz-lv3vsj&LQ6q$YbG*c6ai&`6GhPffs;vo4c z6e#&4pP!_OqEy2$+`dppYTtS)nX{M57WGidyWS8MeMn3G?&S~d6eD}zB19gM!DzY> zZMe?HI%lHKFb3sj3QVjx%SSECLHdbYa*lOCSW-3u9(L15$`r)Dd>P{G_BFj4P*a?A zDXzEp@J*e0)TX}5ukLK&_cT7D$B(+H*w31_9=||o4YN>Keu6F+T&DLUqA{rAP?nKJ z32qbVTb&pdQMnL3%z(eEIY!Q_$=&A%I&nB2YT+9AN!P+eE5wM(3%$;p$xqeHSLWN1 zUEe~S6kE}m8A{sp^Duv9_*YUjrjn>M2DVSj$RWcP-d}p+@L&a<_7uQ-$OOfgd=Mbr z&(3-g_Nsj7`~o?u@8&?))%;Yw@SJ(mNtt<7Lu zB!gd_JK$zBjfT7|}KIpa!#hzM8 zrLIQ!D)m6fQF|M#-``Ck!wpoF9!huGSJ7UH3+5ILP;!nLL>0>^(BlB*9I{7V)haxj zX3L*7{g^IFL|Clz#D+hwkfNZUe)RA{dqoJ$?`$JM2K&!?C1f%rLb0ra@;;UD<{PHM zxcUr#NtBDV{*Fkv6M@o^40P;V4Zr?tonXnzx(e66WJII>z)#^Qw0) zWb@)Y5jQ^0WesxkMVv8nf94vskdrds%)C)8;tZIdW!|JVnArXI%*QXk|G9OnPRw3j z!YEX>wF)v zQa{J<$H2fV^_~8H|5genPTGXy*!lwYHSGA<$Tr7*g@Eg^6qq)tS(Y=Nh)ZUgnKr7& z^X3yH#}%by>~T7hKE9q`O=OH>S%6CF{n0k=p6e*xO)aN`2d&U7i@_&1E6L@%IOs=T zQlVrP!n3C#wQrQ(Xbw|$MInFQej_b*7*=#H2*C^eMr1{G^U3$N;?JNGI`ioooQYV3 zfv+_DtgIMnR4S2dV20eB*Dv7>PpJZ4NH0uThhD1MvYuaF<%Bj^B(2D^_9Vac}HWZ21`VbM(ZZL6i zhpOM5ysvvIUUM_3VZk%fmY0*f@1`Q4Apxgtc2n_;7_1u;;O4?!a!GhWC+?<_K}Q>D2K|d(#iNSAhyubUWo+yD>^EF>VVex0x~G}gr(Ud8nPXxrqgqw>8FJB(J7`|$ z2S_vdn8J64(WzPqHF@mlc9Yjz6NACq8e zj3xijdl}jWHj>3OdsxJ|VBaebNQc7cH!m^9LK4Z`nZp6wa6Us1%qdubo?KHBW{L13 zAQ8$TN5r`i-L_i|qZT`?AG09Wb_c$HX)Q&Eq*B{p9(~(!nNJI;q55CN=zMjO|Gmo? z57ISs^&QeBkpp@_urz?SP80Ey5h?vW2CiS47uzAwI`PHeL8E>=B2>;m??R_ zOhZAEmWFb(sY;{cJ42t4;hyO@*E2$?EiKgDwT}!f@6#%$K&b0Huxe8d@33<&?$*C1 zUg?EywuhcjYYdhJ(}`v&J#NgP9GwAX$K+E}-5t7a&tV|d7x!Z?@>we2sc#gj)8etT z?kTO+bx?iV03RIuoNjHHPHz3BM4<=xW7P?;)n6dbN-;{c7VtQ|1z~bM&9q;LEJqp4 yzq-nYgw@bbe^>Hk=Yy=mEx6sP#Es~DMf$TKbk}Ucrm;8(?;DYNUIac*a>hUPYvAty literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-2.tif b/tests/data/mmflood/activations/EMSR000-0/DEM/EMSR000-2.tif new file mode 100644 index 0000000000000000000000000000000000000000..eb9a32f0cd7c2f46e559be95effe2a18c1daf4bc GIT binary patch literal 1486 zcmaixeN@eN9LImRo9a?`S7lgAu7p_910~hH-_N_}hY=%&6ou0DFrv0jPSMS(D@0b* z4!KUtCeJ-Ad5D_Sw3!p-*zPRNRLbMYiG7Qm(?2`={m$$6eSJRP&+q&B{=Pqde>=fQ z5CoYZh{S^Eoe82ruJ_)p`JDK@_tzN=^5G)={uP8y&WJ(z4+r^3?)^25oH2v)^WM9d z=cTE}LjP#b8>DPE77V!ia6h3I3)bA@xtFSq1wHQPxSv$(4eb6J_x?-qKX)Uqlk%5C zI2ukPN6H(9a5S7oj#Qt=91W+DBOStHj)v2>+2Td{@77+PqMxE`vPXKo>-yi%3r?QW zfZwJn>iqn!l(hIL{;d=ZoV1}J@byLfYxwc;k#COw3X#wz6B(UU^I5@QAl8hR7?rB~ z=Z6i9f{-4!X}@LB#{Tsb+WBG1g}(EwU7f!ayz0AY*(dQhH9H*1U8WdSaEP+cOR4>e zJAzIhCfeA>K?|li^xrvMsCRmDYK=E$#b0G8Xk|mr&UxK;DZ_YTan`}!+|4fsp^sfNu_;sy(|>0 zYXNB=S5nZuNX$=~h`6Q)w9EZF3iH@Ysop=cEPq3UCV11St?THE6gw!TZYU6<(U7J> zga>unHX2}8G6IradhPqlBIjVqcaZc%JY;eO)+ho3ub1t z1c^Pyc-ZAg9cN6?l(!s{rX8kjHWM&+RTou-TCsE&E%}#4V)0FHWIZuL^uAe0a|%Rp zR3RlBE<(u}7j*vBNBWkbq+1?94U-dWcdZj!GkC2RXkGOxH#I+XImYx<+=^(>)V)xw3RZa=CX@ft~lG?PIp?LP_+F<*y=V=rA~&st)q~e zIt5YEHujC7m@OFQfY85-sp3@~$>qKJCE$ z-wq*Get^m+O@rUJz3kb=R}`DDhu&n15a?r$;|^cbtBzD^%6Utx@}w-ydpkucMj@u% z5%+>?l}DKm1qbO!;$;k5vu^TSf0>Og&!UbUw`pp&E2=$GDY`_8^js?%ZD)ttM?r8Z zRa2*Q8J0HBLeS<2WV+vBGacm^aiE8uCaaM6+6qakM*7p%imH0$ur%$Zo%uH@y>Bxn zSI6SKEQwWnY1!Dh_ozkRkF_-%BkF4OX}1X5vT3N;hKy2lBs zLQ_<>l+dp+L(pft61D2rmNY($EDxEXwj!5qY>#3S&ON1n E0R0!@PXGV_ literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-0.tif b/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..1901e098a59138a4091e5feb09c5feea5a2bd4df GIT binary patch literal 1486 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 G#J>U3c9@d@ literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-0.tif b/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..0528d355a6cb1c7c3a09f75d7b2fad9c884d5c3c GIT binary patch literal 1486 zcmaiydrVVT9LH~4L@2d@6`7)}fC!3;JbW=J?fre_>2yq(Vu6a1#tA}YK8mYh2v`NF zFeb(cN{qrLFu+$(kwxYR%QmP_X3(GukEw{dWvElxPSGX&v+Uk{?)jYa``z<9_naFM zF_W|5IL?9N1R_rG#yG(!7ru2li;3R4CC_@4I|{5UGjM|pr%~e*N4Ya|OHMmu%Bb-b zZ(YR3r8yGL(#~N8cIHYrYv!TM>x?2!&U`ELdZUCBGC#|_!6+PAeJgXzrTm|}o#jc{ z%gY!hMms~w3NB-q80`$H73(uhjCO`}4C^yYjMrOShR)rShbROc+RH`GA#bYw_p>=b zA&tB{OW774{#|ufsPf-T!N^XJo%iUOyNn?})|d-tRz7v=FXY({NX_9(wmyI*%VHJ35YT+>S+q z+5de;llXWZa?>XGWX<96EGsl)gEsc80z z!Qr$9I^r}5hSL$qZ%%}KtsSz1CL*o2l<&*iLmzbgO`Z6XLWg?DbI~hOC;UOfN*ShH zdq}+(=fLyeFC_7MAGr=m_|!^?Xmt|GteVKjzKA#M$|jGVGJ0uOO(}8Sblq_cFMb(} zO1DR3j&(yyX%UUTM_3sjK)u!h^jGjv+8;HYt{?2Cl5`!mK5{|T(glz=w^6O1015gF zRFoVJ*=HdLD>+OTU$pXG1%c2il9AX^#c#8nM^kgbO(vyirz#Y0~#rOlB?=|Gtb zeUyfWOP)~Vws34(EyM6YAH^i>qd$+VMMuFj=*tR-$~IxpSAxu_FqBJoQ9nNe1$%uF zo-N{$q~=?HEuuOr4vs^2RQvWQadm=(Zi?dJWUAx6KJ!BUDkr{iW&oySdZJ~S7AFSp zP)+kjY zO4St_5c)#(SWA;n*`ckwmychy9LIx&Xn$^p1v*z6P~=iYRU-D>Z{*GHx#XVdPbPCJ zO*k_Nl{>|>boEO7RN_scdJXhqdvwjVLePV8)Dl)hb(-I3;xi38^49@#)wHC<6?;`; zI(!M_QzydDt$loLYy#N~OvA}BM#@UJ#|@zq3_mJKw#LjC&S>OwtZ(tQ-rvwa#jn^Y literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-1.tif new file mode 100644 index 0000000000000000000000000000000000000000..48645cf75110e0e7052a0a0365246fea1a860f67 GIT binary patch literal 1486 zcmaiyc~BHb6o+RI4g;(Uh)4u?5tZNq9;+4-f!%pOH-JDy6cIHXQ4kLhWJMB;l&DBW zSI~$72|_@GQaK_=_ydeV5K<)~5Y3inyhoatWPKLyY^%*+G8|~!b#^2>W-hwMJ7qe}B-c|kYXS32< z;(ymw)$Z%ta{7iw^>3zNbf--?j?FJ%U&FSKwQO(Z0@UoN0CD zNZf3+<+%M@l5-tOlSbxKl`RibulyuL?lCWoyI(m4w^P@kcs+0^qEbEV$)PX$0PmVN z0Z&Z~{0U70^3-WG7?Q%bt!tt9M~Nu$-AprH^i%dtXWFG)f)xEUMEDaX46aAF%M2JT z${{yqyHW`=9kd)7*qOSvm%c zQ}wG98alZhW&@@PuRbz@%7WK8rFs~?uHogjmEa9rUTo6cky`N!c zwFwf$&V1{+WeBUX!s<)kP(rN=*9K};jU@^snTv7#t_k!L7U3qEzf+U)d2y6`p$ z^*LoU)Vdtk?SfJCOD#1uN?|C7!nWeCdA-7$@^ted|1Jsq!zxWa{E?l>2}21Q*X%5I1Xut zMC4!igqkbtp_JN^d!zzh^JVa!5`^BN9sE#K9Nk=z09n)^uh;kR+Nm9+*w#V46>;cO zTBCM<0ZsnR6|1i_k_}!^(!gb^7;u8mk^opvw}3o#8+9L%!7X|_++v9c-?;D{%{}i=A!Z)Z3=uMXvOM9KsKFJ3zrA}~P7>ZzJ zIL6PbQx6GV(qrigxKy4Xx5#s(ohikdwrC_3JRxyWKA#dc2}|QVVP4-%Jd?0WP{YI=Ts-m-fKr(X^)AoO91T_q;S|G!~^OB4rWdiRm#his$+~z|8A&>nkWe zW%4#bzERUkpHaL5ZaoQVPU)>a_n`Y{C9$}S15VFNQUI@k-$kAT;K$&ds3bY?d+=_Q zOP~G)++N{`UjmGuLHT*~s2G)?{4w;X81;KJ`}6P_RM*Yh-t%Rp z+B-G-{YCrudj{9yyQfrAmY;EdPHPC@0?UQ*CE(A_YV z-2M?-PxX&aj9h@>CJ+HJ5a5ALAOd0_$e;!yAO?aF)IbD$?N5K+zLhFv_q_dZR;=`% zI{f=?sT{1oE>xfD_4})rT9rwcyy;O9?05Jzczs}84u6G{_kzoHL%5Pn>S%5(*9p}h zPgvx1XYYLWVpsQ7FE_SUH`iLFmE}W}?f#vVt%j8x_OxKeaxeAs%{-ZoouO9GYG!@r zE@oNVdF#>tjjd;A?M~inouj75J+^N(eVeyE J&HATZ`36Y)m=OQ~ literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-0.tif b/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..65dbbda8e48cdd56037ab77e13618ec8c613ad59 GIT binary patch literal 2522 zcmah{dpOnE8s2-mN6P&YJ0TJgVvuD2zV{#u*|xBg%jA+`sUBrg>U31P zk#o~M7#bAIc2*Z2P3^?l#F)_T^wdDA({91cf= z!{N$txGbB7=5J+ zzi?LO1hag!PL3Vw>($tq!*OHH^77R)lH5J-(277A+;6j#cRH^+9V`J5>c`BDqoR>H>aJ)~7!v`q#jse!ps;8F@5foi0Bf9}Z z1f+}Me2kBS!X=p8cR)~~J&p3T+{jjB31Qk4*d%%*Y)~J5AGJ`mxRPl)G9MHBgKfBN zY95_`(MMT@I_NLj&nW!15YgY3lJ?34P!o$Vdr=(zY5Ruw`|U{>Hvw0y$6;5P4tlN% zkk|Z>v=Vugu__7rvDu_LBt#lE3;x3-A!(!q?wAMA z9;ptM%ua-2ggZ1gN$_{ga70BYkDVldrpKWXo%6V&QQ zBS}sHXT*w#3vZxPiJFwKP)7dmKG3>|nW)(3fq^~c)L5m9fDheNFyk(HjGu-%CE=vs zV8L8jYl5N{Rl0vw6O@yIfWk_Wobf=;@`*@WE60>ww}Ri47^u`3VQ!Tj-MkTk+t(k^ zrxqjFvbesDDQ+xdCZ*PrNy&I@YB$G?oIj|UHw%lGrXwcbjmow<;$G4aZI7`<{8N)! zgs+b~-0Q`Zv5kk-)e6`y2&3IQ9Z|920%be$D0Imys(!o_6AZ)XfGizBmrv8di`n#N zmN$w75qN4di{jfBkf|&J+9L_5km;lA?hKSYTu=USv3Sw*gi6}XDN-j1yp$12IG2S? zD=yll%EaHTONJ6LZkETRO?sBRNRLLM9N@Sz5xF%CLW|w3iIZt}s^(0_kn9 z7yRbDVKSYzP@l;(Sl`-C))G6seY=+ac6~Z7sA`e_sW4n#`+zVIhAqns&~F=0?#WU* zlQl%5`e>x`oKd*Ki8SO_(W^sCvD08PsU{tzjzIbu=ki4IsKi#TkA$8grJkS5*sE`$(B}$rGqjxs}??`bl};Y-Y3cB6G-bFOx3~ zfX+7}veMSZjZhxc*D#Qo|3&4AO@h|A05p{vp`%j=f_!`QYhI;$#{Xj6&E-)VCr7)y z-cV`CbLtH?WR9;;q>AY`DbmdjeQDR|+@%QeX_i135`b%Owo>bwvs5^8mqN`$u%k8! z+bxsuxHlcGU7?83$-?5O*(g8bf&-B$bbDtqcJ^E$`4}3c)c>AB&Noo%CPxH80g=(_ zcw*c_@sBn#K~vf&AySSiRlRXuJ_|P;eNY!2fX)S>NQ>6Q)L{)wPC7~DM@$jx6oSE{ zSE=piG&)l$q!Qk1`gGd?d(TVhT|qEfB?NItw_rU#4th;|^bG?m%+1Ko(j7)xugNZd zGpUuPFlyPi=;;DK6bo|b87~b^C*6@9Y=T}7KEf=$QT;HRvY$0DN4v$)PMJ^T!^^R; zaSkaM@@NE9qogP#BZ literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-1.tif new file mode 100644 index 0000000000000000000000000000000000000000..9b85b5c11988ac51b16fbce62c6910a7b3ce7691 GIT binary patch literal 2522 zcmah}d036x9{zUYZZ3(klS<0aAS#q<@AZ2dq*4jlC8DTQgp?sFLz%W?jB|+4;F1PI zNQN?9Qz%o4)ImZKCBt#fm+L-{f9`X?^}Or%e((CN^{(}M*7uDWGnf-{9H+x^0(nlr zGi@G%U$`94<#^4QZ}LC&?mVSmxQ;;K$Gk7RuJ=>#p401>oZdX|Ub~iu;V*i?PtMES zc%FC9ljob}3U$8cI7i+rZKXP=#Pa~27pBYeJK;5+7p1HJ$gkjeak|`(licNb_mlE} zS}mU^<;`k&NO_}L9#RE9@{lU>k%yEwt>qzA<|7X&U#eOjQWcK-D?!wG>Y1^ti=b-e z#ov0n{>Sxy_h*N@XnHkRim!TlRvvonF8*((;78T@#d%)=e;U4&eB?LB`v|xf+5%y5 zIzP%O{dkx4R}mJacaN+6h#Z#@yI_0Y1M|Aq6Xym7NhW!WHy&xXlzYF&8vBOtBD3NM z9JWcson=EwH;Kc+mQYwI4uN+-j^u<%1gw%RNcv^^DQS{9crvL2*yW`N8u9et>sd6-O#{%eR+;n|)xCbV*h7lX$h^*5U z{ZljJua5@fy0H zrgsD|h1qvWx7Y#BY##YH9U@;QLbj}kyeb`V&|U;l{2I#rq{-YajUZho6RbQt0GWYd z&>ro9Rr{?GKetBWq;G_KIr>OyE+LQf$8=@WE841ZkoH!LM9Va7lo&eF+m!x@@zcU_ zE)=s^2YtC!M_)7CQSfCd>Z|jq;ldtj$W%nsdOcKD6_Iy_8M?X}+5Oaa*LbKxEljqlfOF{3>Rv1T){L8y*X=X=63+JvrNtRf|u zBhFiGrqXr+n(MyOq|d$ZqTx1cP&Pr$+Cg}l)q{$!2Gge7)udf6#GHVBXcMX6!O<`r z=@E;H=_yop`4}bdR)wSQN7{491BxAD=w2 zKeupv{>vEQk_qU)KaF-ryFwOyf)+^^(fq?Z$S6^Oz*>`sT50Bv)$3d#~SXJt+9Ri11ep*mSi71adP$$z3DpM7n}xLE^_K5#hmK|-o~gTV&vVfIUQ(9u^$s0}~nPCRO%&q)(#NZ2Ga+smi)+X##s%jlmQsj$D|?-1g%+&9yx%3kAIU!v=ZBhQUC^0i;qzdqam| zN#{Zw)tH3k-v+bqH*&~&*Ap@__hK#sO)2iH1W^NgAlR}1%Z@Mz?ixaVsV5Yc$55cY z4FtO*F;-U}sm~A4Dxb+%`FxG|PEZHC;dYyx`u|ROTY?elZ2|8~Unu2x5TbSlLgRTT z%yjd~?$Tn~a(N;qcU)u_(x>CdwgdFAt&{|tjN#pPJ>^zqvYcK7X-yF|Cys&Ey&#nM zM`Fo#Wz4bTVEf${yG-3#p`?p#Q1pXz>{+TX5hCkaZ_3y(3w9zYzUd{xAnFyfcs>ob z1AEdy@khEnQyGKrIivg$|1Y?iPQORa!b8s>c4FiPQVW`b1Oxv5!bnm*$d3I3;ed@Q literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-0.tif b/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..52fb7cb2d0278013907d60a67ef97f8b518537f6 GIT binary patch literal 1486 zcmaixc~DbV6o+331Q19;C14dA1yL5af=C4+dFLk(M64o;T8gY-3nYk!*WDkGQzEx>uOpU6Gr5N-@B0I z#p$M;Zk)~n_><+%2q5%w7&+ zC1c+REo$*Z+PtxO$5N zx?N;f{QWh@I(=pTRtkns+K}Ve`U3Vf?D*KoHphO2fO}*vFglaPW;xMtoNH%nbUI5n zZ!$b`Ty|VizRlr8-FmW$rQz~0zhJ3{+h%U`Krw&4DW6K^=7@bV9a$k?P+Iec@ZqDe zvo8r7)!_&}%cI#(%aurUl&Mr)BJj=j0}QstkibZikae2dVjG5x?5P9fgtwRH1lH39Xmu!2)O0dd;CB zvy1diTL7MkozebX2sKZ(2B&ersjYofo#}znC;zVo_*VT6;_B{` z*X>k{@h#yky8fn=A3Tv*v5W4m2|$O*WGGyK5_1zQzfwi3GWC2C>#k9s5J2&q8P;ZE$4nO@5Db7pX-l7-;lFXZ<5GDM*6Lcpm{?_U)mV7wt4|ktX`MG(4ADvGfPIVNS@{rcq9->CEEmT4! zy38DT&6+XPa4r^-)GCr?UZ<0>M(~@J1gATtl*>6`#^75@H%vxPH zz~#CSm3uj&4`v}XcPz%MhA4XBAldR|^xAkWosO&I{VSs=Vqz_YcRwKY@eoQ#DWw%X zN)+|4Me!0P29}tjHdBQR+hl&*Y$+zye@Sbj-4NlEir?lGQ$_Xzn%^3SKP=4A*cpj` E0Iqx3j{pDw literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-1.tif b/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-1.tif new file mode 100644 index 0000000000000000000000000000000000000000..a2b07cd672ee26253bbc85b9fab1c64ec415e164 GIT binary patch literal 1486 zcmaixeN>ED9LJwAHB)U8EorByb`ec!RVS}C^ZdT4WIH;A=*3ie5khYxtwmvWS7u7* zto2S?PGSqSk!3^)t(^(2VPn~i*rezbXKl8<#h%kY&OXoi-21t|`+M&1KKBL$OyW#9 zj^rD z)C?WNf)xy6s2Mtjg%KMw)C?WN!kCR2YQ~#%$;;~B?R~rj`uMs$YoE7O-`{H8y)EWE zkjk$3`_~?Ao-O+?Q!sGSVvb|$3)tVV<6}MB9J>ku*J>#+IiX^+oM>RI{@B#yxT=5t zgMpsozFe91ja^Yn|9Y~+p<(icej)Z#JwE5gwz%TVqZ|s;KjC})CR6OJ7WtTMBPigP z-Snt4lbjo7VC>Z{Y8aYDCv!Up_6m$&zn@QeGzm^(e~dH)(c0uP+OH0!2$c)6J7+=I zH6Dhl$Nc<>VaRGq!&9{aVaZQvT~R1H9G640ubwn@f6z+PcKPT&9}2jI%tJ^6$UcyTR?tgm84&OmYTDr zxZC3lCr>A2YE$8ow+J2uK6rpVw8LQ(4AOe~s@)VLw$DYYd^O?@=2J_rC$wpDxc~T& zu1<(W)&XCL+ai(gNw^&rOqq4pXv>Q@vYI*@BW`}rS8%Jyds+hZDnEtpTP-cAo`=ks z)zFuEqGWpt=I@<`O*Tj5m;Ss@sghu<30#KizFyk9F&NhthogH|E}gBAqT}jeUZwEE zOLYb$ZN3x`MEGGCL6sHA8(tTYA@Dgl9i4(`K?&c{91WAu0$L#3LoSKg{20S9G_{yf z;291MQZY1N7~`>X8iseiB6!yEk>b-762B1Pmt7$;sQC17Pi(o~Nm+gIn0%&*6e1wB zmP4brJ9d2W8f#-IEpGx4V+P@p3ly0aj)L<6`1$No<6`U6{orHX|)C)<%8Q6YW!&^ED@nG6GI=S8$H$04~ymuL5 zPZi3?)r6qUb1Ox8Wzw=71u47#p$(fGY1gY9N~kX&ucF&DG^&^}>+k5L$_;m{*P<|?js68{eChQ7 literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-0.tif b/tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..342e5daf69e36e35ec886f7b7cd380d992c8c19b GIT binary patch literal 1486 zcmah`F)u?=82xTrRnffEKnEmEY$6s0Q`J(fp-UzF0-HrJ83f5Qy_oz0u^6**Q-sZdKeCM3|-P^X^o|lq{R76}}T+GB7o=bQQUOwTPD;hrG z3OYf)P?JWVGQ0||nGiK&^lrlQ(7n^LC@$iHle4lE!JFW>eqL(ehu}TGEIIHy@V=jm zUjGcPt1;vgBIn_72Nj?~l!psDr~nnByaIYufC^FG2zpe2`Z<{XzW=DL*4^vQ(?xYP zzBPM)SgLy)?+cBWR_pQRt=VAGMZff@2 zACFMve0TqH_G(Y(HLf?e*S6NXwUy#txis4M$u~I zvHJHu_Gv$>=Y0C?iJWJ9<;)Z56RjSFlZ`%+Iil+17Gw4Q&N;+EXXx44uo;TDsS5kV) z6zvB20;ZKeqjVM8W>R2I`JJKm;0I@AvAm1}r{`rUK{uf9VoyToBj|2imI8Dix)&F+ zr+E&sCj#+w~p-*N%?+$(D z^jw~1mdu=G4Y#jrozwF=<7#)tt!+Nfb9b@kjAq?)eLk!9dDHc{hkY`AogbxnOEY(n zxy#Lr+Q+PBJ;xb(dA0VLCF|W<+debYthsL#yF1(rcW8FCtzA#=;SJ_=mc76H3(LWZ AqW}N^ literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-0.tif b/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..a1aa21bfa28fb4758f1e01f7f9c948595239c4d6 GIT binary patch literal 2522 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 Bh4BCY literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-1.tif b/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-1.tif new file mode 100644 index 0000000000000000000000000000000000000000..039ffa35b920afb64f5160ba74ac9a79781c548a GIT binary patch literal 2522 zcmah|eN>Iv9zLg1p@}Bc$mv_6v=)=(;B?OWJW6FkA4(aeNfdpTw8oHA$;3qH8_f)N zCPK*=`ASBEF>C0!+tr$#_5k}lM*Imm$cinfdXaAn}*}wgJ_WrH4-Q6cibR`mr zfkYzJlt@K3QiSv!t|4*_Q8O2Z=39L*Px~EiAk})a?}ez3eyb1e8S_q#k;n&Yw}}|{ zj_&=Ii?YN=q)dl?k)22Oil45q9*c+OuaYxKZyKjrpB9_ zG>ClgOZ`8iP0R}v&Dul+ibicB0=2{_B5;TpMFfhbZ6X47#3&+AY*m|xz@ZY!K$6MR zlkMg%3h6(?YquM@{Kxfw&($9)^gZs_D=*KTd!g*QtMb2@(l=cfi;KQe@it;B#V8(5 z^pQ%sMoM*$W{Rs4?Kl6~Rzr0wG6&a(zljpb=BUIx+5Y&!ddhEn162NQKIYDj>m&x3 zdf^sy8Uu`HL%IRKxe)_v9V6j%uNyU!Qz(~z$OK&(UMA=Yy|_m88d{yks@MQeVNRzZ zHT}QEfbSt;livG0a(NAl!Y6aX!U&a-+JP+|CUhF%N9RuzObAH8)7p_j;iNcxdAeRz z+vLkvdMgEC*J4gCZN_EyZOVhz-(%_+2QF6|GUlEad%r9JpU2a?at0f+eo-aZZbgIs zQDOO`;T&({jm*RmxbU|tD}uDRqhBU0^HOn7Qo68CrAD&Z5x75-;^YuZnoP+SZq+z) z|B73}?N(Rr8viMB*K0BA!Jp`^pGc!gmoRKXE_5AEA~j+e&l_5?*GLpS`WZM>T}NVJEIXEIQdSmDjSJ1N8~Q6&XmrEyL=?Bw1#x3z44uwR;JU6; zg7J4FDLojBZOTy`n?Di;Q$L{pft$+CxwC}q+8lVM-Gt%u`Mh4SS*S2REwt7h5ni>} za#E`cpXmLJxUfq|N-~GWw}0Ty%?LbgzKmbPR^hirAJO2d1T!O>P@ZrGfApw$F?T!u zs%t<(r9ZzNejJ;l4V2INr_pkvu4=o6BVWr>Xgj?{kXS|{{@Q9}g@w}mQ4QQqXP~J? zE(kY|3fssygQ;V?hOLud;^6uo_-wk1M5`vDZ`&?(Om=2MMJ!9MSDHw{YVs}kG+vi^`pfvF z$c$mWDO6t^&3vyltXg{>cKw9WgBbqps+BfogoQp!2aWLGb`15f`AGD?0p%NtY^AWhu z+=gG+K8AWi20l7+6$g!y5b?E)R|Bos?br_aj}ctZ5CPe)o!D+;&DmiK__|*ot=}KQ z&Xf8qjIUMQ>sDjMXT9*4VoJ|#J=ksNf&w==`)4cYwDX9tCmftp)gYua=AooN3!_d{ zsxrsuF!|333_IY#QF;DMGf^QvxC@~cPQ=F~E?M#L!+7qP|0kl>*TTEmj^}H&nN)QN zd(Uk~WWT9UmYpbAl^Jq{#Vg#rvYZQ@;yG;fN=!s0M*Zc>dDqN&J}rPTcL%Vmyj;kx zKY>$yHnKqm$ z-{oQB%jGnyc?h4i5mdD7f%9E$E}6R<<=Ia$RIv&>*Ik2D-$8J@n1Hs}VubcpqE#Lw zEQ%|@?wC4!q;1I?8Tx#X5=`wV1s6|DW{ANsI^;h>%+?WXNPdmx;%s~n_&yZ@<#7HD z%xL#zN=Ou<54iByoguLPDiz>PnEX_V4PNHL%(G?a*RZFgupR9;dQh`5S{41iGkV&}g5!}1mpSOLdQ#~|{76mFc&-O-7uNtwRIb$GWG>`1-#PrrBEIew*RdG4+ za}Gem(x=GDPrzc2M#w5-sZ%^49J?ICFFHxHPVs%c*o3kDRl+Arlngra5Pu&Y505=2 ze3B!l&Bi|Ar=DvV_uQYKs)4=7OOWfI$l!e~P;5*lv+6PIhAT6Sme3>BhK&|7<}L0* ytz{a%y63^knQQ2IT!#gZs^Fh1XW94z(3lp<%oB?cUr~%8&sk`jGZQZ^y5QeY#HI59 literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR004-0/DEM/EMSR004-0.tif b/tests/data/mmflood/activations/EMSR004-0/DEM/EMSR004-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..29e5053d6b8c60763d3ce67de200cf2a4616d083 GIT binary patch literal 1486 zcmaiydr(wW9LMjnxC;V1AcDN4&Ap zR1`DuiKw6xI0|N|7$Kr$q9iFPC?gmK8enR^Q#*?`!#_>;&gY)b`JLZA=iJ}9lP9|g zCW0VX3W7u`NZy(t8RSOqT*=3z@7$0l8{}3JW5WzWKgVWJ+;)(U;BLri;y4Y8>)yGP z#}zr|f}!=~3uNb-3o`B@+>hi*1ts@%?v;7wf)V%cxL4&F4eY*wyWvv*&)vlH6#Qi^ zr-0MMQSb$|oB~c0M`6tSoB~c0M=^x=IR%_o%N(Da>{JG;BtIve-83Tj?W+I%{J~eH z2ES!w{*V{<> zI1sHrPDAZpAK1G4qt}CxRa+RW4}@WykV-LqE^teZL6KZU!{Mjw>Z`V|IxS@5raU z&5^X%&lIIsLU2fYNb@y1G>5dYJ8=Q9nqEg)SphJ8G6voq`{?_)BUf#BW zwoV57KToo>(>{16c}R87O;MEQOtbv1(loUnDx-X1ZC^{beru)e8$F@VU&EF@il*)d zU(p?vH(2F7I=?Ux+F=BX4f7DcSc(nRcC`D**L1M{zIZvbfpX(3=}mJ#h0T0S<0bKU zeC{E8wyKmOb{S)Rxeqjd#A2p=GaY+Ri|%zg=z4*O(W&tFUV=02;h?P`{x zrgIeQQq+-Uk}2sgL}N&?8L`k}x@RSU(Rdxs-abLC(J8!UV2N#x^zpO+#03vWMz{q$ zciN&X#~ZH`GWrrwph{sUbH+05V1NDeAG_OU8EedZ;QkIcBZKNKU(~zgv zS+z$GrEln`g=w>ppz}jS#{-sA+f6IBl+m%v{p2x9#BrH1Z5d@l4ZZcW8L^OB-l6uP zb@cwRKJ_c#C3I@~YP#{FgsS=%BXar-bnJT%7fwh;>x=+gzBq~g8ncr<$Q0mKxth7Q z3`5h^5}NDX!TSGphd;B%mDp|6(&L4aYf;EJS4i^hzW60E6?>vT#jaL8SkP@czQ2*& zCLE%Yz(pwDy%++P!I>>b)6%D8^Vv(<)Rh9OOClZ4)}!#EoIaL*+)(ZW5M?SL`jh% yqV8IvD>I+U#?FS#t|ZLJYNT|VFeF;@|I=NLvgSi{J-(OnU!^0t)E`Cddi(?HpxRRa literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR004-0/mask/EMSR004-0.tif b/tests/data/mmflood/activations/EMSR004-0/mask/EMSR004-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..a9588d8cf6f918371b50f31399f078720ed8b33c GIT binary patch literal 1486 zcma)3zb`{!6unXI8oE@%UtqHcCW9b(rWcdHKr9CBBC%MA zn4Ukt!eTVqB+l*YNw1gZ$7^oSx#ygFzpt;&=B(sJq$uJ%aWNBTc&5+G@Vq|PT+Z+@ zm(>mOjv6=mgyAJ{%|xgvqqqCqgYF*{L~$Mm93B@W2VMui3Op%;?}2xMf@Hw2!Mj1G zcl!r$eQJMvBIJAww@@J}Lisqbg$hv-%Fm)lg{TPS51~hesL$QWkDJ$Wwc?&O?@vqB z_|%`jZx<^5>dRd1vC+7@e6H7+biFq{EQ0+GzXq=lh|A%xaPn4k`ECGLa=kvB8Oe77 z^+$UsaV1+r&U>!) z|ED$ZJUKUO;E;2p1`fFjj~wzoJaWjnX#L=fLodIrkOur{P=4BVQc%5ea_|6sa9g z;G;s{-urEa>T1Uldd4+-qaY*(N2HD18{RXY!@h5QWZq7mQmZLzg!^Xas7m7#<<@+l zH=1>_rkNR3YpRTJg$dqBc2cUJHXe%4%j!zku+Wlc)D>366637VoYze{e!ei+VoD~3 zVpJ#CB16Rmeil;r&9KLbh$A#4^%K3<8-j(N^HJxO!lHA6P*=PNZQJ!ww|z6|>@%iy z4_oOZJ43TdmeIR+fr$6&3oPA915eDyN8535n-Yy6Wm9%=MKqSRE6^Pp#nuhJNA(kZ zaPE{9Oqv+38VyCqumWn=v&C0!FRA*wQ?x(Hf_6xUz-nC`kzEJ^+r3eJ&kWkz&QP59 zO?FzIK>aFnSxsgr?zW21CK(Aml{c)j`UqWGtDyQIH{=}HL=_(INbJ5C*JB1#mU<0K zTQwQ6tBnvG8j9aNRmfH`398SoG1tpF2ze~R;sNK#t;8RxYG##nv=Dztjiv@V zLgkV%9ZuXuRhs$qNXY~q?Rr?{vjD@(46*f03R%tshV~nelz)dJd6XHMq?S_t;~BCi z1D8V_`%xB=b&G7q&clnKTv}TgKyNgf>FjoE1g^2hEvXgecIn~AqJ1pmr|I~{&Jjkl z?OAD&DxFT8Lvuraq=pOYYGlFRnNZY*RuT=1#&}(0 zij&&nK|?zQpF2Y)rmFCWTZol+_R-NB$>g>|U#2WPVBhAZQ%G_#i`8e(m>I&2J5>2m2KdX7*i z*%?C;?TeMcLnu~L#A4r{qq6=X2w0a$yZ^XCO>^%PIeZPvCq)!>Q4I6ft(1FFjsRN; zLfxY9yjT-0+Cq}d(+<_#OiKLP>3Hkk1@vQtCnh+OEw=;Lq>^UpZo)L+n zpFNS+FPZWchsa2223o>qVnUq>+wx~1`CBW&OJ4)7jSpF$=3l6+qJi3U^zn1)OPcy^ zCgtcx$!zn?k-kO&MbbEQSYIcpZ!pXpCLqLdEE}#HgeZ>%*b=#(DpLhm=MTokTj2<< z_eIKwYFWCq3$$MRN^84=V5M*((@z@s=u}I~CC}N2!3wNj8iH48+i1i%eM)m(&Gf>x zpzGWR=07(x1fpg>6w^<7ucNZH~E3CQF(q>8Kp*r)#5bd6vv)+G_l3dp}Hk|06Sd(n&9c zWbz(647=WIkaQ1lMXZFE(tE7gU6|MXh=>2(msG&a&Bu|PR*FH*}+8_bI^lr8Y`Mqx-keN}LZ z>g%u4{FKAYM0}D4jf%j@`+HdW1y@WT+(C~O<+O2k8qE(8Aa0dWe5)nwt@WYubO&us z`U>;-*=u)Cg87FeQrH*Jy-O~nXO~Sc^d>;Hco{Yt`Jm+z;lwYQvY-dla{}70OdWA9X`Jhp-TcW#-@wK1^YvE`3{@W z>VwLmVw&)MF4Y_fVLw#XGKQS(27>CZ z(P(MD?C#ldC>Bqq$Jyafi>aUyo=!+?-p_tejECleX(-Gd1Cft2JTCl2in&dc>;63p z@-jgDQ8Og3A?U|nWR0~dup2*t`6hUxTwzQ*g8j%av6OaAu4Fq4r5NR3&$5b7laFWw zG~+T^*c?+d8OyL?_yXL1s!k3!XQN{DEHn-Cg;hx$i}fp~-|tD;0)D=mYaH2_#z43a zHD%Y^_s|Ww4`xrAkCTz=c;7q`-eu+_%C^K%s}#~d7z*cQv!V3+YP_}dqT`V#$#>c; z+`Rt6!A7f#F7bE!;1qwX(lUXYeGzp`2|&QgN(#8Toj$~khmqIcth~sKN^^?HIb918 z6&=*I(;h9(=jrd1QmUKoi#_iAySKEHmV^v|%X0o7GO!_k^-PvI-~|PYnvSW)9!#E} z#cJ-kla=lWbQ@=rZ($94vn7cfix zUrfjHXX5oy2`O!wfj;p;u+X?eqBd=8^ None: generate_tar_gz(src='activations', dst='activations.tar.gz') split_tar(path='activations.tar.gz', dst='.', nparts=2) os.remove('activations.tar.gz') - shutil.rmtree('activations') with open(os.path.join(metadatapath, 'activations.json'), 'w') as fp: json.dump(metadata, fp) diff --git a/tests/datamodules/test_mmflood.py b/tests/datamodules/test_mmflood.py deleted file mode 100644 index df96fc1525e..00000000000 --- a/tests/datamodules/test_mmflood.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -from itertools import product -from pathlib import Path - -import pytest -from _pytest.fixtures import SubRequest -from pytest import MonkeyPatch -from torch import nn - -from torchgeo.datamodules import MMFloodDataModule -from torchgeo.datasets import MMFlood - - -class TestMMFloodDataModule: - @pytest.fixture(params=product([True, False], ['mean', 'median'])) - def datamodule( - self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest - ) -> MMFloodDataModule: - dataset_root = os.path.join('tests', 'data', 'mmflood/') - # url = os.path.join(dataset_root) - - # monkeypatch.setattr(MMFlood, 'url', url) - monkeypatch.setattr(MMFlood, '_nparts', 2) - - include_dem, normalization = request.param - # root = tmp_path - return MMFloodDataModule( - batch_size=2, - patch_size=8, - normalization=normalization, - root=dataset_root, - include_dem=include_dem, - transforms=nn.Identity(), - download=True, - checksum=True, - ) - - def test_fit_stage(self, datamodule: MMFloodDataModule) -> None: - datamodule.setup(stage='fit') - datamodule.setup(stage='fit') - if datamodule.trainer: - datamodule.trainer.training = True - batch = next(iter(datamodule.train_dataloader())) - batch = datamodule.on_after_batch_transfer(batch, 0) - nchannels = 3 if datamodule.kwargs['include_dem'] else 2 - assert batch['image'].shape == (2, nchannels, 8, 8) - assert batch['mask'].shape == (2, 8, 8) - return - - def test_validate_stage(self, datamodule: MMFloodDataModule) -> None: - datamodule.setup(stage='validate') - datamodule.setup(stage='validate') - if datamodule.trainer: - datamodule.trainer.validating = True - batch = next(iter(datamodule.val_dataloader())) - batch = datamodule.on_after_batch_transfer(batch, 0) - nchannels = 3 if datamodule.kwargs['include_dem'] else 2 - assert batch['image'].shape == (2, nchannels, 8, 8) - assert batch['mask'].shape == (2, 8, 8) - return - - def test_test_stage(self, datamodule: MMFloodDataModule) -> None: - datamodule.setup(stage='test') - datamodule.setup(stage='test') - if datamodule.trainer: - datamodule.trainer.testing = True - batch = next(iter(datamodule.test_dataloader())) - batch = datamodule.on_after_batch_transfer(batch, 0) - nchannels = 3 if datamodule.kwargs['include_dem'] else 2 - assert batch['image'].shape == (2, nchannels, 8, 8) - assert batch['mask'].shape == (2, 8, 8) - return diff --git a/tests/datasets/test_mmflood.py b/tests/datasets/test_mmflood.py index 3b004a194f8..5649e51c5ac 100644 --- a/tests/datasets/test_mmflood.py +++ b/tests/datasets/test_mmflood.py @@ -98,26 +98,3 @@ def test_invalid_query(self, dataset: MMFlood) -> None: IndexError, match='query: .* not found in index with bounds:' ): dataset[query] - - def test_check_folders(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: - class MockMMFlood(MMFlood): - def _load_folders( - self, check_folders: bool = False - ) -> list[dict[str, str]]: - return super()._load_folders(check_folders=False) - - dataset_root = os.path.join('tests', 'data', 'mmflood/') - url = os.path.join(dataset_root) - - monkeypatch.setattr(MMFlood, 'url', url) - monkeypatch.setattr(MMFlood, '_nparts', 2) - - _ = MockMMFlood( - tmp_path, - split='train', - include_dem=True, - transforms=nn.Identity(), - download=True, - checksum=True, - ) - return diff --git a/tests/trainers/test_segmentation.py b/tests/trainers/test_segmentation.py index 4bdd966a1bb..a21f0e5f4c8 100644 --- a/tests/trainers/test_segmentation.py +++ b/tests/trainers/test_segmentation.py @@ -65,6 +65,7 @@ class TestSemanticSegmentationTask: 'landcoverai', 'landcoverai100', 'loveda', + 'mmflood', 'naipchesapeake', 'potsdam2d', 'sen12ms_all', diff --git a/torchgeo/datamodules/mmflood.py b/torchgeo/datamodules/mmflood.py index e669f3c19db..6f4a80babfa 100644 --- a/torchgeo/datamodules/mmflood.py +++ b/torchgeo/datamodules/mmflood.py @@ -16,7 +16,10 @@ class MMFloodDataModule(GeoDataModule): - """LightningDataModule implementation for the MMFlood dataset.""" + """LightningDataModule implementation for the MMFlood dataset. + + .. versionadded:: 0.7 + """ # Computed over train set mean = torch.tensor([0.1785585, 0.03574104, 168.45529]) @@ -75,8 +78,6 @@ def __init__( K.Normalize(avg, self.std), keepdim=True, data_keys=None ) - return - def setup(self, stage: str) -> None: """Set up datasets. @@ -98,4 +99,3 @@ def setup(self, stage: str) -> None: self.test_sampler = GridGeoSampler( self.test_dataset, self.patch_size, self.patch_size ) - return diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py index 2e6fd747ba1..2cb30eedba7 100644 --- a/torchgeo/datasets/mmflood.py +++ b/torchgeo/datasets/mmflood.py @@ -43,6 +43,8 @@ class MMFlood(RasterDataset): If you use this dataset in your research, please cite the following paper: * https://doi.org/10.1109/ACCESS.2022.3205419 + + .. versionadded:: 0.7 """ url = 'https://huggingface.co/datasets/links-ads/mmflood/resolve/24ca097306c9e50ad0711903c11e1ba13ea1bedc/' @@ -122,12 +124,11 @@ def __init__( # self.image_files, self.label_files, self.dem_files attributes self._verify() self.metadata_df = self._load_metadata() - self.folders = self._load_folders(check_folders=True) + self.folders = self._load_folders() paths = [x['s1_raw'] for x in self.folders] # Build the index super().__init__(paths=paths, crs=crs, transforms=transforms, cache=cache) - return def _merge_tar_files(self) -> None: """Merge part tar gz files.""" @@ -143,7 +144,6 @@ def _merge_tar_files(self) -> None: with open(part_path, 'rb') as part_fp: dst_fp.write(part_fp.read()) - return def __getitem__(self, query: BoundingBox) -> dict[str, Tensor]: """Retrieve image/mask and metadata indexed by query. @@ -194,25 +194,35 @@ def _load_metadata(self) -> pd.DataFrame: ).transpose() return df - def _load_folders(self, check_folders: bool = False) -> list[dict[str, str]]: - """Load folder paths. + def _load_tif_files( + self, check_folders: bool = False, load_all: bool = False + ) -> dict[str, list[str]]: + """Load paths of all tif files for Sentinel-1, DEM and masks. Args: - check_folders: if True, verify pairings of all s1, dem and mask data across all the folders + check_folders: if True, verifies pairings of all s1, dem and mask data across all the folders + load_all: if True, loads all tif files contained in the "activations" folder in the root folder specified. Otherwise, only acquisitions for the given split are loaded. Returns: - list of dicts of s1, dem and masks folder paths + dict containing list of paths, with 'image', 'dem' and 'mask' as keys """ + paths = {} + dirpath = os.path.join(self.root, self.metadata['directory']) # initialize tif file lists containing masks, DEM and S1_raw data - folders = self.metadata_df[ - self.metadata_df['subset'] == self.split - ].index.tolist() + if load_all: + # Get all directories + folders = os.listdir(dirpath) + else: + # Assemble regex for glob + folders = ( + self.metadata_df[self.metadata_df['subset'] == self.split].index + '-*' + ).tolist() image_files = [] mask_files = [] dem_files = [] for f in folders: - path = os.path.join(self.root, self.metadata['directory'], f'{f}-*') + path = os.path.join(self.root, self.metadata['directory'], f) image_files += glob(os.path.join(path, 's1_raw', '*.tif')) mask_files += glob(os.path.join(path, 'mask', '*.tif')) dem_files += glob(os.path.join(path, 'DEM', '*.tif')) @@ -221,34 +231,42 @@ def _load_folders(self, check_folders: bool = False) -> list[dict[str, str]]: mask_files = sorted(mask_files) dem_files = sorted(dem_files) + paths['image'] = image_files + paths['mask'] = mask_files + paths['dem'] = dem_files + # Verify image, dem and mask lengths assert ( - len(image_files) > 0 + len(paths['image']) > 0 ), f'No images found, is the given path correct? ({self.root!s})' assert ( - len(image_files) == len(mask_files) - ), f'Length mismatch between tiles and masks: {len(image_files)} != {len(mask_files)}' - assert len(image_files) == len( - dem_files + len(paths['image']) == len(paths['mask']) + ), f'Length mismatch between tiles and masks: {len(paths['image'])} != {len(paths['mask'])}' + assert len(paths['image']) == len( + paths['dem'] ), 'Length mismatch between tiles and DEMs' + if check_folders: + # Verify image, dem and mask pairings + self._verify_pairings(paths['image'], paths['dem'], paths['mask']) + + return paths + + def _load_folders(self) -> list[dict[str, str]]: + """Load folder paths. + + Returns: + list of dicts of s1, dem and masks folder paths + """ + paths = self._load_tif_files(check_folders=False, load_all=False) + res_folders = [ {'s1_raw': img_path, 'mask': mask_path, 'dem': dem_path} - for img_path, mask_path, dem_path in zip(image_files, mask_files, dem_files) + for img_path, mask_path, dem_path in zip( + paths['image'], paths['mask'], paths['dem'] + ) ] - if not check_folders: - return res_folders - - # Verify image, dem and mask pairings - for image, mask, dem in zip(image_files, mask_files, dem_files): - image_tile = pathlib.Path(image).stem - mask_tile = pathlib.Path(mask).stem - dem_tile = pathlib.Path(dem).stem - assert ( - image_tile == mask_tile == dem_tile - ), f'Filenames not matching: image {image_tile}; mask {mask_tile}; dem {dem_tile}' - return res_folders def _load_image(self, index: list[int], query: BoundingBox) -> Tensor: @@ -299,7 +317,7 @@ def _load_target(self, index: list[int], query: BoundingBox) -> Tensor: the target mask """ tensor = self._load_tif(index, modality='mask', query=query).type(torch.uint8) - return tensor.squeeze(dim=0) + return tensor.long().squeeze(dim=0) def _download(self) -> None: """Download the dataset.""" @@ -323,18 +341,12 @@ def _check_and_download(filename: str, url: str) -> None: _check_and_download( self.metadata['metadata_file'], self.url + self.metadata['metadata_file'] ) - return def _extract(self) -> None: - """Extract the dataset. - - Args: - filepath: path to file to be extracted - """ + """Extract the dataset.""" filepath = os.path.join(self.root, self.metadata['filename']) if str(filepath).endswith('.tar.gz'): extract_archive(filepath) - return def _verify(self) -> None: """Verify the integrity of the dataset.""" @@ -342,13 +354,36 @@ def _verify(self) -> None: metadata_filepath = os.path.join(self.root, self.metadata['metadata_file']) # Check if both metadata file and directory exist if os.path.isdir(dirpath) and os.path.isfile(metadata_filepath): + # Check pairings of all files + _ = self._load_tif_files(check_folders=True, load_all=True) return if not self.download: raise DatasetNotFoundError(self) self._download() self._merge_tar_files() self._extract() - return + + def _verify_pairings( + self, s1_paths: list[str], dem_paths: list[str], mask_paths: list[str] + ) -> None: + """Verify all pairings of Sentinel-1, DEM and mask tif files. All inputs must be sorted. + + Args: + s1_paths: list of paths of Sentinel-1 tif files + dem_paths: list of paths of DEM tif files + mask_paths: list of paths of mask tif files + """ + assert ( + len(s1_paths) == len(dem_paths) == len(mask_paths) + ), f'Lengths of s1, dem and mask files do not match! ({len(s1_paths)}, {len(dem_paths)}, {len(mask_paths)})' + + for image, mask, dem in zip(s1_paths, mask_paths, dem_paths): + image_tile = pathlib.Path(image).stem + mask_tile = pathlib.Path(mask).stem + dem_tile = pathlib.Path(dem).stem + assert ( + image_tile == mask_tile == dem_tile + ), f'Filenames not matching: image {image_tile}; mask {mask_tile}; dem {dem_tile}' def plot( self, From 9d0f76f218746ce8c204252119b20481f8e3fffa Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Tue, 10 Dec 2024 12:12:44 +0100 Subject: [PATCH 4/9] fix assertion --- torchgeo/datasets/mmflood.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py index 2cb30eedba7..611f11293f0 100644 --- a/torchgeo/datasets/mmflood.py +++ b/torchgeo/datasets/mmflood.py @@ -241,7 +241,7 @@ def _load_tif_files( ), f'No images found, is the given path correct? ({self.root!s})' assert ( len(paths['image']) == len(paths['mask']) - ), f'Length mismatch between tiles and masks: {len(paths['image'])} != {len(paths['mask'])}' + ), f'Length mismatch between tiles and masks: {len(paths["image"])} != {len(paths["mask"])}' assert len(paths['image']) == len( paths['dem'] ), 'Length mismatch between tiles and DEMs' From 37ac4ab35629deefa81dfef045d0a869411ac059 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Wed, 11 Dec 2024 09:47:40 +0100 Subject: [PATCH 5/9] updated docstring --- torchgeo/datasets/mmflood.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py index 611f11293f0..8f3dd808d50 100644 --- a/torchgeo/datasets/mmflood.py +++ b/torchgeo/datasets/mmflood.py @@ -29,10 +29,11 @@ class MMFlood(RasterDataset): Dataset features: - * 1,748 Sentinel-1 acquisitions + * 1,748 Sentinel-1 tiles of varying pixel dimensions * multimodal dataset * 95 flood events from 42 different countries - * hydrography maps (not available for all Sentinel-1 acquisitions) + * includes DEMs + * includes hydrography maps (not available for all areas of interest) * flood delineation maps (ground truth) is obtained from Copernicus EMS Dataset classes: From fd80544f6b1bc36100d7be282ced1169cc038d24 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Sat, 21 Dec 2024 23:17:42 +0100 Subject: [PATCH 6/9] updated test data --- .../data/mmflood/activations.tar.000.gz.part | Bin 12515 -> 14483 bytes .../data/mmflood/activations.tar.001.gz.part | Bin 12515 -> 14484 bytes .../activations/EMSR000-0/DEM/EMSR000-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR000-0/DEM/EMSR000-1.tif | Bin 1486 -> 1486 bytes .../activations/EMSR000-0/DEM/EMSR000-2.tif | Bin 1486 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-1.tif | Bin 1486 -> 1486 bytes .../activations/EMSR000-0/mask/EMSR000-2.tif | Bin 1486 -> 1486 bytes .../EMSR000-0/s1_raw/EMSR000-0.tif | Bin 2522 -> 2522 bytes .../EMSR000-0/s1_raw/EMSR000-1.tif | Bin 2522 -> 2522 bytes .../EMSR000-0/s1_raw/EMSR000-2.tif | Bin 2522 -> 2522 bytes .../activations/EMSR001-0/DEM/EMSR001-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR001-0/DEM/EMSR001-1.tif | Bin 1486 -> 1486 bytes .../activations/EMSR001-0/hydro/EMSR001-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR001-0/hydro/EMSR001-1.tif | Bin 0 -> 1486 bytes .../activations/EMSR001-0/mask/EMSR001-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR001-0/mask/EMSR001-1.tif | Bin 1486 -> 1486 bytes .../EMSR001-0/s1_raw/EMSR001-0.tif | Bin 2522 -> 2522 bytes .../EMSR001-0/s1_raw/EMSR001-1.tif | Bin 2522 -> 2522 bytes .../activations/EMSR003-0/DEM/EMSR003-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR003-0/DEM/EMSR003-1.tif | Bin 1486 -> 1486 bytes .../activations/EMSR003-0/hydro/EMSR003-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR003-0/mask/EMSR003-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR003-0/mask/EMSR003-1.tif | Bin 1486 -> 1486 bytes .../EMSR003-0/s1_raw/EMSR003-0.tif | Bin 2522 -> 2522 bytes .../EMSR003-0/s1_raw/EMSR003-1.tif | Bin 2522 -> 2522 bytes .../activations/EMSR004-0/DEM/EMSR004-0.tif | Bin 1486 -> 1486 bytes .../activations/EMSR004-0/hydro/EMSR004-0.tif | Bin 0 -> 1486 bytes .../activations/EMSR004-0/mask/EMSR004-0.tif | Bin 1486 -> 1486 bytes .../EMSR004-0/s1_raw/EMSR004-0.tif | Bin 2522 -> 2522 bytes tests/data/mmflood/data.py | 43 ++++++++++++------ 31 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 tests/data/mmflood/activations/EMSR001-0/hydro/EMSR001-0.tif create mode 100644 tests/data/mmflood/activations/EMSR001-0/hydro/EMSR001-1.tif create mode 100644 tests/data/mmflood/activations/EMSR003-0/hydro/EMSR003-0.tif create mode 100644 tests/data/mmflood/activations/EMSR004-0/hydro/EMSR004-0.tif diff --git a/tests/data/mmflood/activations.tar.000.gz.part b/tests/data/mmflood/activations.tar.000.gz.part index 2f34189f45603ddb1b4c368042fd565342e23d40..af2e6bf26ee565a55513f2c2c0ac7f894857dde7 100644 GIT binary patch literal 14483 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 W3IEsdtT+sY?5C0#{Q?W|<^KVS23GR` literal 12515 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

X_jjlmJU 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 F{0|Uos8Rp` delta 1040 zcmWO4|1%eM0LO9coT-oR+%R7z=4R|8&G?|i`}LyMit~~0ky5^lZhZDh9QkZRpQsO~ z_T{Wq7sstnG?i{ErZ2v9o2cZ=Et@RVRBGjOo1vON;Q7nrp8d=Fm$&rmtDbsWB0+A= zjIwPU{&xdYV&&YZt3++VDh`$X0l(ZmjB|)*MQ0in&ed@1?|=pgIJiEGVKP^KnKp(^ zP1~9K;t*O~eQ?CmfDX zG{(Ms8S3Kx9bV095GPCFV4tt(9x22P&4AcB(kG5~J;syAW2o@A!GUwvkkR=*OY2&3 zv*ac|iiqZ%T936kxkymCFT?jbQ(DRkD0!5_05=ORUe&X!v^YvDJjyV4=4iJ{%IS~%v|(dQd4HqX`L zqL)5>=1o{}-Isx~6HD4rJf`-A>gq-Xo9^dRcVirZ$~Q$o@+_urxG{P@j7b3*w5;38 zJGcGtLCF*B_bL_h+5!Ayav8<958z@@0ncWI3ES;@97vVXsbUe~$t`$rCkPALAf9il zfnK*ByZ0sXgm)&7_VglVCY~7%UyE%|rJ}UVO2v0ZwJ?b6!^EmY`aV-o8*ITVgV9=;*u zE2K0TjG@BDoW|v*RNMb9+V{z*`|&P@N3BFaWGbAjBpmxy)ec!`2o>(_c)mM`X6+wy zzG6fK-*ZLmh3%+uGvc@D4WU0c*mBQ_F@!F3qC7JUQ$ss&OzO&Y<+Di6H>adN6+xa2 z$Uo)4LhTMdUtuRMn|+2}sf3$#UM%|i7L+A3_}SB&L-nDwm^ujkoTb-P6H=JWOIQ&y zjKVLZ!lKBT#!Z*Rbx8rU1Du#J6~UUt91b7eN&lJSqF`KsxqA`J)#zZXcE?-m%y}gK z0(3^N@w9Oqz1v+VlFr~^r4Nnztl@arjI~agj5b{>UWM6mL=}kYDoYRYbu+U@fhmJCZS41cI8%*wddS9&lc*;Z@`NnuJuG4^#8iitChbbM?<>A`w*_&QOgisGBTP-+Y8*;$wk z-yRc={pmyfD+wIc+4IG&X5@7$*)$zU$3!`QvXY~4se;mt3ckKG2G=*)LKmu$b3UpU zI@xtBwhy9j=Q=U%KOxkvn>Zgi3i$y);V~GEt=5Ye?t28au?8&r+JZ4v^=Q16$)V0K zIFUap3NrVgtf3UsSst{%vyAtX_QR;EfKvurx%B4opzj~3pxoiikJ9^uzKt2@W_R=K zk~s`%tmcr_n#qRrM0D*vCEBOc7~JB*lqi$HO9y9BXS0gE{l_q_bLEV-4tFzNA!l0{ zK9)JK&E1pD3PZS_tirnLmneFrVw^Uft5Yq|ELBpMwTCaXhHOjF;}uCAmJF~{Qu>VnZa*K#j=wdzT)RG>^`AzvIzYzZ`vSIl_FVRdK>0`^ zZuU`VY@Z_h-XOH&p1eHb#`uzQ@wjjk)oBW`*gFY_pXd1U(qxgN)YRa9G5z&iFvsL%R{>ho{WWHf|0jj<_n+9TO2QQ(p41Psc(IoTP; zsPoT-)$w`wEX(4O;3DY9NAri(5n`=DB-eP11iW1-$3A4SI8|_iO*rk|D(P7!<>}h{ zXms1iO7{g^8nk56(FCr4-vCimI50Wf~4 AaR2}S delta 1040 zcmWO0i&N7D00-~^BG7PvJSHF=B6QaDcUq5WTSmb4f!=9fn4Su5rhwQI00 zbUssFtzpWvk?~4&f11 zCPgna7olp%qbym>3RykOdBcbqHz8VEEu8+&8@)I5iaSat*48fLsar`5sZZeHU>(YG zUHDD?`;3nBr;Tp_Rbv4NxgDgK6)i%`$`R;mY9JrIt61NX#7>v5QMo9dyUiB7yJ{G| zNt3wnFddeC8BEiZ{DXPHcI>w~D9Y{*)K(uX{2kI*bPjyRpT_gm*6=RfrGjae7M5pO4$q ze#D3`nh#<{#Cy!J`4CZuBBAMMkfJSk7+S5A31wDPC##`4@*4({Qdv8u;*$qXxUIZ` z{RMl_bY9B&@s2`&$Y%DKvf!}5oepYW9(xl=?}rr#xe z`lem@b6_r?Wb1I|(q1TTM$pTp0*@qql+7(i<&jDu@qi7ZEUu#4u2dMR@Z{j84eanK zLC0(hikd&*ilzWgm&$qcQXJ0&$SFU54>Ji;YPV_p_~j>N=$%6;mt8||a+NS@aE88k z51r53BItDnOH=fiC@Mg`&LBLEc!4?JF68BB(@5{?!r;I$%yE2-?Oq#L-WbU3Ifa79 zq2+woGz%dtn1lZ(o<%rwygD2gu6pBnYc2{6mRuTNg0{v7xUF#)(UiWHj}xv7`6}|| z+E{8c61l1IC3YG5(9~lR!oy$TUgkphk5vH?Cxr9bB)S@}A*fEmD!l^(JNGeCZp32u zRm}I4QLOn%P)44^FaOpFa9hLtvVFXNBaCv}_Rp(K%hD|4aw$czh} zQrTi=f~)^bV};}h)N2kQV8dJ{_wM4T^$pw@`yG=;=aF%D36l z5Xzy~at2?I7BfpyIp$i3>c7tl&B-OyE|>H4@LHTYS0&Qan>nb@fxAjd&5nx-u_t90 zZN2npI2uCZkwsjxbScF(V`RtJ(Kg42<>e+QJraP^hucv9-I%mg9>Y-IK$e=VW5}S4 zl66+>oa(}hHTxaL1AE;~FmHmdY%fn(<*qfZ^+=U(= z6|%+^iS*Bjz#vA`E*+6<<}F6Nd*E$lA% zitxNxY2()AEK@YO^Vsok5velctFmtN4D6=e-6=8Q@?5y>4W{Z{3C>nlV&ClbTu^#P z`ogOi?^GLbKvO3^aB-(TK#zOROYr#kQ`j9kz)$P+IJ&<9A$u2dpLPRZ_8H@Zrvv>b z0_e3pf{z!jVQ+T0Fn{PwYo`Jz+7H|kV}CkQ?-@_|iur8$$(2@HKjbs3jl6D}2cw$T znC+J$-qP#Qoqi4OPT@%Mo<;qH8z+5S>8bt-eG)^~C->l&mQ2QL4kGVmCUU!?Syt!B z#EN9z?p%sS?G9FYHzTb7Jkoj$c~ND+M+UD8aPBL3Z12<8;*(+|5g1>&tHt@qHXKh6WJb_F81bSTnb5Cr1_jfvov|cCRsw z{k{*SS62mdVD%h0TJ=FQ?aZS#I#jQo#gg_QUNX-SB0Ys({ delta 1040 zcmV~$3pCYt00;1unk09^&Z!ydPL!JW%`1=l`+iYgD{H2fJc>eHYUQ=#{5#2|dG*j3 z#g4==ZL=QBW2Rchh?A{#ZqezSyC!RS>v1^P=hLg-tKZPOK=SS$$L^0(cx6*0rBNfU zsJMVrH)I(4)q_D-tAN9ULSB~Rqi32VUgxMW)wPuO-8R$Nc{v^C1Xt~MWNJ}0juhO& zdb5M9kBQ~8tveZDlZa+bIP>&^=~L^+y!L1si&|8KD7h)G4TJO1ynCla66FU{p_5&K z1;!Lx5|;B-`xzl>cEC-mhMROA`Te6pUhG8I$P}KvszPOeA2*h#vDnv;r56vN^_CAa z8)nhf7)mi3hME^G2zngD?U~j**!L7g9^WC{^DMG_{}2URbQzlNiz{hK_~N(?l`_eV z6>r6{N3CMaybZMu6Hr;JVfbi7SnTS=JHakoUHuuSBRzO&?-&eoJ?S}ekV#EtN>yPU zj%}kbUb2fB6U+E))Dgqi4A@r|#kF=-IA*zq+xCs3HPl?>Dr&H$F@`(u`LbYa3FFSW zQ|%PUIw`gilI{-HUsG`8-&rg&3xzf+06pt6loN%2BRu^*D9`C~$L9$&a@XT;5qDrv zlM981g7(^l%y2ZJu-(d+Zl=7aKMwhx2CTBphe`7jXmxyf+QpRFI{y4PIuWsFPU6#x zGRi~mi0>^FX#CL`()9s*rgmAfyL(WW$_9|Xp+wv)aOL%(Av_!yL!9klT4@iVMQg}M z11ngPwVtuELGg{QjtJAYXXt-*XnxZPx!j*7tDhmD>NQf{_u|Pf1F0SA#8)ltV*E*= zGR$@lmv(!yC$Jom<8Q;Yx(G2+rzB0V*J!)EVQ&`P5&X~ZI5{IVVN#fn)}#4 ztjpjBArMWaXj*H>&EHOoshe+*n0^Lxrxq~K&xDulzs8&4Ec6x6V_%s}sC`c$#%Bc+ zh8+1gxI=kK_#rq*3q9{;v@#xtSMqJKvZ(;W+0p}SIOWPV&n(2%%a~hYj+Hhx?06nT zr-mGi$aXVA@x_2M5ZbF@y&X=XHyljZl2JA0Jt z+7fxgFjKU7*9ePkkI}zqvl#5D1!f1Jmu|)*PxtfmWIWGH{;OGT5Qxf_+x5@V$NFaWk=2oycta}SaSD7tXT8YB>o2h C@wVsy diff --git a/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-0.tif b/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-0.tif index 1901e098a59138a4091e5feb09c5feea5a2bd4df..26281f81091d9bf74fb9fbd1825743c0e327b29e 100644 GIT binary patch delta 360 zcmZvYv2DXJ5JfHW2ng^MEL^DI5FQ{!M)3$5z=cN;{96?+GJrU5L*3&y-w6aGkv$z!Bv2rx{t0onfm0)VC%hQ z%ydev?!nG!$F%7P&m{eSN(R_%;g(*^rS?BsEWEpfm{ zJV)$5s6!-1X#a zWQ6>L0#qq{vOazP-a9YP%XD36=U0j@XjqswO{cv@B_Fv5Wgj%TUdcLT3wnq52DWwv z7IZPwC)iz|z3Ib6P3RN&7TyeJcJ|fuI$5jKw9s1(#GJ60_y%9Fl}6}$qF#~CaKEwo zlk4I-KGR4<;yhqm&t3uHZ=*8F@beaEWHh1zeooap= sjd!>SY)${FHy!cW(!Z{%c+eZ|b57QCdv94U>tY{2_|$o7_oI9H1=1CGZ2$lO diff --git a/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-1.tif b/tests/data/mmflood/activations/EMSR000-0/mask/EMSR000-1.tif index db5063050eb39a1fa93a9a282976e7b520986b3f..ac5f9f9cf7a0a2dd623d158986b7715d7b497abf 100644 GIT binary patch delta 389 zcmZ`#yA6Xd5Eb@0vV;p46ciL5A!SMiNWlncrR6sNqYqL)T}MK zz+fCEBb>=GYeB_##F3m=m0>2=%IA7g27Z;qnY2Tc5J96ew%YADRJ2qjjutq|{>Efm z-(QL#tLm!m;F)Bkqpz|>OSN5*OvjRC24

mV(d3_9oxr%a6CXrs%1_nLhVChnUfN Rv2EqENx>_!OO7Quy#YFPXp;Z{ delta 383 zcmY+9v5mq&6h!g*TUv3}N zIRi9Qu6+R)9lvL!4b8@8u=z&srHSIGbCuL!|5)CWU?u6#Z?`&I&C@7P$~cDw{VC_Z XT$W##G&Nv){w7c6dWPYEuaBhXar4QXatYo5%Op! zad9Ad|NhV~AieIXl3dX|cWnOe3ol8r(g zxVuIrB%Q&(EX~fIS%XW|r-ru|Wn3#|w4ymbX_3_BcV>id^hx#={_p1n-AH)VPoc-Y JdR1FjPCpOUTHXKv delta 339 zcmZXPy=}uV5QQP~7!WWE7A#b#&>fIocJ%@s9^+O1TdYTpP^UoaaYm-UJ7LChQ84g3J_;EmM_cmrQTs$tUcGm6i3 z@^)yN5ucD|Eg>Tx)tKD}t2_sez3s$t&m`P>)$WJ`@Ng4t``wWPQT}^LuuPc@UnrB& RQ7+6%*uK(YCH*nHd;>I2U`YS~ diff --git a/tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-0.tif b/tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-0.tif index 2e09c9467b0693785ed116bf59b21ac8c4873671..0a587035d2db25b9ab5c75059e8ee6b9132e78b1 100644 GIT binary patch delta 2072 zcmWO2`9sVJ0|xL&w?>*IcQJHMhg?OO`94o{nhsr3Y8@RqY^|eMBVrRG$1y8L&Kt&+_mSDIWhV9Lsm zkhdk$^tc%Z#0nhDwBVlRQF3!TL*DQi1xZvrlALpp8#{wPPFqNIj4_R3Z^}#lT7<`j z^Z52Fdya^=;*nYIeCVG@>me?5?7oV`tvYmTT)}``J9hn;iKyaN=$UIp=N5m?zNI8O z{fg0Vp+ha_3H)Vo0nX@p(J)@bg`b0waf7h*`tSI3A%?o9TKw>`3v>3C zVn*5;;naYXfr&0uUs}b>`^wS0HV>+ZR_u2$#W@<=v|1OG&S<- zx@6rM}8_XQjDk0#5K5R|MNYi%tbICkNw%$Wn-FQl0%6KnlKNep!;rT1$ zxc_)DvRu;{JV%eVCbyy76fa}oxhe9q#bNXbH>RbvGt1P2DGr#xaQ#6@yOal`ePcM{ z;6(1|OXaSXMp;nvTAsY(#OnoX*wFn1AFb!mFfNpLL=WYe*XogKI)nzkFL8GNQP`nc zSQep6rx!KYC0R-%i5KJ3Uf|m_Z*CZMLkU@eAKjweQR3;yI4Ux?Ju?wH+njkac(&kM8q4}FAG-T{QxUP8k6iTO($OQBI|cH) zf86?#oida7#~7jI#qi{{9B|3T%;a=D()^6~!ww5MdCD+8>OF)fMtxAIbL3S;3T8i5 z@SS8PA9-&;muWO_T9+W$?HQ7ejbM(ID~soZ@m+y0n{VX`=BPl;3R}hoG+|wME!zJO zgyfS_-dFkY$;K#d%lw2v{=Y$$p^d)d+T3LJ6M7YAg@MubtWMJ6Fy)rfbHz&y`Rg@9 zq$g(!Rs~}@eZDoKNnD!;s$d@=2W!F)}%bk_HdxgnDwayULs=Ab9}dG~P~l<3ugAyp!)t+C(nitxq#H61T|h;Y zl=Ht|gnpV+bF?MRyTZAs%9(lry7;qRjmm5dTC~sSsj~m#Ls=elM3J;VT>yjkV`zN2 z4jx+esBwr^qBDOH?;qce{4NJLB`=3|eT0xRt^!rQ-t^x!h8p^Zgrx@-(C<#SJS zhDj@FS$0q!8s){eA%VQsB;ko~7xUudk9c}g7m2OK7%}aQ&=aTN6&`_A%{)|oXn?6O z9x9DEp%pv*)vd z9CU)XrS%&cDE_Hke+QdHyAh}%=BGz0l!v`S|6D`5=SVo-Gnno%zWDgfLO~)Q%WYG% zWsCaOa)P-g*KM{!Zr)IMRt?7ZlpKt@`yTgF^ca%458m#!P^xSS&@J|1$Mq0axXz-~ zz@0he8&JM<6Z#4Up*?*w8mC7?<(h;Y^NOKrs+5NawTP^)!zze*O&eBOgW(ibS1@DZQd{}NIP4rAIWwGu@a*K(in zY3y8Z26v53pF;SZn1v%5;q&D^_Sbw$abWbOpO`Mb`0<~ zp-S71uWxzNaGFGL6e(zwV#Rj{{9$IM%WY;}tk2WruxDh)SI6P2_W*|fGvZ6B1#RXl zhshUs)WbeTkE@-X*jP1H*kww_{WOxkS;P5ZNh)0mN233-Jz|6AK)F31mP6VglGt+h z=2SjZPiD0LTsDSVvZ&3T{UQ;n&rD|0V%AQc#1`3VCfED{Lqj(P zhT3vjMkuqDvo!hhTTggxjOV%UETL_D0H+UZhJJZIuInf9Y{)vkj?02;ex)$<^i-Y; zOJTqk1r2uA!qe^zF0LNU(?t>%&xquVFZC#SSO(3g1l&4uRNi{(kYE$DoJECNlsKfq k|Arfz3frMJ5+g2lJ^tmg3`R|PB044vqs=ir+T^+7Z-Vtm8~^|S delta 2072 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 diff --git a/tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-1.tif b/tests/data/mmflood/activations/EMSR000-0/s1_raw/EMSR000-1.tif index 24fb55d52950234df1db4e118d57ed2d43ca642b..ec6dbed7b70763ab2e265a6ae45220c13b3aaf90 100644 GIT binary patch delta 2072 zcmWNR`CkqA0)|^G9V*cx%~(q1NZH!U()qq`n;M0p8d^}=Q_7k-BBeBT%95#RGF+uF z)=1niEjL$LQbY(1M-ht2H2#9;hv)e`P1;S`rA^B6kyfY0`9b$E+rJlY0(__y@)@ru zBqF6enCZ_FdCEVKlc2-WH?iDYWz6wjVQe>D$XEkgF4ubk<4py4TWyW4YUM&XZyZ~S z$A}_LDI=$y7Dj%D;aF29!j5DkGAKx-Epo!1S|_ftJB;+=Qk?4wl;dydc3JYPFGBN8 z89wF5F@K>PW-aH?RQmzBeHTRNT}^iA7D6X6j9aIci@fAAgr+Dn@>M*;6>;2|5=OU# zbP+sVpULH_Ot}kIsRZ$GfH!M%bj4f~A3j{3z`_)DD!I*s>g62Kyw@KUKK_WCA`gW9 z#V)LKHsP{!M-ZQGN(bwiNIYafBflAp9}^);b+V)4$uwGc#ZWm)O08xOt{X06V6+2G zW7N1%{>6}U=6bQ>=TsaRtHEHkbj+x{fK=E1+>)As;(7Jx6jPWdQ;5JVLE>RS3p$ls)2E2;YVvGGN z@uj{}*muq1l1E|G4veR_QViD~e2)F<%XzB33=xSwbUN9O-O?6uyz--nom7Fe)_n9f zxY7IFK2dzupVol~#ON#iL48+lSFDz!`MEBNT|S`CJsz1)r}O2IfozlX;q35K;iCNo z(M8TMjhe<$i+fet+9~q5LWqZ>*P&k#$9xh?r){|&I9?rJr z4p^8U!(;#XT=_j^eJv|6(rX-Zrmo?Oo;vs!e-uwN!ypNZ@0p4?Yahpuv6 zj=r{-*G5@!+qiY8zLy3oOLux_x5$Pao+q4VBvEg*E6plxncQ;^(Wz2y-Z+n5l5N6s zmoY-!eq`K>xA0rkBD<#_z|I{RDEiV3=du|7SLr#F$7}OU%nZKX@e(!i0#%vz%p@MP z7)8YcUzX;TVnT{7itEO)XWM)>b-K_~vkC~5@$so7Zny-lmkt#ByWYWY=XSJzlJZf- zI|MwQPVM~P@VkBuT&`zhRlOc{9qm~plQ8w%IJzkfWR+DVjwSTLJSthVw*iZ+>?z;9 zcBRZ@ydyQ_xA8}N5(6qvqxl~-s$EiI9c25q#`^r@uo>GYI29BJha~CQlzL=f$f1xtQmapq$_>ZOy*3W81|GR(S zW5_Hv<+#%(!=2odC(fIx^61?yc=5avFH6js?l*ubmmfMUdp8-Se~u70-i+bt%W5=o zuZMhX91J^bvDRo3_jc$ZK3JXwkN=FNMDLQ=ZU`>{^ikoIb8Kf1!GMo7{ zaI!{UlZ5q3GufN#%@6m2nNlKnQ$127)suyT8_;8;!{{<2p&0f~R_f3TJ0%ZxcQ4?4 zsX87g#&ej{5{{fRk>-)HT)ZWkKeq11ad~qhdrcANKK6KrdLE zs~(N{=C(xypZ*7L{|;wSmIiaRMhjzL1klYmd=6(zb+K*U^$58#B zHJih)A;+=^??ct7qNxPxRg2WC8(=;zkf-cJ(XH5yZQo8 zLN-+5gsC1X69U;CH5dg~`}1VFgf72GXmKZ7bgwjFW_K<;evRRZLmF&1tizLuOEJ4} z3B3(BAggpXjOCfJ3>~qWQJZb}v@3}E(kS{|{}o|6z8DoUnKjksV$fm_sz&QVrB#dB zzxnabv7L}Ca6r|HQ&4LhM2B6$aL@PSH2w8r;B$Rc6>WlIy#_l2)`(oS?|A9{04`}< lK+m2_xUgRxRhH?PcW44^?*m%nVMxi60YFUPb@_ delta 2072 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 delta 2072 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 EKXsS2x&QzG delta 1040 zcmWO4jXTr_00(d#tK127$x4}*bV??a#&X>6`wgY7-Nx!1cgW*u5yx7crsuk$Lc|PD z>y)Rb?zUpm%N`T<!A2?ejN$dL_M*bGzWYI1el{m_G!O>61Z zbq&qIGnk^kj4DG$9HslC7+Z53ozHs&S@~*a$}_0xvk0FQ=Ww5T5na7i9Bz{HRLLH8 zhi|5jE|F#WLkJ1wYr~l6_$TIwMlpT#GO~MQd~|@+ zL|U<=qzeHbWbyvZ1lVaGE6m>q(-zx^>V_D;G8E&cTLwG63TM7_JM9Z9u-BAKeYG=o zZ9Iv3layn~;e+*0uyIcu^OB^To)|MAA>|1EK9SD8@}<<9$^laz9|bwFAU>82&Ie!< zmb3hD5aW#Y0@Kt&&+k=eaj@gu$-9aprO~`G-w6Zu$#ko15oG&iEZgcPw5qSqj1lIKgD4|2!yT{;A>sD^IdJ)x{M zgvwc~`FtW1xr2609zH9aeZNT9_%w{u(E;?dyMm$S*V(#E3iCNjlyB@cK{OIdkBUgT zRHWh9|5u_F*D)aqqT+ET7G03=+VH55ymd2AM~c`xCE;3~CngN?Vl-MbEFEtXtUkr? zDF{KOH5>CUEu{G~F*YS_;m?4p^s1xNpH F@E^{Qtik{Q diff --git a/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/DEM/EMSR001-1.tif index 48645cf75110e0e7052a0a0365246fea1a860f67..5f4d785cf25e8ff272e2555b24edcc3c659eaae7 100644 GIT binary patch delta 1040 zcmWO4`7<1L9EWj)L>zV1RV~>~RZ{1gsW{i}_w$}IL>yVf5h_w<9f{h|IBFB5Oh{|G z6+#$NbwyBZsiri?mZ>e7#Hg#tD(#FTjW|-Yo%#cwA6_%hi1vtf=ZLCwE&UMmK2=hg z(-zxR(S=)3C2iR$g}E(p_-aWHmruH*GE5s+n`NXn+D@(;2GpW3p?e=0!SZ__)^*ec z-_+h@Q*#__St^)wav^cWv1E{xfkVp|q2kGd-0vZ+KRt|LZW<<3)6n&l2j40Sj7Ako z$^N!0Zh*llznK<$c}Pg?V?SSwU}bU*NW5BE<4QJj&6FVcdK8ui>e)|chlCNFHWvMO znWE=Cacw69T0vD5K6x0vxffs)%!QFl02-6L(ED8|${k)on&w;R-qAtt5BFK#VjyIE zKUU(DfvKZXE10}=#@0(Sw63J$PK_1rPVbU%lZ(E`lN8_X51aW}IzRh}vfB?pV*WbW zNCgPe@I^wTA5ua&kd|9hvwV`OU1E?H-o%!4(!sCrgxp4i`eH|vK6xLjlV`y#GDK#) z0Zde4@i1|b@%ZahEv@LI30Ds44jw~M#|QZ9+;Kz)hJect0ms%1mt=Fyzp=kF|rT) zDo>NwTpM+`%+ak|nozcNQ=ydsq`w#)AzoA}0(9~PO8;>xjx$1&D-Vtj#+Y}!7`lQU z;!D+OHEW3eJJvzT%^^_soI>8_3N1ZZBf}fDq>(Kmz410SKRC}`2sL2dlukVzMeIzp z3E2u(*hIX9QkN_dKiffeyAl$Zgkj8XhDv7qrC7ME4~3I8a(vY2_kY6in};#Y)|g_s zte>_+>RGJ9A9v;Ym@v0s%QgKZ&)TFf#x5ZIhy))OR0}_86ElVEJe{ynV`60@2B%Wd zpHRj8m%b$3(KRx&PQpftiZDMT0Nrry5a?FQ_RBL7bkP>B5q+d1 zeMI8TSFtfXNndN&py5Yt@)V>YM|O=+EFsfz2bldW!tuT!=q@;-Wz~ZXOC5*UT8kF$ z^E=2bK?Jd;2bxp{D03kRqBAL2OSwtzIs1{}9)XG%suVgOj`Zt|#M^s>B}bx^zeC-s z_bBF+HXgS|gEMxUMNisO*K`6DVcw{F5R6X2zhvxul6@u%px9b-sPh{rcy*ArWp_yP M;FVnV?&@pwKd892VE_OC delta 1040 zcmWO4{WsMI90zbucWj=nr?7Bcly2?0Lnqoy;$qc4N0NFRfmq>y z_+WzR`WZ|)?FGxXocLB;dJ)1#N4+n;)zHLXql~tlr>Ijci)4p=>7QqOG%W6uqKM-H z{R$pc+fI^b!wJHxgp9lDq^HZnE{#ZB%4((0Mh+tI2QO&->wtm>xw!dKjEqxr)NkT| zua^99#%vepP&q}Id<5IeyJ75e78yPMNMZKFy4;dR2F&60oQ2~q3Ak5wi$;Caq1LB~ z&y`Yd=6Rtp+6J=lkoCPFZ+V`$vU=K+tV8dmb5RohE(vv-$h{{Mk;PnCglkX}7N{>J z4v{9W8Sd|vxF#&1jx8~%8ZSV1^+R;HvN7%WjEdYOwXjJK5+iZz9#uF+8yhN9`|i>g#1gS(S*Kx~~}p&z~wwJYoA?C{(kf^rcx4 zZWk0#$n}F*_Dn&MgoUnO<00+37KGOkv9Om1!&-D63GzZpsvjrGi*CwL`XR3-7R7Ti zMkltJF&e9-vX)bVf~*zt-fxc3>mQ&e(2m$ffV%;Y+E2SPirhCe{nr|`E^bmo$s}{{ z$8%K9_d{`V3%R~uM;=}YK-euJp*8BbN-52r=whlx>r^=}LZj0Lx$@3}rO1VGbUf7N zhv@Qv1|COmFw)TTbV#{PQTza`$n9`$T!6mp8S;q*t_6Rr<0;4_pu+7h*sT&;dlP-YINrci~ zLZScX3o%T_WN-z2aq&1j4AM|5O@~Q~5E~0DEX$f{5C%+-R8U24Cq+0qdaK_nJL?@SO4aHIFGMYf$v?+ z=jGqZguZbS-ym~?OptK*;(jDsEGW4raWBu72}azjxgX6o>OcJj?tPc$f9@upC+9EE z=IA(0968@`Hb=*4;>eA8%+Yb0IC2vnb99{7i6gpe?oi-H&USGt&hPRK8{BZnNh=eYMNP0AzuL_d>SZ8l0Ezj;- zAJjhzLQd@J-6QwM_tn!B1%+sXX9OyzxugmAU1wnW^1YNA*2+qnS3)&5p7h%$!BhMR z##cnaJm(Vgi+VuaZT@7g8VmW>d2mQvPsVXtv{kxdk;0lLoiK(|z!ehIMkwfRql>p2 z$hIR6XOz`s=jeg(P2p5kAcg+(FA$+Bqr!W4X!j0Z9CxW;c8ijrN=?R%wUMZG-A=Kp zk1?}oJ4Gjd1cv|vb)Rsh%6v;|b6E|;wv&|9GYq{c1+=Mly>^I9fu{Mv6cFUl<#KbH`aelLQuJi@l=(>lt<_98c zSP;3{m|*|GD%xyjNq1+?L5t}!teB<7tLypH;rA8RTB@*5<*YH}eTfXOOLXj*H6o;T z2;Fgu(o=R&QtA_?d|StQ>N8n|%}9zrWdXg+13t;NTA76eSFgLU^L6bMP_jk)(qSr7 z8?IB<%{#1c-gNfpzQ9T&s%id$I+}WBCB{9vP22KHi4>{YSJ6K*!3qUN2@d8DLuO3^vZMuc%%q%Jo)wYumt9nIyPiUX3UaTUiImPTO!zqgPHI2w zbm$=GoAIbUGz(08lV#2ggzMxgs;M%CU~7!Gekt%wAA+3XXOugB1tJYjFm_msx{L&v zZ{9`4p(@sL8U5lml{v{V&c6Bbf%eNu=QUwJ@^5OE%QRQb~WXt*`wlN zJxgJODXnTO8ppNMsp0;}%yq_;ZV_%BS%{w!o0)Qf6*NorbS-Z(q{Rxvu@5n>Y%eRx z+rr$w_kr{11~O}_Ci`G3T3#rDYpXTH7HYH{cfh?7OJFb?N=MIMw@ON^l%Oj*2;lHk9^9k= literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/hydro/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/hydro/EMSR001-1.tif new file mode 100644 index 0000000000000000000000000000000000000000..a0c043f0d313b79b0c1c370bafd3bc5f9c71500a GIT binary patch literal 1486 zcmaiydr(wW9LMi2Xpp$zItYpgt0;m92<9Nj>fjuV-1qJNAN^>Oh#cVuHG@4P3ke;*$tGVQ6rJz`k*=^xU^ZJ77u7#YL+^ef-F z3G0_^vfz56JDXr*h6UH3`84KbS`*HZc`9?g)`Al=Kf%0QEACzW1?D|h(Er>hEB)808xXcKF22TpIQI=S?wJl zk$1QSU79|}%NevB(z3jzYxUwGx??YuPB*zqhR$Wp1X| z_KXkcjU1Pmv?AZWXjxCapkh@dKQ~zE=rb{mQ`Bhq4<9trKAjXB_YT2oA75;B^q|nE zM`@w^A5;@AM_#-q?br~9V*f-0vuj7Lq!z5h zNGzANQrasoWaW+(l#8dKxONM5E%8H4{?}BY^ust?CrW#hO;5BNY2xN!{)*8bkIvUq zpT`zTe%mZmhFqp@*#vUQxj<2Ex5+cBo8F$-LzefPNxH~Dr~S+L zi_fd6#%rHY+BHgOFFZg6fqSVuAeqK$9#BflUD6#%r3=@RVXYEy(zuH*MP>`qr4f)_ zy+N(|Y>NE$6)6{dgxb|}g@-2-a5`@$Bx?-R)}lsqrk-v-p9lZdL;1(&R$;&8N?bnl zLP!|gOtX{2@X$qr;KrHo)fV%Xg~6B_TTOG~6|fJfrp|F4f{S`R6^JKelSMF!OJdP4 zH3^2aRoF2pgu+Hez$$e>$pU;vNa^m z(voZ4Y^b*kgP?Rm*Re=ptFD_oysdjZz8_~NpS0EqFaJ5 z3M$pO-r@!;cQe#0zT+>Ybdb-@8sX~hIS4%^N86tR5%4S-nx`-MywBo=*03j3*HTO) zyCu8A z@vLkFs)}7mD%U^}G#e}SyCXQ1N5W2T98zv4$F6V`s$<}ORKPlY2=ZMLpyCE#v8^Xu zo0edwxeZ-iRzg2YouNu!1g~ft7##+|$~ytk+nQ-XktrgwWXPx}pgkRG1Qa!p!wMw= zGwOw$^mgHX$~a+oq9@{fqHyQnb+SlMp?$+(>}#3~zq-+svOZT(2MV}#{VfgRH3%=2 F;%}<*;hX>f literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-0.tif b/tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-0.tif index 6870d674ba1a730d99d114ab24dd75a21435322a..3cb5a7fcc63451add201e94c71af4bd2e918d890 100644 GIT binary patch delta 407 zcmY+Av2DaK3`Jp!ItVx+VBsR=6&b(-r0@tH;RZ;N5j=w7zd%Ot04Z*Qt5bRe4-f=- zvJxOE2#Dkt`9AOG`+5Cn^xnEg%Njzb*EVS))?m8D2Yt0CxtVstL)O-sdV5kI(L`$0 z$Jpr_lID;X>wctOqze--s5VQwuwiY}!eHmQGicBTs~AZ=y#W)|U&)D>7iizG viUI8B<6Il&`r?9oU`*|2O)(#5*>N_bFZHeIz)W=vHvsM*1V4`BqBwm6c~EgI delta 353 zcmYk2yA8rX5Jcs>J#-QqY~a9wOK?C5E`Y!#xC9qKKmi1ZNeL*yB~Spe91;*v0tFB- zcLs^TlK1mw=5|_7>+xP-7b5ylVhD!TmuOjUU5(zc?*vzgFo`E0ap20?D_XW*-Y|>g z^+V9Kf-CWk*cV}~c)5yPnk%{@%WSz~q0VUI^+dxB)i2KQrfN^6IgtfNN-@RA9Nhf1 zj5)?27@T6r9*T#M%#oMaM)X;fREp@B(}e#iFYB2OwN%L^nIHK*X>P<9*^)sj+#IjM a)eDj3$xD*obMnQ=HVpkgZ+M1%SbYG$Bx4@{ diff --git a/tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/mask/EMSR001-1.tif index 4190927481286678d1af511df915e7780695fcab..9e12af8b2b7603f82649873a10072856e58985a5 100644 GIT binary patch literal 1486 zcmah`Jud`N6uobEtreTeD%b+aBsvidh02b`SFz1X_yszRpi*p*jB29t3q+$pBhhGx zsKyV_&?uEUiF3!Cd71Yq95>$!`2GFAt)X)C-_x(rMS#+=4PiO7U z->v1}4|9vb`uj}uvby?s^|l-_>9RLHD1!YCzXq=lh=;>p;pDUB8n-cAsb_sMHPpC? z)gR7KMavFkj#Io=babKF>!opjVcquVH35?|Vf(vt{%2 z?X1=OIX(mP7CFZmW^HdZ=BaU)%@=Xb5jj`1hZ(!a=BQh=oV&|3)%7sP-PDLy_ceM% z_5EW#s#(;i#%p5D(B%{ I>=pZ+Un%N~VgLXD delta 331 zcmY+9p%H^X5JiW(C37>JIDv?WpnwQUKtx1DL_{DdAq7NG03yS001+8M2~@yP0tFx_ zAs>!OCTj0?|GxddkNT(`0=)DAjkb7-MHw2LmnsgevKRy3%XNY)-%dNE%HS#nkI>?# zU9AL9wmTx`z~IjHg8!M4lG2v{H&LB7+bWT3Usl|mx}18yC$-vRP)BsKM@LyhN39l) zV_42M8H(_Lis*lh!HLd)?hU5JO5l(v>z&#a=R-K+1sUB{#LqnEakgf*kzpo=N>z-E Smj(vCuwGR1b&-*t%I*UwAYXC- diff --git a/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-0.tif b/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-0.tif index 65dbbda8e48cdd56037ab77e13618ec8c613ad59..5e06184db96fe159350284c5c3d92a05e4534ded 100644 GIT binary patch delta 2072 zcmWNQi9eNz9)~Lta~k`WJv$*JW$nE0?<>bLj+A}hvSe$c)hQKP5G|5sbW<`+rG*w9 zZdw@XQlG9Fq%>qnDHZpqaQhRU&+|Op3f&4d-E#$wUP>tSK1iJlT~OW-3Ws(LMAfV# z!y7uV?|VfXm6yZ!pIXNBjR%4p%~05Jiuf;w=*fIF@Jwjg zk!MtztO#wxO7>)PCt2StV|M7Zv7U2=*{k)2=(>NErh;YhF+2>iAD2TTltGS$zzAkL z{b0Cm8A`5ypcBzNHaGq=4T}`8dx}q{5$Z5Z9$`)nXd>?*!>p@VgWvy7q|i}EZ2lBQ zovn7bJN1se>1#yYe|V#7i6$frt7u<|Hcg?3{4(Rgd6CN|#wufCrN=qw)} zp6Aos*M2C|3x(dqWmeGm%?wLt6%qNB$F#bYkObEfjULt{5!b^m9yv}KX&a#=ErzG* z8F**Bm|Wc5Sfv7g#EEXRE=|K!?LNfXeNo4Y%EkDvw>>Qr|Dmt0n3`p6kaJa;1_sTr z-I#-*@mXHM7T-CVFdVbQ`4lSwaub#!AUGW78=5F_=qE_^j#0V%QS$j1gUgQM zbRmB}jD`bYf9@Zqtiu%tO1=;$X9LcL#p6hRI}J?Tp;_zI=pQVh#Ns;IG_REMK4sIX z8&%ArJ_$Gsm{3)vG}+F%!rqOHqkxVW+_=0Lt~RNd+a+kA+OgBjuWT>oo(}{2i1va(s9OGzah3U(*V{+-)N|(gMGgbsJz5S)^{0L z`qYxISqaPOKTmqe`k2GBKv<TIO^{eKURExtm*|y`Mr?nX{^nPIwzKA69x9 zRIYW1p1w)J`iuGGxAPs{pYMm8DT`3du+Xgg7j2bZM`K}ENKtAPB+XjcZSoIUCfygz zn2-eVwz_ba?PN>t29P+nn!Ae=OhvC2V1M&>%9PrO^5>68tRa>?F5Sr{|A_0~5iOKs z?1khUGmOW&pkdS&+1fgIHJMBDY!{7&dE<&u$l7%!L-Z~Ihs#oNeu*oxTAC@PSPCy{ zIap{rOAX)CQTDTgfO&nw80%FRLBf*<)0Q3@@3X=MZx1xr>?PV3hF)$2&gq8W>n0zZ zkx@YR!3kP9tw@iP5>dmcAvX^`i5y%Zq3c0c4$QEnJaI(0MZ$ORA`RM*JT~$|=h!q`>e@l|n(?&e+e$osMzG$Sj<$Ol@S+s_G%QJO#wt{w6w5^T zTTn=Y8qA7kDAMH+`8<(BQF{`k^P^FHSQZ*}tLcuE9t2197r{|26?_Lze8><(LD&m! z(h5m9Cic_#Lt+NkBa3EtwEcqKHOdOnN0cj zG!i5aH}gK~vd*UI1{+ey{6>Ejs*nJWNMc5gChpmw&m(|30@@kf26q93Zc)t6dUaMM zZZ$aC3(&iI8wE<)QmW10R4uHhW0v}ed{ji@6NA*~84i=v97O!*BWVOBqFqZI?owQo z<298o|4b9{QOGtpN{7$6p!mn<&v!hf#)5FRY2`BTSH|MFe=EIIQ-npO7!)7y(SKiX zmW}OmN86-`tOI4>zB!BLDMeFV_tLKk^TL%bXZ8ftl8Ctc=nx_~MiVd!I^L zlEpys!(1Al702-kY4ld;Am`r>Pn&@m#M{`5G!Q)+xU35 z)*Kh|Wbsw>kXk*B;cGiX<0?kb{rL=KR|Uf0_OG;5E|bm#NMq`n7dFqag@uU|-d{?< mUn;pIbJPlF^Lj`!_CB-g@eIj6%OT9!%B+_g1?A delta 2072 zcmWO4i96Ma0><&l5-KVOVK|jUB9fRasq=f^i(&{zILR_uvX$k~c6B0CGYzGBlu5~T zRobY-lu*rrOmQfxY@(y9^N^j=9 z8VkpR-n`MJ!G*_`;K9-r&~IPEu5K?jhqR)~Kb?t{dQ@mlfwL@;$A5& zQ7WkazJ!tA7ejCLVvdnX>AEzA|F!LhxYQm2F=KhvdK7nu=<~rf33JXrhA3VIX=@T_ z7@dXDg8@uop5(9H+HCvMfQ~3LfNO`x&jfH~;1$()NFdSWn z!Mbw1K7CnGQy7d!+LutCSMd_+=FDNK#w@k8Z!H@vX>1*!9ARx1+uzng@h{YHJo2rP8nBy2Sf!C#+X~g2j#PLSaLR z@NIGpCKYLOORG6=D*nKEmDyajB9&3OE-2aN!21b<*b$|$W$vgAlH={_bPb)(rt)7T z@@&$#;WvznYLpCpYDL>Os`N>5X25JQHa6%|)Naed5q-k5snc2KV9bSHZgi`YA#J+~ z*Pc}84oL|1>~dhmrVGe&P(kqWH#qrp1;-kPpsX#G{#Q=pz@;qwnc>L-g(Qs6ZDu33 zWiia!!l*YK$BH&XcHW!8lE)k26BEr>51yf@#T?=K2~Ud^X3k)iB!6xrCuI&%y$I7|%$xWj34=L<>QVY{*ub|jm zVar9f2XJJ*GZ%`Ag>3iZLg1^1fQ~x`+OknIe<2@sB}is&jAMPcE@I2(a+RhjrBh_+ zoR&^gLmRr(eunhHa^_4h;Guz;g4!|-_8$s`a?3s#m0T6B8TsK|w+FrF_6zBbThTLV z8m({dfVJFC!FTW0;~zJs^TKEmd`^Y%%KArOUkJCZG-9u9EZh>6IFm65XM&J4li#k}Dm+&DQ|vm6#xe&= z#St_uKZow-IkY-tL8D=XHD|Uvu}$oOyDEnv|7JA@PMyYhnHctP=db&wGFV?1iDO9M zG78hYc_ivA^wyk|to2d~8xqH&QnMK?Q+lDf-&M#{UJ?#E>=Sa8zSRFt3M)N5-V9cu z&N_i@=6|6)zDd#?i*4$H!maA#K{cRjcYwI~^5F0J9k;-gR;nxloX_&Lmqe}juYa{9WZ(%jyS zt#_nof3TDmA7Xho%aR_|c67=P5nR&JS)iAX9?cQlV-QaFe;yY`MC1s&U$3AO=d+Lt zxU(Rc+y0|V6NND~_AQ`x)q5Nsb6v2}JdU56BIw(x3OiXl{C*QFn7MNy99m!EnU$JE zYhnP0t!j~+ejmrxP6~Sm{RHQL4?@9L+1rAx_;mP$hPKAMZ`W){3Q5K zZb4kQDk?{NvQaIAw;a4!8|llA#lcL8)aBHn@tl}&0_BIz80{Fufg{(@@@optR0g0( zQQ(D23LQ_lF)MHqyWPbMvGnB0$63gFQ7;_nl2I>d5z2=$`BTGOs2i(b_t!z4 zU+l-RG#%!xtcBM3MA-Lcve5P_e&6efUYk@jUXG9qYG>nJbvl(J=kd{!omlT_PvzW1 f6sEdUUwRq2pPypfl!i8R{5X&2vzi1glP>%X=Ydqd diff --git a/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-1.tif b/tests/data/mmflood/activations/EMSR001-0/s1_raw/EMSR001-1.tif index 9b85b5c11988ac51b16fbce62c6910a7b3ce7691..4a7aadce480abe546ad2366e6692f1b59cd40bc2 100644 GIT binary patch delta 2072 zcmWO72}2F&0tVn#Qm2qDC4~|dO;M7_`QBGq+EJ9qp-s`MY&p6~%2XyKozP@U$&$#H zB3Z|9r4X_u>zyt_&7{Vkc%H_7js1={D$AR0IHNtj1aBR3FyW0d^R~%^^%Gx4Wxhi0 zG#zSueJbXgN@=IKjeXe_*q!9fD|t@bs#Xoh9cPi9RET`Deu${?;=?K(hBZc&2fxc!a{DDM=Iv8qvtg1PGcIq%OM_%+ce?VA6jfFj zPZR07!)bhSIQ!eWGEN$eT+Kj+6?sx-7{~!v4q*S&D1P&}j1$Fj^ep~@s3*=m5x$5= zI<=Y6GK+&9f@tqN8J>zMj0!A-SH1~HPjSG&SA7v+*albB;=0Lrc(0dx@YIr2Y0eWJ z+B|SzTKRXe=Hm$3Jl90>$Z{N5;{>Cssk|^u0d7*`w$wCis~JqaY2!rv$%Rx>*I*yX zY$k6VLzVN3dA`el!MbiZ{>+}A3!CBp*_R>n1+DJJP=At!&~nztRr6rVFJ42I)-tY< z(~mO!Zqe95CYYupOrtlyirM(u+$yRHq_p+BiTg#}*qk|VGowyhIPOpt9vFOhODK1Q? z4?(s>l{d}2X}c^6H*Q$4{KL>h&v@LP&UZpOgMGkLg8Yyk_|6h8QWi( z>n@q{Q1%0~?G5lF=0&1dWDb{J+wLUZaOG-?ulX zsJX)aqc_(zm{4m#0uLF##=(3&tVp+{yN5o#n;mHO>YLa!^cp^Wcjds;aumtbSvylI zr|Nw*uAg8{=Ud*aP0GZc`_U+i3a0df7ag^HL{o_!y{`oDKVq;b8+ZW8hPz;+SOrCd z1>eSGh>ulvbQzim9?usYSyH#Yt61%^ zl-3?9Tz+^Jib`WSr@dYLS9%BER?Oqope!-7W(fABThrYxnuE`n@KkyV;yWA}5~4)5 zJG0eyE745sT_> zp-;+Dtc@#&Z@4`lw#;XyajSGk#%;)_t22ATY)Y0p!r|67 z1dr;&FAt2^bYGv_#(%)QPc}?jZOhKNo#NuHX-McOg4eM(z*j4#EVW=*;|xwMwW4cs zA8L1h0$mG{5#hz=kw2u%b}WO+fCUWQtw~>%2vOVR$gb}BTr@ul5sg7KGL|cFL?ReD zONpgN4B5M`0RK$7i^Mg(=uzcP?U*F2eN+d-spF_f9Kk=A?}7i)24qQ_utD-3Mt;@M zZu+U>-FhrY>4vmbm-(85_+*C_#!oiI?$F10Cq0UiN)tYwc|$z==0zvT5-u>)U`w)$ z6XmTl*j;lLr`H`q+6f;XDvsqi2}K6$pj2Wzq;cf$2W5Bg<-rz;yUS!lE9-d+SJujc+-IX3HjfoJSw bt`4x|$g!>Hb0x4p4~J}p)#gxM{MqflO^{m} delta 2072 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 F{{yiox3B;J delta 1040 zcmV~$4>Z&V00(f2L%2~KZDWeazp0U0#NF@v^*7aI{4M035>ZUus2LE)O~A5qkwQWb*s2hX~}|}3()mjv2Y^R1&ZVI zX?m*?F9XY9T;a{KdD%SXI)tGWA91&CC94$iGIjk1j+ZTEWz2El>`|#nfRUPyGSZmQ zV#nLo=TM*@lttD_s5m0Qe18$G_qx+NazOAta1a`KG+jF9(8aY2V?S+TN5v`>I~Bvk zxJWqlSstCYX;C`?FHA8VwPzkkfw^ z_gAYu*&4VUuPxi~&0r*-i9I>=Lo9k1+0#Jh$_wgAXf-QXn4hSF!6jV|I*!+4PF656Qg-ZzLTr9 zj!d`Rjrylj?yDSwUcO7vwX{Ime@!+#zJ~f#2cBMeQz&sd1f?jCQ(XZZ>39h93Khej zM9}A&Ehg%W=-C;<(S#1fn~gwiFP5;s#+PlUm6Y1rF(_sYGhOrfT2uj%{$F@JULkn7 zFXn-XY(aU)iHWn1p!9ni=*LCO4mRY>WC=2!4`Kc~Uv8KV#l72;n6Cc=-vuQT4gPrX z(w=Tv>0BssprZDfwAFY9#z!CGoUIr3?OCLT`Pv64bCn^?FOb6AatwR4BQ@^a9rx(m9^^U&FxNlAVaq?#U_&opJ&QWZUJ*W$2&2Yp_=MWM+KjyGRv`kA`u<5e!>0@91zQl#aYAZiJ^G#vR9Df$s zB=LrWA-#HLv$50wcxNf|58LoP#cQOmc>zbE4zJ9%;9^#z5Z;)Elm)Fw7`qSU*;wS{ z)gpOZ&Z?(dSiN4(sr43Y)hKwtafh&XnG+Xwe1$FPeoP6@=by`~(NJ_BA^ll2{oC4# IT_dUdAE`5~C;$Ke diff --git a/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-1.tif b/tests/data/mmflood/activations/EMSR003-0/DEM/EMSR003-1.tif index a2b07cd672ee26253bbc85b9fab1c64ec415e164..54b32ade8804b0dc5a02f6edeb8b02569538b1de 100644 GIT binary patch delta 1040 zcmWO5`!^SM9LI6D@7!{7jj!+5zHV!+VQh@AF6Zm>el2{}cP_c~MZRa^Y%XCcN%JL6 zE-hlE!=@d|$;ml$`<_))=6;=H4y6%g8Icp_wif0ec>eG>=h?5_uU*rxEN^(|2-a-_ z#Yz#Y5SpMc{4~N`+Gsw6kDhfIvzBPWQz%35Q90#~+T(neC1dmY%wVS9t!YyoN^gQ- zDAkD!`wh9280CQH>o zXH*TP*B)ZxRrxq&DXHVe_*2&HDnfCkCWPzB6kL)+8KupvWUY+x)1_4XZju614Va3S z8kVx}($7Wr>ASNL$kDz}(d9Onjk7`P^*D?T>tR4g4UzTkSXzHfk1z8{y&{xiHx=|= zg99=>bs;{_L1UjLqWkihNZw(O@VqV3YPQFN%~=*N6R`}a${sxX)E;Qq9i#|UO+F`UoG}1zEqa={NNkn3c9xZd$Nh_$56xmM1 zQ)n_%=TMw5Jx9txH%Sm`h5qg4V7vfwkHrLN{hCkDB_cF9T_Mi68ZK~ZXtOhv8Yhim z+~$k}if_q|lk|7WG31_e#oi0a2pH6X?;d{yS85|rX_mfd=%#9o(=g{5;QO=} zbZz%0g~e;YXV4Z;4(`Ld-K{iMRZT(HKE!lfGfDs8$?=o+Nh+=t!@yYzUUL)GmU>}O z!pCu!+my%)z+$jFu{0HIbY{{I8IcGtsHdUp19bDBT?pv-7`~gTxHN1*dh!+GCTW1{ zA44%Jey9?E0L@EVZtCF+RQcZeMH0`eYNvQ&p)NNT;2yt4jsmoZ1G)WspMTX(w5knNN zI-qhbpOua7#8(MI@-mtyagaA&HtOTmy-rrU%^rQv3?oU~>n4>_;)4n1zrjUnZ#Sj& O2%&M-#St9u3Hl#M>b*Gt delta 1040 zcmWO3|2xzN00(d$Ini2QY;n83@vKsgEbbBdcAxj#(6t^OlEQV!msF}l7x%O)R-P@n z@I#Bf8hbEPi}^NPO5fbtonl?BT0PUZA<9FZna%e47ha|*rYRRp*1CB;o@{xZizxFe zVI*iN5?1!e=N_4fkY7vid@u{{#sJRi7=m%C78mme!1*dJ+<#h7KVL#OyI_83S&w~b zwK!c6ftWlG<_xZ+&Co(x8eR$;7DrLrox#2W6{FJnkX;_Z0oU!+pSl74wLh@azF#MI zo>+~Lo295Y?!-}X8tcy$pt~`h)rxv_8Yf_Cb7ACi6MQNph%>c8_2?3Y&N}jOodvcN zkqD}-hk1J|9_C2-XvCdvGB;)!(&=$*6Mc$T^C?SFyS^9dtaf3QUoyru zpV0KR0b80wnH9gA=4u%$kE*%hq#qB8tK`@Je1ddI822c)v3X(~Cl7@2_U32~ugphl zos2MHi_Z^`G1FrF#Zv432`bYeKAD6T*`=GWw<`MV34vGg`(&cLt%I zNaE6#Zm2|LM7tF`S9w!cbpD8NH8qeCiH8OCT^}j~>oDR`i!`-{$scT{tJiDzOVgR+ zn9ASoE#&Kjb$q|jjJ|(X(0_Ui*4+6W{f*b*yirWMHyUOelbEtq4^RIzdgBSqui_d0 zI7pa%OGWYSB+e;#fzI{>WE@*W#yjX}v*ik6Goo31IfTEoZe?MOn28myQCQL-+%tso zivF2UZQUl%@wJi<K;cL#Z!QZ31|-NiR%yv*A;}1-Q81 zmUn$@aejOo6B^6p3);fjD?1FOZx*)Ys-PME7rHNWoj5U?i{u-{@GZZOsmgY|$akW| z(k1j`)O$YRjOJR0`6? z#~>fxOZA11(2$`)^O6*d+$S&zM9n*UsUdL=aHS~Q1#bI{9sh_@2|eWyF4#We&Do^Wy!tx E55mg6jsO4v diff --git a/tests/data/mmflood/activations/EMSR003-0/hydro/EMSR003-0.tif b/tests/data/mmflood/activations/EMSR003-0/hydro/EMSR003-0.tif new file mode 100644 index 0000000000000000000000000000000000000000..61ece234e90047db8eaf36f6d9200ff323bec976 GIT binary patch literal 1486 zcmaixc~BHb6o+RxmeUO=LLe%uqNN<7h?LyB^M33iC?XqHa3O%g8bk!SR0PW)ibes8 zs1$`_jEEt4fW#{b;#n902_{jL09Fz#OEGvNZqPJns`w|Bnfj)`_xer0o}PL0>^Vb@ z)?+cFfYHJb>$8|qz!*&b$wrsm2{E8q8B|NB`#+eaMG z?I>;Z_rF|qPcHqpk{>>41CC?s^XzNb@v)I@j{ORr>oMaEYx3AECmN0mCL0-6=MBx9 z438YQH8E|^`0|vY^`sS_hRGHyl#;owS=`?Sp3tq1!oi1eLXSfm=}&!1;kT4jTICGu zQ|D;bMsHBE2(`}FX|6IIKUlR>yK5+!U9P2~%wL2qMH&*!E|Q_{Yua{BExi@hMZQjt zsatJJyK?I&&)yGf%gt%~M17&JaV9zV=hN>&3hIbnLk*>cLhOTROuE)b7hMynw$c}G zouICOkW1;Uc1;v?t!hp*d=y^^+ zm4gfV`cKn*3m=Saj}taOu|{>{c(Moy5cc~=!r@0tl1Oy)BAw#uU|?z!4}vN;)qtHA!hdHh_rTxjrOjvBrp!S(G6s+7>^P^6>K-Y zqkZ*@k(%!((^1Ex^|>3m&X|BNc12r!IEu;_QkaD$H6;o3v#5;%&lgj==mA+PkJEvA zJGiB(QREYf1h-{W8RY@9`Kw71%+t6{p%}e$kWT9Eq*t+o7wy|*J?7^GYyB|HS^AU? z_m6?n>oh4BCBs1zA;i^aP?r5A#jn?r;gJjUQlg@!uoPsaH3?NO7oz)%mDJ#_A(O3v zIN>Y7{G$tys?|Zn!83GGM~Se{swrP(08Pdqg`7`DB)^>O3u2M|YcFL_>y_Oz-b1l? zOWHq^;k|b$Y&tkZOf`p7T{D?`D{#yv1|_Q>lInFGxhsze&r>YvPH`z+*n3v6teiu} zTLYwZR~A5YSB?4}58Pfog`_n>WZxQ%k6y&1D`vNFrZbazs}is`{wZC%4IHV~!}b}T z$bA+^+CwhTH|mw0+~9!H{c^h8??5+Nr%|ba1Y1m_5&y?6T2aG8<`#*4sq0|6>i|`x zJE1c{j@-E_XdQ1-O6PTo^?O7(cS9`XvJ%gc*zS9M}v`;phKCC zMl{R(CqX9i#@e1-x??;IcfOZH{gp3L{00PNH;+DfAj<5*kuF7c0>dL{1P_oR zA8i>f2p;d=-|@1$>`vE4*7h1>)@=S`NnezwkTuzdrZ#D9vOIP=grp^!3iGvbD5mc= z>tJ9*ryKLy+s9r*Yg^ypWpJ(%Ojj_DrbpgjM)(EKT2D>Vp7{;j8O{=u{(|d>erfSH z+jQ$q4~Y_7HHRULW*eg(ake|Pu#WvAB;Y4hP`Kj##(%yci=y~mEOIrLy&E5cy! delta 377 zcmZ9IyKTcT5QZT>03v2_g$kD%!6Ud#$q_U_3YHqcr3=A-<1sXX1_%s~kP$pWK8Z== zf;jU2*Bu{+$65XK(co)2i?>Fp5snFU8rb<+^-^POibzrQC zsDkB)yuDjwi-E^T6|BM=-06m7fj)EoEk1!N+s|Ml{RR3I2bC`CixFD?_-7GxhqKu) a-uRK8`1jvRY{e#BKyO;X+m?wHK7IkdT4z`Q diff --git a/tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-1.tif b/tests/data/mmflood/activations/EMSR003-0/mask/EMSR003-1.tif index 62d0243f2a22d2571213ccbb4541a920e6c0a68b..f8bc4908ac5a04cd708e2343e8f8b51dc17a1191 100644 GIT binary patch delta 332 zcmYLFv2DXJ5Co|wK){6X1q&4_G=fHOkpWbuaG?=2Kt^z3@D}h08i4~Cf(j3i0bFPV zAEPKKh=1JO-~DqrEvNZu@F6`e;?Y?_W_{QOYo!>RqID53(_*eX2J4|VU^5M}t1*~5 zIgc;O&f*i9-9a%^+1UcOn27hHP#4$>dUBt{aut#Uj5Jn;o=NXx5fj6AS;R)^g(Kgr zlf)z)R=Q?Fca9ImwG!OutV~%8#&;zB-*s%~ah1p@ZuxhUCbq@0oOh>X7QyI3B1BdC Txln&Y$Z+2qC&uz&tedId(vDhI delta 369 zcmYL_y=}uV5QQO%3=Xgg7AjPzP>}&NLNaz({IKYgy+W_? zGjnc>p8KhG-_(5C0cYTm8i66rMih+Zuj^7wv4DWp$sibN+_ks%(!HUvE0c~jH!`C zPedZivy{*wDk4iLB|Uk)V+g|(J^z0G`keDQ=hGr-k(9T5rwsczfho^2=`!_KMajsk z`26rUbgs+fVNXYPf8%j&MwGg^@qnQN@9PNap0uMz(E-GNv89u|B z)jYKI%;KjuCk7Qe(5XKP&7J`~8YYT}@EYFKhHuOSy#f>)0BUt~p7w-o(AUwjI9Zz$`%qlh31*c*E?0USu zxgL9>9}CqAcSd|~!mGb)q9CaS%R@8yYR6GDSlUo;lNp(&SvP(8usWeyDMivG2J(a`VSh`9+&4t;GmP z%|q0o=Rk!vSMLnu@qzhN&%1-r)7lKwuv2oQP6oA)?|_cXm4|D8MoQCXwCwWb#f2wC zT-Hk;`?eWuzw!bFGE>e?iRNaR8`dW_3VV4A(vs6yd(NLZ^*(f6<;^tX1eBTAU}fCD zg>k$m^|WmevmpU5yQ7%0%Yb1+Wz;z2g+9v;d|jf=(@jc0&IxQ6rXy3RZ#|3;qa=zq z_L1DQWC#_u_Iz@s3Kh3~c_ex;*9FDEJjaTvf%)jrx{rC^h4AbWT@=Oqg1%G1oR}&& z{GJXCFUov!OI`UP+gCVGiQ$u^EqD;G!MGt-uzS#ogJyzT_tG(FL_G=#f94+x`qYDjLBq2xzBb_i?O zmyq|A<9Of4nCUN4=u#k~N$CN6;C#l-tj0KDLzT?~Bzg>`-34_q-%6J{36;WOF{vCp z)*J5WN?cp~4=hb#{pb)X zU%RqdbxQbTX5h%)X!^k$CkBPlV}hJBx7iEnFh6mtAXzj$9l>I=8Jv*l4biCio8 ztT1ORe#iFL|HBczNthjb9R?AfMDn^B=vDXQcvTRKjoi3mmlut1WuxoqVm=*q4Fe79 zMAE59TF;H5>S`TywiSw_W=G+jJy|p=XO$>?PqpE{2b+cS+xOU*E@*VjmJ_eJ^TsC$ zOOIJ7yk}=YUsiy^x-VGpK+2DbFkVcKrdB`_x2m+bx;~m`EEe-T|NceF+7!;Ap zsn;`EyLTUqf0Xm(`7xqX+5*R{E=b2#A>*VjPmhnGJZ2($!!}`j?GvRaAD=ArwWW+7 z6iGFeB}7UE>>a$I*WZNlYIim)OJtQogBFXc#gkAi)ZVPa*H_8xIzNP0azvwX52RldR8Q67%4H%cvb zPp`#4Q+`A934t4t#%wQ@_dnC5f~(cw-g3v}hM<;R3mgE2iOPU5lm z1^8=$A+tTDIQ@K)xNfV@$R=k#yZ%%(_xSN}o&l?u#lrFASPnIP2SvlT`4o*2Qpa;x hQZk0QP4yV6_8i;09BAm0_7+aEepuO@5qaNQ{~v0kQpNxP delta 2072 zcmWNRi6azvAI4QEr4{3j$>c@IRg6pq^ZP#3YS=<6Vo@1#6e>rxJGn+}l0=7Eq)oZr zP^s;(w1jjh$6GcPOQ~g(U2UOy`xl>ZD$(ibP#<#krbyB&+|o@UHsniIKr#3JS+AA2=SA(=A4Q_l!|7MP=U(g=C! zTw3MG!O4$iC=fCU-a5D~Oe0~72O2I2(KOAF{z%nlNn8cGIVhB(G}w%6{+giT zdTmtiUWK2JNMQRUn>5$~YW#Z{^;TKYc~=E`Djp=$k^p#kF2T1!87y#M6Mi0Bj-!^B zDR9Pj==&5R(o{61mb+0{b2zJc2466C0E!~R7JU?uax{a2e&(EK;86tvSalV!B!As1$A z$LU^hGC7JpY2SGvdz`BTvQZ$PdNUNsZql8}Fv_Y52X93=&3tXiMhXv6@xQTTDdQo% z#R`ko`XN3^h*ar-D)P0X;c-q2lSda|>kU~Gmykg3FZqK#8Qwf+ zrZdn(S`8T#Xse5mNPldQ?;xqx!3dNn=s;sE9HH(ytRvqN&6{Jfv{(R#ksIt>YaJ;a z5MyZE2i+$;aU|grt^ChAq#8M3Xw?ME6-O~~ksB(K{4ktufsOZPqcwLu?aNvTJbXhj zeG-x#Iwp!M&!Lk%J6sX;(6;y}dex;5qkCd0S?!*W`JXSb$+94XTKc2BCzC9y0-#YX zM7Xq_@_s5|K_wBSF*uF#L)1`M#DOq=20{l9QKM`YJrk&6!k)`y?%S#LwuD`~s7I1- zkI~&jO4u5Bo{Wp6q`tV2G8;AUGAkOVa>q#4)$bgjHcF@0NhR247Kq+NLrlOOhQ3W~ zX=FWRhnqoRfg#dg=wjFOUzp%KlWiEL2h2Pa3omAUo&AMJB z;Dn(y_`j{e2aYNn9|l7uXca2s_RyUV8z3lgg?YwRiad}^owHq_pDadBJQr=pjZtB| zi=@i4&7oeIK+nDWp;K2)jqU#U{y{n&Q0!;b3ytW}Vk0aYb%fU9GPd443YIAssWO?5 zlS2*UchCm^`Yl6hVH-J?+QYuc9((&7@l|6jHBOe(lLd9e@mzzPT6wt7;$tMYhhFdC z(Wso(ms++UqTfggkJd*do>GUNq>hdK_7Ax%+)ZxQ*U2te1^hfAj)*O3-tl!j%0NNsfxh0bwNYR5Yo zHuQo~cL;6Tl88Cer{mKVUAWDYqpBHSZ2x7L2IGe4*VkFJ^W99i{4bE0AQXa94tnR$ zqnVoL>G_FnmU#Uri6?ebaI7{?fr$za>q0}Rl`N&dyU|nA`OuE`M(Y7XI^(hk8+NMT zXmm8*sQ*c-CYNYWQUwM6=?GEFAv%7|1OW^DaMnH;!_S6pQu6CCN^si)S&`%+wXZ2)~f0+In3WaNC6D1}aW68@e&-q!1%Wr2v z{+S$DOFaG9VFB~BpQyvwj7=qsQ|b6q>eAYVt>K~cLU9fvg3nUXB6)c5i&@Knw-l!Y zv*CVL6;t>9ktVdm8i_TPl;qK_mr>}}EM#rD=5(R3fYevE(1UVcq)gqT7#{=7oo0ki zIa^Vfft4t6*MM_=nIB!)XbwA*NJLnRv0PIf_nz3`RDu`o%#Fcme{X~a>d>dT?#TPc z5Usm5p}AG64a0&Fy0zLE9h??g?VCh)nzt$EbUWQx&x3`rD~1)E5H`94>9=N54ObaU zQ!X$!qc9kBx0BtIUy0YW7GG=aq&B&obmqMxCEcl@kdp}*n=L?-f+kiQ^PpU*FKXYO z1Whe28y`!6eOn6ETV580N^Jv?y*~`bES>meUF?Uk9k?myfPtPE#C$VFn~#LU^;{6T zbMk2an;@o8y_80Nh(JhND7}s_L;J`OeZSlQa^Au4-fIWR6%X`r{cy>PLzVxPW0DVi tygeL&t)&KVigU)E;dtDaIUsZqmsVI5uz4$LRq(zd32AAE>E%d+=>HWvR}cUI diff --git a/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-1.tif b/tests/data/mmflood/activations/EMSR003-0/s1_raw/EMSR003-1.tif index 039ffa35b920afb64f5160ba74ac9a79781c548a..675768a5910f494ac187669f0ab3e2900845945d 100644 GIT binary patch delta 2072 zcmWO5i(d_P1IKYn$tB&yoV4;(6MDKxr5tpA-!Dl@>2#q|DkahlQQ?%97+ypqwIqaG zVrFJokx;tum`Y)+Rc;yeB)Vw+hR^%csMDx(zHzYP=+rUzdv6SlOr3$SPH0yb2zz+} z=I>I%nRhO%@qLXBi8Aw43vhSJD+F)r6W6;;d0e8#j%`M~_i74P|GI)z$%{BhwiZs2 zuJGL;c*@h6wdZc2SndJ&+bE9gd5NTa8_wOihA}0paqD0tlLu8RuqW7wZ|8(#dcQLF zHES{QM=6t<*6?7cluOTrW8cG6^xBxq7RLX9c$qr&PcP#f&rtOAPQoXN5>1^RVv~j^ zyPCY&l5`2O-f(`|x*hrYfrvWL^0^@ z6dJG{x2?^%^<^I$Sa>%Cb1&3<&>uNNEr)p_EVU?^3t z=cKY!L@1PusPxE%RdLqzEB}ZTk7SgOAH_ryW05V@<%4xHzFhDd)E3)PV_&T})UMCb zkM5(Uv<_|Reh97i;)Rx2=B)LkWKV}IJoy%;&fmb)dzN%OO>Ww1&%>ka_(I)+*(%Nq zJ>bL6230m(-2>ga&rrD6_%LMEH0-x4#+8TX5#6(%j}i=cG1G%xqrPKhW)B`Dh4Gw0 z7=1p6Fz3+kxtP^fixq86;#tcVR7s2(+i4@@`Mt8L>8FsYb`bRiBd~W@7MAQdi4;R` z%$v4?_JPj)eUv3TH-^(I-uy})X2Q!|PP{Q@p{%p?G*Xq8F>0-@Y-sTTO!GS>d^8JjCD4pbiZTt}pQXdN zV-dV|B#g6WgmG=@W_%f{Md`1*@%L&~&Z%0&hD=LbRmp(X^#l%UKlx|O)UHx2T!-Ed2_r-R>0p;~eh{w3+cPID@66_r=EVhx zg0x~BYa7cy?!_w@WTS#jzpL_=lO@lU_sM!5=it-pNKTkPpMilIV%Ifa-ff=A6ZvkG z`@3;Qs+1;^9l7pi0*|iM5U<`HPg&`kB)WP+6Av*P1F)k7)DV6cX ztnn-yQzFW@`0}{(Pz-#^gh9}9q3nAa)d{`m?oJUCqFdmvzaO)*zh!5JRE$%OpuL|V zBUHY}e!Wmm9AwO_SaO3`DAk((i|P~yxL?=jZN=aMbaZE-d2XdJ9jwlp^*=+`(Spk9 zZLoY`M(@Ht;WdH`e`iW_?GALLTeCu{!-_S<7}=14ZL&+^<_$Yq^vkJz^LsSU8o;pk zdAKsV47cLn!px*VR$HnC2i+Ci-4{Z!+=jnBwxabODH2N^sF*!4jxTiE;4mQ<72*!A zdCp-&<6<7_NZ=FuEKDo`FL&2t%=8>FDnFPOKRfgBQVF$V)u?9a%)R;A+%+wp1tnf| zTOCj5ACwt)Xdy-h=0R+>WmTFPO_s;t$XiWr>0Ald3}arnREwzlI`kPYRWQ2aAWnuy z^1#%3>^Ay{9F;PW^Urji2$(=<#Nuh*C$vQQpvXBynC(#Jj`pGautJ}f4lbOWQH1}M z=ukf@8(lLe@`AcM=fM`r=f-nvU;tX{#^WcgsVt9i;~WD$PHZ<|M!px%g<6PR3}Lms z!kK>BSQG@_W$uA&qTcU9LkL^=E{ncLTFXc2GvVmZ2oZx7FP5jJ;;O6#Sc(f z@e&EEyjbgC#+eEcGzLr%D;TY@GJ}N`MT(W`Tt>Cye-r67Qj6ynud#f mxY=eUat&N*`5}tkw)deG97#=&)e%&(2xI2WAv|&|5B~x@-)7JN delta 2072 zcmWO5iCfM20taA)vSv9b%W0P=Jr9%Q(5c_|^C1-_l@Ll2MSDrl;BXWsL}*7tW~8Wu zk+S59F!D@Eo3-gyj3o(S#J%tT@HWYsWJOIHs$rF#aP%od4cW7*PFyqdGgQ0Ra)yf^Z4|9YzfvnTyZcjr`&BqJ zHlxe_8S3qtL`PJ&cz)HD8>AoT)2b(hFLn{LTV?F+eG)yMr&VIdpy4dIy@8p5lc{nK zl62KpY;Yb+n;~AbU8Q8y;wXHo7%DO*Mc~)6YN?{mlivre72?nmPR^~zZKr(>d8U70 z>L@F&R?8Un(v_XRW`kcMxv+Q!YZCvGqRjW8M(3TArgQ116-PpVcL74O+?TG85Jt?MK79VrYiGhQ}KX76!Co;*&Tw zi9_q$St6+-87|wOK=#Frsz*h;M3G*ZXsjv_-y6(1snMPv2K|YMfJ(%~7((;pKY0Em z2%qY2<3YeWJX|!N!|wIM%-}i{MqS1~9TIP)?8nEd8blR)^W?w_*cCd=;dA#i8c*ye z?bo#C4|yETr#FaR#=(fZzaEJJ{xp174#%b+p#U{8k{ z2`27+(D(RbD92pI3BwUM@K6uIi^eloKNk(_tFY&9OX=BZGtSvPhaax^Qrmn38z+B5 z-j)t{?0A7_lRDA0?+{uh+cK&soY{|xP&~Iw1TXic>lQUkXR9o@Eus-svKcI$*MjP= z=KSFjNBdV^6OpH;c5@_e6U`fBu#00k5;poR{g(a?fc93(Uhj<#!ATw`5(6l6}HGskREG z$?Q-`HDLiSEYjue^eN)~@l2Gb8SztOKc?!e}<8@ec6S}X% zsQTB{ismcE@mQKQ_x`Y@zWG$RM4Iu1zB_wf`B2ZV8*cx}aY+B~J%`jm!5kE*$r`zw z))}+tU>C!}i-Y0%TPJ2XeT4qiSX!-}%A&i!z)NAy@6}b1eVxWw&kS^XyHR&>FJ=Xf zWwP#IdJQn8->VQ68?dxd2y)n4EAQRVe`m*yuExg^VIrF6xMm}E**~%6S_s24Fr0qfUdB8oBMqbKLxH{|v0iy8K!2Zss^MN;)ely;FT%ENFbI6~zl1WdwWL=g{zc5RA7EWZd~wsJ4AuO@s0`@YoncWy2BJzR=>b zxrb4h^a=fy>u_+>eQ4-d3CCMeXbR7Qe^)UY6+U87L^=+KRbjrCF&`)B@O7LowL+9! zGBK8Z!v@eQ?H$7Q3}H>|57cKRVf51BR4y)r?L#o3*^_a8AqYKXuj0AqePMcRE5L&o zdnXqvS3@!LNB%&sXB*WNQ|+fbP$txwd0E z<=IV0|J#@blPnoH`Vkbi#MKiU(YUM)zbC%J)taR=TsV}nHD3{?3c|JKL~$ci<-_7|DG8S=ObhJAz4AQZC&C{$9;_Z;?1Acz|jlY zNb!!QZ%zZ0+hWPYY7BThhY7mN=)Bd8wMKHLE@?xB@pc@0>CDMHH_+vRHq+mgz&l03 f{PCxtIW3qwFD}B+$f7LxxXeP+?3ws_%O3v&Kk0W8 diff --git a/tests/data/mmflood/activations/EMSR004-0/DEM/EMSR004-0.tif b/tests/data/mmflood/activations/EMSR004-0/DEM/EMSR004-0.tif index 29e5053d6b8c60763d3ce67de200cf2a4616d083..c761aed58f4afba06c2ffd53903bf916746eb940 100644 GIT binary patch delta 1040 zcmWO3jaSWg00-c%=FV2F>^g|vtkVm*R>N+Z==c46+Ns;5PN}FZM_a5o*6n4C<95BJ zH`&fss%5HOCfk%o&C4WuPwRzj%fX^BkrI`1n}6YXT1;9@N?T;wvdyztx9I}peUXeU zmW!z8o{U#(U_E^XwMgRRcc$}V`+Q-pPQ;JNCFne=q+%?B9l2E^Iq8V-UPa1_WK1_N zN7VzpbT_~THRTp?cqn7OE{3ZUzQv`)I4+;7M2EZ%QQm&M`nrl67T7VMOR2@!@r_JV z#Bh9%CEbRyg>Cg-R2u$4&utaMtTr+GG3Zir8ID`0veoG_%r>s1p{*YQ^WBj$u>wigT9&n8hA!~B8Ccj zBxlEFw8vW0eq6<49+uQJv?I(y$poJsT*=EqvXemJv}j)WX#_B<5NG>?IV78lk?{&} zzCv1mI+3$0*D!F~N;Z!8@@4m+P@j9G#nOgAj$d-;J-N`%MBgzH{=w*Kyn4ATfqElz@I#!MQWcVYD_@%u(Ig|W8Kj=ndH*}eZQsUgD( zjy^{a-5SB(K_f0WA1UrW4MT;gk`K~qkla59O9LO`-0;U7uiuUClt|i?Sa70X36HFL zjZNPvq{258yslV>QF*m^k*i?%-!hiTioDuCdxD+eOL=me5p~1PyxnsK+1lxxlB!C@ InFclg2LrypBme*a delta 1040 zcmWO4k2BVH00(eVo-odjm>(r`^n;E)e&mkA_w#PoZe=aYV@8(mXtSJRvxmTi&CV(}~&#(8QUElsIbt$*QluTHy8yM|6n-P23CR2#DC zsrQF_wFm1php>KI3jZiSD^?hnGP-pTmuA+B;1{{*`LrEnO}oY5Jx7#!GAikER>_4Hc}O+;F(4w!t5wv+apYnlI z_$fV`>bmVvo%7<$Ay=vl*7W%2M^SJ+gj1FiFg&+mb%8h5hIJz`HjE9)p>*+R#{Iwg zaCmjHrd3g+%7qX>4 zfzX{0U)_JJ*R+*Qx|lUnwhgp5Q#eC!2NAEwLnlj4Tdd~KLs!w4lKYm8BFpH7 z4-+Gp9y5!@3HA&+>c-lga=y-#!GFOnL_}IsS@8vu8lqWWW<}XT6=U>!M8=ZqqT5i+ zr9BCZw%mc?{VK^KeJ7ID!PEss1u^eIq_n;=hx!3eJnUGGczZdU6nEj)Z^06~-6(1D z7n?#Bva4LhFO8q0ZZMZQr2%4&g9mg6O3_&MzO-&{97FOfnb{OhTl-j!wrA0N{|1uG zQFLlMj_aBv#5e@=wV#qgv6&g=D=1x@r=!{Lh-B@lp{@1zXbDv?_T3h2=<#8dgPhBJ zY7vP2F!3GCc-AW#7nxABZycKn)-qEa#-xGAVrTOxwpZ2S^6haf@|AeSdOE6nUC}ny zii1p}mE#EdZ4G$uvN`ruXd$kx+JQe`)ZpTHK3A^Nt>(a~xx9JRN^&WV;O&lO_;>zM z@wmi-0S!BZe~&XeyK9gk9}wgJ1u|T?@J`wx^q7KKb2pjAH!2}J9LnFb@^~WUV;<|% zQbY~m%IS6lEIE&w$j#Ir&!+{q(pzj}=ay%1{p=+U4Cm76mV^tsGA%1Rw2(aSNl|{+ zQ0JLI=Ym4IPn*NY@#R$6Jwg2^vl;5G<;Kx?dOLmuS91;<-+Y5vAwOVhzJeyxDa_7X zKymsvWUrK%{J@dJB^9V$u#T?Bvbnmn9YwB6X1Tmg8Yp9J*Ln16#!&I9h%Bx+9LK-+UM@}U2uY>r$CR|JL{8@3`@BDN*$w3?oMP1phos%E$&byHA|kY! zY3Hc?Sf({jN0DkZl-`;LHgb2-)vyaWmhykP{`d3T zB6pd0tAnCmrK&5x=c)L&QZ#zf27D*f0+p+m`P(dUN)lWqXCP|RN-C;7gh}4xSYkjdHBNJ* z?jA1+?C_&}Uk%OEouj(105^!lpF6> zBT4E&U3<+a!?gOGCqQiqLQPSc=%}$RI{PYF;xLQLeztz#M zWF7g`w=l6?F}m_LQ|sButm9Y)W>N%#8^WpgxIP)(h@y{P>1h2xKN|dtaYX+Ot|U9r zP}Y5P>a3}|WI&OX*N@!DWUM;vOqo4)$|uLHl{v}rD0M2sjPfD$46dP9-{d03uA9l1 zxFDuMk6d$#VB_8oO>rl)5T`;EsHSVSo>VQXM#9n>I9uPs{38M?|1>An11Y8a3+w1|aB6oW4v&G8X_H_t9mkZs7wOCbn0hgZ0>Bk6X z+E%B*2388ZdBx%mis?+93l4fXQ*>%Mo*9=weTYq!ID+!@SB+)9_z?O?rN3nI^lFjGkZ!c!D=0V?rIaE%Q;|@64fc-HN#n5YwF6RqgLm>TF9& z@Rd;Mc31KcuST<0O&=PV(=Ekv%KiBz?f%(HFR~r4IoHj1n((qHu}<#XFgi?Mr{^usQ^*jNfpwK9J(d<)j$y#qIFD z7;0_D#@I00muW@ubENe7#d5?NI+L#ZHfswtLXfo;>9;PYZK3mNLdsLt7~qLEPdU|U G8T}1Q{pQF3 literal 0 HcmV?d00001 diff --git a/tests/data/mmflood/activations/EMSR004-0/mask/EMSR004-0.tif b/tests/data/mmflood/activations/EMSR004-0/mask/EMSR004-0.tif index a9588d8cf6f918371b50f31399f078720ed8b33c..b686190d2bcaa23e66ae29dc7bd9d5c8bda5294f 100644 GIT binary patch delta 336 zcmX|+v2DXJ5JfHW6cDfwuuzdAg$tK1G(ZXsU?3d9h07Eg!6Oj1 z6doauvEkz5c=zxBe>^YG%hRRO-hF6vjb<>qpsj0_y*&|i^FbSHmEaRKTccAQmED6Y znjdDVHVwX2&%CGK;+vhRN54WF&2)uN)jN38p=R!*zN^5y(SYX8-YJ&1CQVVTzCS*69gVX z1Ek2462z6L$NP8pdD&jJ$7`c^mnYw;xTGOOWlPEnO1iqN>LOo|nLeCN-c&8?)X)n^T`(UXAXt7~=RTwv$0)0f{cj}Lr?pM0wW zbMxW@E9-w`X30s@g00gVk)!oGxn6r`e>IR$V~^(B9#>tpa^D~#B^CDp+5b{4M+&iP b36x!T&N}cU&xHP#NhI+uINzD+=pTLn8B1FY diff --git a/tests/data/mmflood/activations/EMSR004-0/s1_raw/EMSR004-0.tif b/tests/data/mmflood/activations/EMSR004-0/s1_raw/EMSR004-0.tif index eee624c79b11c48248b8eba630d6df3f73f87f1a..52a2e7ca04d7f6a5452f10529b730443e9f1a5c1 100644 GIT binary patch delta 2072 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 None: - MAX_VALUE = 1000.0 - MIN_VALUE = 0.0 - RANGE = MAX_VALUE - MIN_VALUE - FOLDERS = ['s1_raw', 'DEM', 'mask'] +def generate_data( + path: str, filename: str, height: int, width: int, include_hydro: bool = False +) -> None: + max_value = 1000.0 + min_value = 0.0 + interval = max_value - min_value + folders = ['s1_raw', 'DEM', 'mask', 'hydro'] profile = { 'driver': 'GTiff', 'dtype': 'float32', @@ -36,24 +38,29 @@ def generate_data(path: str, filename: str, height: int, width: int) -> None: 'width': width, } data = { - 's1_raw': np.random.rand(2, height, width).astype(np.float32) * RANGE - - MIN_VALUE, - 'DEM': np.random.rand(1, height, width).astype(np.float32) * RANGE - MIN_VALUE, + 's1_raw': np.random.rand(2, height, width).astype(np.float32) * interval + - min_value, + 'DEM': np.random.rand(1, height, width).astype(np.float32) * interval + - min_value, 'mask': np.random.randint(low=0, high=2, size=(1, height, width)).astype( np.uint8 ), } - os.makedirs(os.path.join(path, 'hydro'), exist_ok=True) + if include_hydro: + data['hydro'] = ( + np.random.rand(1, height, width).astype(np.float32) * interval - min_value + ) - for folder in FOLDERS: + for folder in folders: folder_path = os.path.join(path, folder) os.makedirs(folder_path, exist_ok=True) filepath = os.path.join(folder_path, filename) profile2 = profile.copy() profile2['count'] = 2 if folder == 's1_raw' else 1 - with rasterio.open(filepath, mode='w', **profile2) as src: - src.write(data[folder]) + if folder in data: + with rasterio.open(filepath, mode='w', **profile2) as src: + src.write(data[folder]) return @@ -88,6 +95,7 @@ def generate_folders_and_metadata(datapath: str, metadatapath: str) -> None: ('EMSR004', 'test'), ] num_files = {'EMSR000': 3, 'EMSR001': 2, 'EMSR003': 2, 'EMSR004': 1} + num_hydro = {'EMSR001': 2, 'EMSR003': 1, 'EMSR004': 1} metadata = {} for folder, split in folders_splits: data = {} @@ -101,11 +109,20 @@ def generate_folders_and_metadata(datapath: str, metadatapath: str) -> None: data['subset'] = split data['delineations'] = [f'{folder}_00'] + count_hydro = 0 + dst_folder = os.path.join(datapath, f'{folder}-0') for idx in range(num_files[folder]): + include_hydro = count_hydro < num_hydro.get(folder, 0) generate_data( - dst_folder, filename=f'{folder}-{idx}.tif', height=16, width=16 + dst_folder, + filename=f'{folder}-{idx}.tif', + height=16, + width=16, + include_hydro=include_hydro, ) + if include_hydro: + count_hydro += 1 metadata[folder] = data From e28d3849a6f959a50ae3f360ca227128ddf65fc4 Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Sat, 21 Dec 2024 23:18:17 +0100 Subject: [PATCH 7/9] changed dataset license --- docs/api/datasets/geo_datasets.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/datasets/geo_datasets.csv b/docs/api/datasets/geo_datasets.csv index 6f8d60bbf2e..2bfafc39c39 100644 --- a/docs/api/datasets/geo_datasets.csv +++ b/docs/api/datasets/geo_datasets.csv @@ -20,7 +20,7 @@ Dataset,Type,Source,License,Size (px),Resolution (m) `L8 Biome`_,"Imagery, Masks",Landsat,"CC0-1.0","8,900x8,900","15, 30" `LandCover.ai Geo`_,"Imagery, Masks",Aerial,"CC-BY-NC-SA-4.0","4,200--9,500",0.25--0.5 `Landsat`_,Imagery,Landsat,"public domain","8,900x8,900",30 -`MMFlood`_,"Imagery,DEM,Masks","Sentinel, MapZen/TileZen, OpenStreetMap",CC-BY-4.0,"2,147x2,313",20 +`MMFlood`_,"Imagery,DEM,Masks","Sentinel, MapZen/TileZen, OpenStreetMap",MIT,"2,147x2,313",20 `NAIP`_,Imagery,Aerial,"public domain","6,100x7,600",0.3--2 `NCCM`_,Masks,Sentinel-2,"CC-BY-4.0",-,10 `NLCD`_,Masks,Landsat,"public domain",-,30 From 3c36b9bc48821cd484ced5cca61401c1a8b5bd9b Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Sat, 21 Dec 2024 23:18:54 +0100 Subject: [PATCH 8/9] added hydrography component, changed MMFlood to IntersectionDataset --- tests/conf/mmflood.yaml | 4 +- tests/datasets/test_mmflood.py | 69 ++++- torchgeo/datamodules/mmflood.py | 29 +- torchgeo/datasets/mmflood.py | 473 ++++++++++++++++++++------------ 4 files changed, 376 insertions(+), 199 deletions(-) diff --git a/tests/conf/mmflood.yaml b/tests/conf/mmflood.yaml index 43f3366dcdb..e9c32663f9b 100644 --- a/tests/conf/mmflood.yaml +++ b/tests/conf/mmflood.yaml @@ -4,9 +4,10 @@ model: loss: 'ce' model: 'unet' backbone: 'resnet18' - in_channels: 3 + in_channels: 4 num_classes: 2 num_filters: 1 + ignore_index: 255 data: class_path: MMFloodDataModule init_args: @@ -16,3 +17,4 @@ data: patch_size: 8 normalization: 'median' include_dem: True + include_hydro: True diff --git a/tests/datasets/test_mmflood.py b/tests/datasets/test_mmflood.py index 5649e51c5ac..593702b4b91 100644 --- a/tests/datasets/test_mmflood.py +++ b/tests/datasets/test_mmflood.py @@ -20,10 +20,13 @@ MMFlood, UnionDataset, ) +from torchgeo.datasets.mmflood import MMFloodComponent, MMFloodIntersection class TestMMFlood: - @pytest.fixture(params=product([True, False], ['train', 'val', 'test'])) + @pytest.fixture( + params=product([True, False], [True, False], ['train', 'val', 'test']) + ) def dataset( self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest ) -> MMFlood: @@ -33,12 +36,13 @@ def dataset( monkeypatch.setattr(MMFlood, 'url', url) monkeypatch.setattr(MMFlood, '_nparts', 2) - include_dem, split = request.param + include_dem, include_hydro, split = request.param root = tmp_path return MMFlood( root, split=split, include_dem=include_dem, + include_hydro=include_hydro, transforms=nn.Identity(), download=True, checksum=True, @@ -50,19 +54,66 @@ def test_getitem(self, dataset: MMFlood) -> None: assert isinstance(x['crs'], CRS) assert isinstance(x['image'], torch.Tensor) assert isinstance(x['mask'], torch.Tensor) + nchannels = 2 - # If DEM is included, check if 3 channels are present, 2 otherwise + # If DEM is included and hydro is included, check if 4 channels are present, + # If only one between DEM or hydro is included, check if 3 channels are present + # 2 otherwise if dataset.include_dem: - assert x['image'].size(0) == 3 - else: - assert x['image'].size(0) == 2 - return + nchannels += 1 + if dataset.include_hydro: + nchannels += 1 + assert x['image'].size(0) == nchannels + + @pytest.fixture + def mock_intersection_dataset( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ) -> MMFlood: + class MockIntersection(MMFloodIntersection): + def __init__( + self, + dataset1: MMFloodIntersection | MMFloodComponent, + dataset2: MMFloodIntersection | MMFloodComponent, + ) -> None: + super().__init__(dataset2, dataset1) + + monkeypatch.setattr( + 'torchgeo.datasets.mmflood.MMFloodIntersection', MockIntersection + ) + dataset_root = os.path.join('tests', 'data', 'mmflood/') + url = os.path.join(dataset_root) + monkeypatch.setattr(MMFlood, 'url', url) + monkeypatch.setattr(MMFlood, '_nparts', 2) + return MMFlood( + tmp_path, + split='train', + include_dem=True, + include_hydro=True, + transforms=nn.Identity(), + download=True, + checksum=True, + ) + + def test_swap_dataset(self, mock_intersection_dataset: MMFlood) -> None: + d = MMFlood( + mock_intersection_dataset.root, + split='train', + include_dem=True, + include_hydro=True, + ) + assert len(d) == 2 def test_len(self, dataset: MMFlood) -> None: if dataset.split == 'train': - assert len(dataset) == 5 + if not dataset.include_hydro: + assert len(dataset) == 5 + else: + assert len(dataset) == 2 elif dataset.split == 'val': - assert len(dataset) == 2 + if not dataset.include_hydro: + assert len(dataset) == 2 + else: + assert len(dataset) == 1 else: assert len(dataset) == 1 diff --git a/torchgeo/datamodules/mmflood.py b/torchgeo/datamodules/mmflood.py index 6f4a80babfa..bb291fd0441 100644 --- a/torchgeo/datamodules/mmflood.py +++ b/torchgeo/datamodules/mmflood.py @@ -8,6 +8,7 @@ import kornia.augmentation as K import torch from kornia.constants import DataKey, Resample +from torch import Tensor from ..datasets import MMFlood from ..samplers import GridGeoSampler, RandomBatchGeoSampler @@ -22,9 +23,9 @@ class MMFloodDataModule(GeoDataModule): """ # Computed over train set - mean = torch.tensor([0.1785585, 0.03574104, 168.45529]) - median = torch.tensor([0.116051525, 0.025692634, 86.0]) - std = torch.tensor([2.405442, 0.22719479, 242.74359]) + mean = torch.tensor([0.1785585, 0.03574104, 168.45529, 0.02248373255133629]) + median = torch.tensor([0.116051525, 0.025692634, 86.0, 0.0]) + std = torch.tensor([2.405442, 0.22719479, 242.74359, 0.1482505053281784]) def __init__( self, @@ -57,11 +58,16 @@ def __init__( assert ( normalization in {'mean', 'median'} ), f'Invalid normalization parameter specified {normalization}, must be either "mean" or "median".' - avg = self.mean if normalization == 'mean' else self.median + self.normalization = normalization + avg, std = self._get_mean_std( + dem=kwargs.get('include_dem', False), + hydro=kwargs.get('include_hydro', False), + ) + # Using median for normalization for better stability, # as stated by the original authors self.train_aug = K.AugmentationSequential( - K.Normalize(avg, self.std), + K.Normalize(avg, std), K.RandomResizedCrop(_to_tuple(self.patch_size), p=0.8, scale=(0.5, 1.0)), K.RandomHorizontalFlip(p=0.5), K.RandomVerticalFlip(p=0.5), @@ -75,9 +81,20 @@ def __init__( ) self.aug = K.AugmentationSequential( - K.Normalize(avg, self.std), keepdim=True, data_keys=None + K.Normalize(avg, std), keepdim=True, data_keys=None ) + def _get_mean_std( + self, dem: bool = False, hydro: bool = False + ) -> tuple[Tensor, Tensor]: + avg = self.mean if self.normalization == 'mean' else self.median + idxs = [0, 1] # VV, VH + if dem: + idxs.append(2) + if hydro: + idxs.append(3) + return avg[idxs], self.std[idxs] + def setup(self, stage: str) -> None: """Set up datasets. diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py index 8f3dd808d50..2ea686ef783 100644 --- a/torchgeo/datasets/mmflood.py +++ b/torchgeo/datasets/mmflood.py @@ -3,8 +3,10 @@ """MMFlood dataset.""" +from __future__ import annotations + import os -import pathlib +from abc import ABC, abstractmethod from collections.abc import Callable from glob import glob from typing import ClassVar, Literal, cast @@ -12,20 +14,163 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd -import torch from matplotlib.figure import Figure from rasterio.crs import CRS from torch import Tensor from .errors import DatasetNotFoundError -from .geo import RasterDataset +from .geo import IntersectionDataset, RasterDataset from .utils import BoundingBox, Path, download_url, extract_archive -class MMFlood(RasterDataset): +class MMFloodComponent(RasterDataset, ABC): + """Base component for MMFlood dataset.""" + + def __init__( + self, + subfolders: list[str], + root: Path = 'data', + crs: CRS | None = None, + res: float | None = None, + transforms: Callable[[dict[str, Tensor]], dict[str, Tensor]] | None = None, + cache: bool = False, + ) -> None: + """Initialize MMFloodComponent dataset instance.""" + paths = [] + for s in subfolders: + paths += glob(os.path.join(root, '*', f'{s}*-*', self.content, '*.tif')) + paths = sorted(paths) + super().__init__(paths, crs, res, transforms=transforms, cache=cache) + return + + @property + @abstractmethod + def content(self) -> str: + """Returns the name of the folder containing the tif files to be read.""" + + +class MMFloodS1(MMFloodComponent): + """Sentinel-1 component for MMFlood dataset.""" + + @property + def content(self) -> str: + """The subfolder containing Sentinel-1 data.""" + return 's1_raw' + + +class MMFloodDEM(MMFloodComponent): + """DEM component for MMFlood dataset.""" + + @property + def content(self) -> str: + """The subfolder containing DEM data.""" + return 'DEM' + + +class MMFloodMask(MMFloodComponent): + """Mask component for MMFlood dataset.""" + + is_image = False + + @property + def content(self) -> str: + """The subfolder containing mask data.""" + return 'mask' + + +class MMFloodHydro(MMFloodComponent): + """Hydrography map component for MMFlood dataset.""" + + @property + def content(self) -> str: + """The subfolder containing hydrography data.""" + return 'hydro' + + +class MMFloodIntersection(IntersectionDataset): + """Intersection dataset used to merge two or more MMFloodComponents.""" + + _ordering: ClassVar[dict[type[MMFloodComponent], int]] = { + MMFloodS1: 0, + MMFloodDEM: 1, + MMFloodHydro: 2, + } + + def __init__( + self, + dataset1: MMFloodComponent | MMFloodIntersection, + dataset2: MMFloodComponent | MMFloodIntersection, + ) -> None: + """Initialize a new MMFloodIntersection instance. + + Args: + dataset1: the first dataset to merge + dataset2: the second dataset to merge + """ + dataset1, dataset2 = self._swap_datasets(dataset1, dataset2) + super().__init__(dataset1, dataset2) + + @property + def contains_hydro(self) -> bool: + """A flag stating whether the Hydrography dataset is present.""" + # If Hydro dataset is present, it is always the last dataset + return isinstance(self.datasets[1], MMFloodHydro) + + def _swap_datasets( + self, + ds1: MMFloodComponent | MMFloodIntersection, + ds2: MMFloodComponent | MMFloodIntersection, + ) -> tuple[MMFloodComponent | MMFloodIntersection, MMFloodComponent]: + """Sort the datasets in the correct order (Sentinel-1, DEM, Hydrography). + + Arguments: + ds1: first dataset. Must be either an instance of MMFloodComponent or MMFloodIntersection + ds2: second dataset. Must be either an instance of MMFloodComponent or MMFloodIntersection + + Returns: + the two datasets in the correct order + """ + assert not ( + isinstance(ds1, MMFloodIntersection) + and isinstance(ds2, MMFloodIntersection) + ), 'Cannot intersect two Intersection datasets!' + # If one of the two datasets is an instance of MMFloodIntersection, return it first + if isinstance(ds1, MMFloodIntersection): + assert ( + not ds1.contains_hydro + ), 'Instance of MMFloodHydro should be merged as last element!' + ds2 = cast(MMFloodComponent, ds2) + return ds1, ds2 + elif isinstance(ds2, MMFloodIntersection): + assert ( + not ds2.contains_hydro + ), 'Instance of MMFloodHydro should be merged as last element!' + return ds2, ds1 + # Always intersect the datasets in this order: + # Sentinel-1, DEM, Hydro, if present + res = cast( + tuple[MMFloodComponent, MMFloodComponent], + sorted((ds1, ds2), key=lambda x: self._ordering[type(x)]), + ) + return res + + def _merge_dataset_indices(self) -> None: + """Create a new R-tree out of the individual indices from Sentinel-1, DEM and hydrography datasets.""" + _, ds2 = self.datasets + # Always use index of ds2, since it either coincides with ds1 index + # or refers to hydro, which represents only a subset of the dataset + self.index = ds2.index + + +class MMFlood(IntersectionDataset): """MMFlood dataset. - `MMFlood `__ dataset is a multimodal flood delineation dataset. + `MMFlood `__ dataset is a multimodal + flood delineation dataset. Sentinel-1 data is matched with masks and DEM data for all + available tiles. If hydrography maps are loaded, only a subset of the dataset is loaded, + since only 1,012 Sentinel-1 tiles have a corresponding hydrography map. + Some Sentinel-1 tiles have missing data, which are automatically set to 0. + Corresponding pixels in masks are set to 255 and should be ignored in performance computation. Dataset features: @@ -33,7 +178,7 @@ class MMFlood(RasterDataset): * multimodal dataset * 95 flood events from 42 different countries * includes DEMs - * includes hydrography maps (not available for all areas of interest) + * includes hydrography maps (available for 1,012 tiles out of 1,748) * flood delineation maps (ground truth) is obtained from Copernicus EMS Dataset classes: @@ -58,9 +203,10 @@ class MMFlood(RasterDataset): } _ignore_index = 255 _nparts = 11 - _mean = (0.1785585, 0.03574104, 168.45529) - _median = (0.116051525, 0.025692634, 86.0) - _std = (2.405442, 0.22719479, 242.74359) + # VV, VH, dem, hydro + _mean = (0.1785585, 0.03574104, 168.45529, 0.02248373255133629) + _median = (0.116051525, 0.025692634, 86.0, 0.0) + _std = (2.405442, 0.22719479, 242.74359, 0.1482505053281784) metadata: ClassVar[dict[str, str]] = { 'part_file': 'activations.tar.{part}.gz.part', @@ -89,8 +235,10 @@ def __init__( self, root: Path = 'data', crs: CRS | None = None, + res: float | None = None, split: str = 'train', include_dem: bool = False, + include_hydro: bool = False, transforms: Callable[[dict[str, Tensor]], dict[str, Tensor]] | None = None, download: bool = False, checksum: bool = False, @@ -100,9 +248,14 @@ def __init__( Args: root: root directory where dataset can be found - crs: coordinate reference system to be used + crs: :term:`coordinate reference system (CRS)` to warp to + (defaults to the CRS of the first file found) + res: resolution of the dataset in units of CRS + (defaults to the resolution of the first file found) split: train/val/test split to load include_dem: If True, DEM data is concatenated after Sentinel-1 bands. + include_hydro: If True, hydrography data is concatenated as last channel. + Only a smaller subset of the original dataset is loaded in this case. transforms: a function/transform that takes input sample and its target as entry and returns a transformed version download: if True, download dataset and store it in the root directory @@ -118,18 +271,30 @@ def __init__( self.root = root self.split = split self.include_dem = include_dem + self.include_hydro = include_hydro self.transforms = transforms self.download = download self.checksum = checksum - # Verify integrity of the dataset, initializing - # self.image_files, self.label_files, self.dem_files attributes + # Verify integrity of the dataset self._verify() self.metadata_df = self._load_metadata() - self.folders = self._load_folders() - paths = [x['s1_raw'] for x in self.folders] - # Build the index - super().__init__(paths=paths, crs=crs, transforms=transforms, cache=cache) + self.image: MMFloodComponent | MMFloodIntersection = MMFloodS1( + self._get_split_subfolders(), root, crs, res, cache=cache + ) + if include_dem: + dem = MMFloodDEM(self._get_split_subfolders(), root, crs, res, cache=cache) + self.image = MMFloodIntersection(self.image, dem) + if include_hydro: + hydro = MMFloodHydro( + self._get_split_subfolders(), root, crs, res, cache=cache + ) + self.image = MMFloodIntersection(self.image, hydro) + self.mask = MMFloodMask( + self._get_split_subfolders(), root, crs, res, cache=cache + ) + + super().__init__(self.image, self.mask, transforms=transforms) def _merge_tar_files(self) -> None: """Merge part tar gz files.""" @@ -146,44 +311,6 @@ def _merge_tar_files(self) -> None: with open(part_path, 'rb') as part_fp: dst_fp.write(part_fp.read()) - def __getitem__(self, query: BoundingBox) -> dict[str, Tensor]: - """Retrieve image/mask and metadata indexed by query. - - Args: - query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index - - Returns: - sample containing image, mask and metadata at that index - - Raises: - IndexError: if query is not found in the index - """ - hits = self.index.intersection(tuple(query), objects=True) - indexes = cast(list[int], [hit.id for hit in hits]) - - if not indexes: - raise IndexError( - f'query: {query} not found in index with bounds: {self.bounds}' - ) - - image = self._load_image(indexes, query) - mask = self._load_target(indexes, query) - - sample = {'image': image, 'mask': mask, 'crs': self.crs, 'bounds': query} - - if self.transforms is not None: - sample = self.transforms(sample) - - return sample - - def __len__(self) -> int: - """Return the number of data points in the dataset. - - Returns: - length of the dataset - """ - return len(self.folders) - def _load_metadata(self) -> pd.DataFrame: """Load metadata. @@ -195,130 +322,84 @@ def _load_metadata(self) -> pd.DataFrame: ).transpose() return df - def _load_tif_files( - self, check_folders: bool = False, load_all: bool = False - ) -> dict[str, list[str]]: - """Load paths of all tif files for Sentinel-1, DEM and masks. + def _get_split_subfolders(self) -> list[str]: + """Get list of EMSR data folders to load, depending on specified split. - Args: - check_folders: if True, verifies pairings of all s1, dem and mask data across all the folders - load_all: if True, loads all tif files contained in the "activations" folder in the root folder specified. Otherwise, only acquisitions for the given split are loaded. + Returns: + list of EMSR codes to be loaded + """ + folders = self.metadata_df[ + self.metadata_df['subset'] == self.split + ].index.tolist() + return cast(list[str], folders) + + def _load_tif_files(self) -> dict[str, list[str | None]]: + """Load paths of all tif files for Sentinel-1, DEM, hydrography and masks. Returns: - dict containing list of paths, with 'image', 'dem' and 'mask' as keys + dict containing list of paths, with 'image', 'dem', 'hydro' and 'mask' as keys """ - paths = {} + paths: dict[str, list[str | None]] = {} dirpath = os.path.join(self.root, self.metadata['directory']) - # initialize tif file lists containing masks, DEM and S1_raw data - if load_all: - # Get all directories - folders = os.listdir(dirpath) - else: - # Assemble regex for glob - folders = ( - self.metadata_df[self.metadata_df['subset'] == self.split].index + '-*' - ).tolist() + folders = os.listdir(dirpath) + # initialize tif file lists containing masks, DEM, hyd and S1_raw data image_files = [] mask_files = [] dem_files = [] - for f in folders: - path = os.path.join(self.root, self.metadata['directory'], f) - image_files += glob(os.path.join(path, 's1_raw', '*.tif')) - mask_files += glob(os.path.join(path, 'mask', '*.tif')) - dem_files += glob(os.path.join(path, 'DEM', '*.tif')) - - image_files = sorted(image_files) - mask_files = sorted(mask_files) - dem_files = sorted(dem_files) + hydro_files = [] - paths['image'] = image_files - paths['mask'] = mask_files - paths['dem'] = dem_files + def _get_filename_to_path_mapping( + basepath: str, subfolder: Literal['s1_raw', 'mask', 'hydro', 'DEM'] + ) -> dict[str, str]: + # Assemble path and return a mapping filename -> complete path + paths = glob(os.path.join(basepath, subfolder, '*.tif')) + return {os.path.basename(p): p for p in paths} - # Verify image, dem and mask lengths - assert ( - len(paths['image']) > 0 - ), f'No images found, is the given path correct? ({self.root!s})' - assert ( - len(paths['image']) == len(paths['mask']) - ), f'Length mismatch between tiles and masks: {len(paths["image"])} != {len(paths["mask"])}' - assert len(paths['image']) == len( - paths['dem'] - ), 'Length mismatch between tiles and DEMs' - - if check_folders: - # Verify image, dem and mask pairings - self._verify_pairings(paths['image'], paths['dem'], paths['mask']) + for f in sorted(folders): + path = os.path.join(self.root, self.metadata['directory'], f) + images = _get_filename_to_path_mapping(path, 's1_raw') + masks = _get_filename_to_path_mapping(path, 'mask') + dems = _get_filename_to_path_mapping(path, 'DEM') + hydros = _get_filename_to_path_mapping(path, 'hydro') + assert len(images) == len(masks) == len(dems) + for filename in sorted(images.keys()): + image_files.append(images[filename]) + mask_files.append(masks[filename]) + dem_files.append(dems[filename]) + hydro_files.append(hydros.get(filename, None)) + + paths['image'] = cast(list[str | None], image_files) + paths['mask'] = cast(list[str | None], mask_files) + paths['dem'] = cast(list[str | None], dem_files) + paths['hydro'] = hydro_files return paths - def _load_folders(self) -> list[dict[str, str]]: - """Load folder paths. - - Returns: - list of dicts of s1, dem and masks folder paths - """ - paths = self._load_tif_files(check_folders=False, load_all=False) - - res_folders = [ - {'s1_raw': img_path, 'mask': mask_path, 'dem': dem_path} - for img_path, mask_path, dem_path in zip( - paths['image'], paths['mask'], paths['dem'] - ) - ] - - return res_folders - - def _load_image(self, index: list[int], query: BoundingBox) -> Tensor: - """Load a either a single image or a set of images, merging them. - - Args: - index: indexes to return - query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index - - Returns: - the merged image - """ - image = self._load_tif(index, modality='s1_raw', query=query).float() - if self.include_dem: - dem = self._load_tif(index, modality='dem', query=query).float() - image = torch.cat([image, dem], dim=0) - return image - - def _load_tif( - self, - index: list[int], - modality: Literal['s1_raw', 'dem', 'mask'], - query: BoundingBox, - ) -> Tensor: - """Load either a single geotif or a set of geotifs, merging them. + def __getitem__(self, query: BoundingBox) -> dict[str, Tensor]: + """Retrieve image/mask and metadata indexed by query. Args: - index: indexes to return - modality: s1_raw, dem or mask query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index Returns: - the merged image - """ - assert query is not None, 'Query must be specified.' - paths = [self.folders[idx][modality] for idx in index] - tensor = self._merge_files(paths, query) - return tensor - - def _load_target(self, index: list[int], query: BoundingBox) -> Tensor: - """Load the target mask for either a single image or a set of images, merging them. + sample of image, mask and metadata at that index - Args: - index: indexes to return - query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index - - Returns: - the target mask + Raises: + IndexError: if query is not found in the index """ - tensor = self._load_tif(index, modality='mask', query=query).type(torch.uint8) - return tensor.long().squeeze(dim=0) + data = super().__getitem__(query) + missing_data = data['image'].isnan().any(dim=0) + # Set all pixel values of invalid areas to 0, all mask values to 255 + data['image'][:, missing_data] = 0 + data['mask'][missing_data] = self._ignore_index + return data + + def _merge_dataset_indices(self) -> None: + """Create a new R-tree out of the individual indices from Sentinel-1, DEM and hydrography datasets.""" + ds1, _ = self.datasets + # Use ds1 index + self.index = ds1.index def _download(self) -> None: """Download the dataset.""" @@ -356,7 +437,7 @@ def _verify(self) -> None: # Check if both metadata file and directory exist if os.path.isdir(dirpath) and os.path.isfile(metadata_filepath): # Check pairings of all files - _ = self._load_tif_files(check_folders=True, load_all=True) + self._verify_tif_pairings() return if not self.download: raise DatasetNotFoundError(self) @@ -364,27 +445,36 @@ def _verify(self) -> None: self._merge_tar_files() self._extract() - def _verify_pairings( - self, s1_paths: list[str], dem_paths: list[str], mask_paths: list[str] - ) -> None: - """Verify all pairings of Sentinel-1, DEM and mask tif files. All inputs must be sorted. + def _verify_tif_pairings(self) -> None: + """Verify all pairings of Sentinel-1, DEM, hydro and mask tif files. All inputs must be sorted.""" + paths = self._load_tif_files() + s1_paths = cast(list[str], paths['image']) + dem_paths = cast(list[str], paths['dem']) + mask_paths = cast(list[str], paths['mask']) + hydro_paths = paths['hydro'] + + # Verify image, dem and mask lengths + assert ( + len(s1_paths) > 0 + ), f'No images found, is the given path correct? ({self.root!s})' - Args: - s1_paths: list of paths of Sentinel-1 tif files - dem_paths: list of paths of DEM tif files - mask_paths: list of paths of mask tif files - """ assert ( len(s1_paths) == len(dem_paths) == len(mask_paths) ), f'Lengths of s1, dem and mask files do not match! ({len(s1_paths)}, {len(dem_paths)}, {len(mask_paths)})' - for image, mask, dem in zip(s1_paths, mask_paths, dem_paths): - image_tile = pathlib.Path(image).stem - mask_tile = pathlib.Path(mask).stem - dem_tile = pathlib.Path(dem).stem + for image, mask, dem, hydro in zip( + s1_paths, mask_paths, dem_paths, hydro_paths + ): + image_tile = os.path.basename(image) + mask_tile = os.path.basename(mask) + dem_tile = os.path.basename(dem) + hydro_tile = os.path.basename(hydro) if hydro else None assert ( image_tile == mask_tile == dem_tile ), f'Filenames not matching: image {image_tile}; mask {mask_tile}; dem {dem_tile}' + assert ( + (hydro_tile == image_tile) or (hydro_tile is None) + ), f'Hydrography file not matching image file: image {image_tile}; hydrography {hydro_tile}' def plot( self, @@ -405,18 +495,22 @@ def plot( show_mask = 'mask' in sample image = sample['image'][[0, 1]].permute(1, 2, 0).numpy() ncols = 1 - mask_offset = 1 show_predictions = 'prediction' in sample if self.include_dem: - dem = sample['image'][-1].squeeze(0).numpy() + dem_idx = -2 if self.include_hydro else -1 + dem = sample['image'][dem_idx].squeeze(0).numpy() + ncols += 1 + if self.include_hydro: + hydro = sample['image'][-1].squeeze(0).numpy() ncols += 1 if show_mask: mask = sample['mask'].numpy() + # Set ignore_index values to 0 + mask[mask == self._ignore_index] = 0 ncols += 1 if show_predictions: pred = sample['prediction'].numpy() ncols += 1 - mask_offset = 2 # Compute False Color image, from biomassters plot function co_polarization = image[..., 0] # transmit == receive @@ -434,24 +528,37 @@ def plot( fig, axs = plt.subplots(ncols=ncols, figsize=(4 * ncols, 4)) axs[0].imshow(image) axs[0].axis('off') + axs_idx = 1 if self.include_dem: - axs[1].imshow(dem, cmap='gray') - axs[1].axis('off') + axs[axs_idx].imshow(dem, cmap='gray') + axs[axs_idx].axis('off') + axs_idx += 1 + if self.include_hydro: + axs[axs_idx].imshow(hydro, cmap='gray') + axs[axs_idx].axis('off') + axs_idx += 1 if show_mask: - axs[ncols - mask_offset].imshow(mask, cmap='gray') - axs[ncols - mask_offset].axis('off') + axs[axs_idx].imshow(mask, cmap='gray') + axs[axs_idx].axis('off') + axs_idx += 1 if show_predictions: - axs[ncols - 1].imshow(pred, cmap='gray') - axs[ncols - 1].axis('off') + axs[axs_idx].imshow(pred, cmap='gray') + axs[axs_idx].axis('off') if show_titles: axs[0].set_title('Image') + axs_idx = 1 if self.include_dem: - axs[1].set_title('DEM') + axs[axs_idx].set_title('DEM') + axs_idx += 1 + if self.include_hydro: + axs[axs_idx].set_title('Hydrography Map') + axs_idx += 1 if show_mask: - axs[ncols - mask_offset].set_title('Mask') + axs[axs_idx].set_title('Mask') + axs_idx += 1 if show_predictions: - axs[ncols - 1].set_title('Prediction') + axs[axs_idx].set_title('Prediction') if suptitle is not None: plt.suptitle(suptitle) From e3cf883143025c6e03a06c902f769958f77c68db Mon Sep 17 00:00:00 2001 From: Luca Colomba Date: Thu, 16 Jan 2025 12:05:19 +0100 Subject: [PATCH 9/9] simplified code and added missing docstrings --- tests/conf/mmflood.yaml | 1 - tests/data/mmflood/data.py | 7 - tests/datasets/test_mmflood.py | 39 ----- torchgeo/datamodules/mmflood.py | 24 +-- torchgeo/datasets/mmflood.py | 253 +++++--------------------------- 5 files changed, 48 insertions(+), 276 deletions(-) diff --git a/tests/conf/mmflood.yaml b/tests/conf/mmflood.yaml index e9c32663f9b..1a6301080a7 100644 --- a/tests/conf/mmflood.yaml +++ b/tests/conf/mmflood.yaml @@ -15,6 +15,5 @@ data: dict_kwargs: root: 'tests/data/mmflood' patch_size: 8 - normalization: 'median' include_dem: True include_hydro: True diff --git a/tests/data/mmflood/data.py b/tests/data/mmflood/data.py index ab6b97af265..4a684d401d5 100644 --- a/tests/data/mmflood/data.py +++ b/tests/data/mmflood/data.py @@ -62,13 +62,10 @@ def generate_data( with rasterio.open(filepath, mode='w', **profile2) as src: src.write(data[folder]) - return - def generate_tar_gz(src: str, dst: str) -> None: with tarfile.open(dst, 'w:gz') as tar: tar.add(src, arcname=src) - return def split_tar(path: str, dst: str, nparts: int) -> None: @@ -84,8 +81,6 @@ def split_tar(path: str, dst: str, nparts: int) -> None: with open(part_path, 'wb') as dst_fp: dst_fp.write(fp.read(bytes_to_write)) - return - def generate_folders_and_metadata(datapath: str, metadatapath: str) -> None: folders_splits = [ @@ -132,8 +127,6 @@ def generate_folders_and_metadata(datapath: str, metadatapath: str) -> None: with open(os.path.join(metadatapath, 'activations.json'), 'w') as fp: json.dump(metadata, fp) - return - if __name__ == '__main__': datapath = os.path.join(os.getcwd(), 'activations') diff --git a/tests/datasets/test_mmflood.py b/tests/datasets/test_mmflood.py index 593702b4b91..ce29c43b55f 100644 --- a/tests/datasets/test_mmflood.py +++ b/tests/datasets/test_mmflood.py @@ -20,7 +20,6 @@ MMFlood, UnionDataset, ) -from torchgeo.datasets.mmflood import MMFloodComponent, MMFloodIntersection class TestMMFlood: @@ -65,44 +64,6 @@ def test_getitem(self, dataset: MMFlood) -> None: nchannels += 1 assert x['image'].size(0) == nchannels - @pytest.fixture - def mock_intersection_dataset( - self, monkeypatch: MonkeyPatch, tmp_path: Path - ) -> MMFlood: - class MockIntersection(MMFloodIntersection): - def __init__( - self, - dataset1: MMFloodIntersection | MMFloodComponent, - dataset2: MMFloodIntersection | MMFloodComponent, - ) -> None: - super().__init__(dataset2, dataset1) - - monkeypatch.setattr( - 'torchgeo.datasets.mmflood.MMFloodIntersection', MockIntersection - ) - dataset_root = os.path.join('tests', 'data', 'mmflood/') - url = os.path.join(dataset_root) - monkeypatch.setattr(MMFlood, 'url', url) - monkeypatch.setattr(MMFlood, '_nparts', 2) - return MMFlood( - tmp_path, - split='train', - include_dem=True, - include_hydro=True, - transforms=nn.Identity(), - download=True, - checksum=True, - ) - - def test_swap_dataset(self, mock_intersection_dataset: MMFlood) -> None: - d = MMFlood( - mock_intersection_dataset.root, - split='train', - include_dem=True, - include_hydro=True, - ) - assert len(d) == 2 - def test_len(self, dataset: MMFlood) -> None: if dataset.split == 'train': if not dataset.include_hydro: diff --git a/torchgeo/datamodules/mmflood.py b/torchgeo/datamodules/mmflood.py index bb291fd0441..ddd6804cded 100644 --- a/torchgeo/datamodules/mmflood.py +++ b/torchgeo/datamodules/mmflood.py @@ -3,7 +3,7 @@ """MMFlood datamodule.""" -from typing import Any, Literal +from typing import Any import kornia.augmentation as K import torch @@ -23,7 +23,7 @@ class MMFloodDataModule(GeoDataModule): """ # Computed over train set - mean = torch.tensor([0.1785585, 0.03574104, 168.45529, 0.02248373255133629]) + # VV, VH, dem, hydro median = torch.tensor([0.116051525, 0.025692634, 86.0, 0.0]) std = torch.tensor([2.405442, 0.22719479, 242.74359, 0.1482505053281784]) @@ -33,7 +33,6 @@ def __init__( patch_size: int | tuple[int, int] = 512, length: int | None = None, num_workers: int = 0, - normalization: Literal['mean', 'median'] = 'median', **kwargs: Any, ) -> None: """Initialize a new MMFloodDataModule instance. @@ -43,7 +42,6 @@ def __init__( patch_size: Size of each patch, either ``size`` or ``(height, width)``. length: Length of each training epoch. num_workers: Number of workers for parallel data loading. - normalization: Either 'mean' or 'median', used to normalize the dataset **kwargs: Additional keyword arguments passed to :class:`~torchgeo.datasets.MMFlood`. """ @@ -55,10 +53,6 @@ def __init__( num_workers=num_workers, **kwargs, ) - assert ( - normalization in {'mean', 'median'} - ), f'Invalid normalization parameter specified {normalization}, must be either "mean" or "median".' - self.normalization = normalization avg, std = self._get_mean_std( dem=kwargs.get('include_dem', False), hydro=kwargs.get('include_hydro', False), @@ -67,8 +61,8 @@ def __init__( # Using median for normalization for better stability, # as stated by the original authors self.train_aug = K.AugmentationSequential( - K.Normalize(avg, std), K.RandomResizedCrop(_to_tuple(self.patch_size), p=0.8, scale=(0.5, 1.0)), + K.Normalize(avg, std), K.RandomHorizontalFlip(p=0.5), K.RandomVerticalFlip(p=0.5), K.RandomRotation90((0, 3), p=0.5), @@ -87,13 +81,21 @@ def __init__( def _get_mean_std( self, dem: bool = False, hydro: bool = False ) -> tuple[Tensor, Tensor]: - avg = self.mean if self.normalization == 'mean' else self.median + """Retrieve mean and standard deviation tensors used for normalization. + + Args: + dem: True if DEM data is loaded + hydro: True if hydrography data is loaded + + Returns: + mean and standard deviation tensors + """ idxs = [0, 1] # VV, VH if dem: idxs.append(2) if hydro: idxs.append(3) - return avg[idxs], self.std[idxs] + return self.median[idxs], self.std[idxs] def setup(self, stage: str) -> None: """Set up datasets. diff --git a/torchgeo/datasets/mmflood.py b/torchgeo/datasets/mmflood.py index 2ea686ef783..0330a8839da 100644 --- a/torchgeo/datasets/mmflood.py +++ b/torchgeo/datasets/mmflood.py @@ -6,10 +6,9 @@ from __future__ import annotations import os -from abc import ABC, abstractmethod from collections.abc import Callable from glob import glob -from typing import ClassVar, Literal, cast +from typing import ClassVar, Literal import matplotlib.pyplot as plt import numpy as np @@ -23,79 +22,45 @@ from .utils import BoundingBox, Path, download_url, extract_archive -class MMFloodComponent(RasterDataset, ABC): +class MMFloodComponent(RasterDataset): """Base component for MMFlood dataset.""" def __init__( self, subfolders: list[str], + content: Literal['s1_raw', 'DEM', 'hydro', 'mask'], root: Path = 'data', crs: CRS | None = None, res: float | None = None, transforms: Callable[[dict[str, Tensor]], dict[str, Tensor]] | None = None, cache: bool = False, ) -> None: - """Initialize MMFloodComponent dataset instance.""" + """Initialize MMFloodComponent dataset instance. + + Args: + subfolders: list of directories to be loaded + content: specifies which component to load + root: root directory where dataset can be found + crs: :term:`coordinate reference system (CRS)` to warp to + (defaults to the CRS of the first file found) + res: resolution of the dataset in units of CRS + (defaults to the resolution of the first file found) + transforms: a function/transform that takes input sample and its target as + entry and returns a transformed version + cache: if True, cache file handle to speed up repeated sampling + """ + self.content = content + self.is_image = content != 'mask' paths = [] for s in subfolders: - paths += glob(os.path.join(root, '*', f'{s}*-*', self.content, '*.tif')) + paths += glob(os.path.join(root, '**', f'{s}*-*', self.content, '*.tif')) paths = sorted(paths) super().__init__(paths, crs, res, transforms=transforms, cache=cache) - return - - @property - @abstractmethod - def content(self) -> str: - """Returns the name of the folder containing the tif files to be read.""" - - -class MMFloodS1(MMFloodComponent): - """Sentinel-1 component for MMFlood dataset.""" - - @property - def content(self) -> str: - """The subfolder containing Sentinel-1 data.""" - return 's1_raw' - - -class MMFloodDEM(MMFloodComponent): - """DEM component for MMFlood dataset.""" - - @property - def content(self) -> str: - """The subfolder containing DEM data.""" - return 'DEM' - - -class MMFloodMask(MMFloodComponent): - """Mask component for MMFlood dataset.""" - - is_image = False - - @property - def content(self) -> str: - """The subfolder containing mask data.""" - return 'mask' - - -class MMFloodHydro(MMFloodComponent): - """Hydrography map component for MMFlood dataset.""" - - @property - def content(self) -> str: - """The subfolder containing hydrography data.""" - return 'hydro' class MMFloodIntersection(IntersectionDataset): """Intersection dataset used to merge two or more MMFloodComponents.""" - _ordering: ClassVar[dict[type[MMFloodComponent], int]] = { - MMFloodS1: 0, - MMFloodDEM: 1, - MMFloodHydro: 2, - } - def __init__( self, dataset1: MMFloodComponent | MMFloodIntersection, @@ -107,53 +72,9 @@ def __init__( dataset1: the first dataset to merge dataset2: the second dataset to merge """ - dataset1, dataset2 = self._swap_datasets(dataset1, dataset2) + # if hydro component is passed, it should always be passed as dataset2 super().__init__(dataset1, dataset2) - @property - def contains_hydro(self) -> bool: - """A flag stating whether the Hydrography dataset is present.""" - # If Hydro dataset is present, it is always the last dataset - return isinstance(self.datasets[1], MMFloodHydro) - - def _swap_datasets( - self, - ds1: MMFloodComponent | MMFloodIntersection, - ds2: MMFloodComponent | MMFloodIntersection, - ) -> tuple[MMFloodComponent | MMFloodIntersection, MMFloodComponent]: - """Sort the datasets in the correct order (Sentinel-1, DEM, Hydrography). - - Arguments: - ds1: first dataset. Must be either an instance of MMFloodComponent or MMFloodIntersection - ds2: second dataset. Must be either an instance of MMFloodComponent or MMFloodIntersection - - Returns: - the two datasets in the correct order - """ - assert not ( - isinstance(ds1, MMFloodIntersection) - and isinstance(ds2, MMFloodIntersection) - ), 'Cannot intersect two Intersection datasets!' - # If one of the two datasets is an instance of MMFloodIntersection, return it first - if isinstance(ds1, MMFloodIntersection): - assert ( - not ds1.contains_hydro - ), 'Instance of MMFloodHydro should be merged as last element!' - ds2 = cast(MMFloodComponent, ds2) - return ds1, ds2 - elif isinstance(ds2, MMFloodIntersection): - assert ( - not ds2.contains_hydro - ), 'Instance of MMFloodHydro should be merged as last element!' - return ds2, ds1 - # Always intersect the datasets in this order: - # Sentinel-1, DEM, Hydro, if present - res = cast( - tuple[MMFloodComponent, MMFloodComponent], - sorted((ds1, ds2), key=lambda x: self._ordering[type(x)]), - ) - return res - def _merge_dataset_indices(self) -> None: """Create a new R-tree out of the individual indices from Sentinel-1, DEM and hydrography datasets.""" _, ds2 = self.datasets @@ -194,19 +115,8 @@ class MMFlood(IntersectionDataset): """ url = 'https://huggingface.co/datasets/links-ads/mmflood/resolve/24ca097306c9e50ad0711903c11e1ba13ea1bedc/' - _name = 'mmflood' - _categories: ClassVar[dict[int, str]] = {0: 'background', 1: 'flood'} - _palette: ClassVar[dict[int, tuple[int, int, int]]] = { - 0: (0, 0, 0), - 1: (255, 255, 255), - 255: (255, 0, 255), - } _ignore_index = 255 _nparts = 11 - # VV, VH, dem, hydro - _mean = (0.1785585, 0.03574104, 168.45529, 0.02248373255133629) - _median = (0.116051525, 0.025692634, 86.0, 0.0) - _std = (2.405442, 0.22719479, 242.74359, 0.1482505053281784) metadata: ClassVar[dict[str, str]] = { 'part_file': 'activations.tar.{part}.gz.part', @@ -277,21 +187,26 @@ def __init__( self.checksum = checksum # Verify integrity of the dataset self._verify() - self.metadata_df = self._load_metadata() + self.metadata_df = pd.read_json( + os.path.join(self.root, self.metadata['metadata_file']) + ).transpose() - self.image: MMFloodComponent | MMFloodIntersection = MMFloodS1( - self._get_split_subfolders(), root, crs, res, cache=cache + split_subfolders = self.metadata_df[ + self.metadata_df['subset'] == self.split + ].index.tolist() + self.image: MMFloodComponent | MMFloodIntersection = MMFloodComponent( + split_subfolders, 's1_raw', root, crs, res, cache=cache ) if include_dem: - dem = MMFloodDEM(self._get_split_subfolders(), root, crs, res, cache=cache) + dem = MMFloodComponent(split_subfolders, 'DEM', root, crs, res, cache=cache) self.image = MMFloodIntersection(self.image, dem) if include_hydro: - hydro = MMFloodHydro( - self._get_split_subfolders(), root, crs, res, cache=cache + hydro = MMFloodComponent( + split_subfolders, 'hydro', root, crs, res, cache=cache ) self.image = MMFloodIntersection(self.image, hydro) - self.mask = MMFloodMask( - self._get_split_subfolders(), root, crs, res, cache=cache + self.mask = MMFloodComponent( + split_subfolders, 'mask', root, crs, res, cache=cache ) super().__init__(self.image, self.mask, transforms=transforms) @@ -311,71 +226,6 @@ def _merge_tar_files(self) -> None: with open(part_path, 'rb') as part_fp: dst_fp.write(part_fp.read()) - def _load_metadata(self) -> pd.DataFrame: - """Load metadata. - - Returns: - dataframe containing metadata - """ - df = pd.read_json( - os.path.join(self.root, self.metadata['metadata_file']) - ).transpose() - return df - - def _get_split_subfolders(self) -> list[str]: - """Get list of EMSR data folders to load, depending on specified split. - - Returns: - list of EMSR codes to be loaded - """ - folders = self.metadata_df[ - self.metadata_df['subset'] == self.split - ].index.tolist() - return cast(list[str], folders) - - def _load_tif_files(self) -> dict[str, list[str | None]]: - """Load paths of all tif files for Sentinel-1, DEM, hydrography and masks. - - Returns: - dict containing list of paths, with 'image', 'dem', 'hydro' and 'mask' as keys - """ - paths: dict[str, list[str | None]] = {} - dirpath = os.path.join(self.root, self.metadata['directory']) - folders = os.listdir(dirpath) - - # initialize tif file lists containing masks, DEM, hyd and S1_raw data - image_files = [] - mask_files = [] - dem_files = [] - hydro_files = [] - - def _get_filename_to_path_mapping( - basepath: str, subfolder: Literal['s1_raw', 'mask', 'hydro', 'DEM'] - ) -> dict[str, str]: - # Assemble path and return a mapping filename -> complete path - paths = glob(os.path.join(basepath, subfolder, '*.tif')) - return {os.path.basename(p): p for p in paths} - - for f in sorted(folders): - path = os.path.join(self.root, self.metadata['directory'], f) - images = _get_filename_to_path_mapping(path, 's1_raw') - masks = _get_filename_to_path_mapping(path, 'mask') - dems = _get_filename_to_path_mapping(path, 'DEM') - hydros = _get_filename_to_path_mapping(path, 'hydro') - assert len(images) == len(masks) == len(dems) - for filename in sorted(images.keys()): - image_files.append(images[filename]) - mask_files.append(masks[filename]) - dem_files.append(dems[filename]) - hydro_files.append(hydros.get(filename, None)) - - paths['image'] = cast(list[str | None], image_files) - paths['mask'] = cast(list[str | None], mask_files) - paths['dem'] = cast(list[str | None], dem_files) - paths['hydro'] = hydro_files - - return paths - def __getitem__(self, query: BoundingBox) -> dict[str, Tensor]: """Retrieve image/mask and metadata indexed by query. @@ -436,8 +286,6 @@ def _verify(self) -> None: metadata_filepath = os.path.join(self.root, self.metadata['metadata_file']) # Check if both metadata file and directory exist if os.path.isdir(dirpath) and os.path.isfile(metadata_filepath): - # Check pairings of all files - self._verify_tif_pairings() return if not self.download: raise DatasetNotFoundError(self) @@ -445,37 +293,6 @@ def _verify(self) -> None: self._merge_tar_files() self._extract() - def _verify_tif_pairings(self) -> None: - """Verify all pairings of Sentinel-1, DEM, hydro and mask tif files. All inputs must be sorted.""" - paths = self._load_tif_files() - s1_paths = cast(list[str], paths['image']) - dem_paths = cast(list[str], paths['dem']) - mask_paths = cast(list[str], paths['mask']) - hydro_paths = paths['hydro'] - - # Verify image, dem and mask lengths - assert ( - len(s1_paths) > 0 - ), f'No images found, is the given path correct? ({self.root!s})' - - assert ( - len(s1_paths) == len(dem_paths) == len(mask_paths) - ), f'Lengths of s1, dem and mask files do not match! ({len(s1_paths)}, {len(dem_paths)}, {len(mask_paths)})' - - for image, mask, dem, hydro in zip( - s1_paths, mask_paths, dem_paths, hydro_paths - ): - image_tile = os.path.basename(image) - mask_tile = os.path.basename(mask) - dem_tile = os.path.basename(dem) - hydro_tile = os.path.basename(hydro) if hydro else None - assert ( - image_tile == mask_tile == dem_tile - ), f'Filenames not matching: image {image_tile}; mask {mask_tile}; dem {dem_tile}' - assert ( - (hydro_tile == image_tile) or (hydro_tile is None) - ), f'Hydrography file not matching image file: image {image_tile}; hydrography {hydro_tile}' - def plot( self, sample: dict[str, Tensor], @@ -512,7 +329,7 @@ def plot( pred = sample['prediction'].numpy() ncols += 1 - # Compute False Color image, from biomassters plot function + # Compute False Color image, from Sentinel1 plot function co_polarization = image[..., 0] # transmit == receive cross_polarization = image[..., 1] # transmit != receive ratio = co_polarization / cross_polarization