From 0434f3c1ce95ef12422ddf9333b3a75324d9cffc Mon Sep 17 00:00:00 2001 From: isaac <22203655+isaaccorley@users.noreply.github.com> Date: Sun, 5 Dec 2021 16:38:50 -0600 Subject: [PATCH] Add IDTReeS dataset (#201) * add IDTReeS dataset * dataset loads data now * add optional laspy and pandas dependencies * fixed docs failing * format * refactor verify and resample chm/hsi to 200x200 * add open3d optional dep * overhaul * temporarily remove open3d install bc their pypi is broken * mypy fixes * fixes per suggestions * general cleanup * test passing * add min version for laspy and pandas * add open3d dependency * add open3d to mypy tests * add hard install for python 3.9 open3d to actions * attempt #2 * I think I got it now * updated tests.yaml * make open3d dep require python<3.9 * open3d has issues with macos python 3.6 * same for 3.7 * skip open3d plot test for macos * formatting * skip open3d plot test for windows * update per suggestions * update test data readme for las files * updated per suggestions * more changes per suggestions * last change per suggestion * Grammar fix in pandas dep requirement comment Co-authored-by: Adam J. Stewart --- docs/api/datasets.rst | 5 + environment.yml | 4 + setup.cfg | 7 + tests/data/README.md | 25 + .../idtrees/IDTREES_competition_test_v2.zip | Bin 0 -> 16359 bytes .../idtrees/IDTREES_competition_train_v2.zip | Bin 0 -> 12378 bytes .../task1/RemoteSensing/CHM/MLBS_4.tif | Bin 0 -> 423 bytes .../task1/RemoteSensing/CHM/OSBS_11.tif | Bin 0 -> 423 bytes .../task1/RemoteSensing/CHM/TALL_1.tif | Bin 0 -> 423 bytes .../task1/RemoteSensing/HSI/MLBS_4.tif | Bin 0 -> 5536 bytes .../task1/RemoteSensing/HSI/OSBS_11.tif | Bin 0 -> 5536 bytes .../task1/RemoteSensing/HSI/TALL_1.tif | Bin 0 -> 5536 bytes .../task1/RemoteSensing/LAS/MLBS_4.las | Bin 0 -> 1188 bytes .../task1/RemoteSensing/LAS/OSBS_11.las | Bin 0 -> 441 bytes .../task1/RemoteSensing/LAS/TALL_1.las | Bin 0 -> 441 bytes .../task1/RemoteSensing/RGB/MLBS_4.tif | Bin 0 -> 459 bytes .../task1/RemoteSensing/RGB/OSBS_11.tif | Bin 0 -> 459 bytes .../task1/RemoteSensing/RGB/TALL_1.tif | Bin 0 -> 459 bytes tests/data/idtrees/task2/ITC/test_MLBS.dbf | Bin 0 -> 302 bytes tests/data/idtrees/task2/ITC/test_MLBS.shp | Bin 0 -> 372 bytes tests/data/idtrees/task2/ITC/test_MLBS.shx | Bin 0 -> 116 bytes tests/data/idtrees/task2/ITC/test_OSBS.dbf | Bin 0 -> 300 bytes tests/data/idtrees/task2/ITC/test_OSBS.shp | Bin 0 -> 372 bytes tests/data/idtrees/task2/ITC/test_OSBS.shx | Bin 0 -> 116 bytes tests/data/idtrees/task2/ITC/test_TALL.dbf | Bin 0 -> 302 bytes tests/data/idtrees/task2/ITC/test_TALL.shp | Bin 0 -> 372 bytes tests/data/idtrees/task2/ITC/test_TALL.shx | Bin 0 -> 116 bytes .../task2/RemoteSensing/CHM/MLBS_1.tif | Bin 0 -> 423 bytes .../task2/RemoteSensing/CHM/OSBS_15.tif | Bin 0 -> 423 bytes .../task2/RemoteSensing/CHM/TALL_2.tif | Bin 0 -> 423 bytes .../task2/RemoteSensing/HSI/MLBS_1.tif | Bin 0 -> 5536 bytes .../task2/RemoteSensing/HSI/OSBS_15.tif | Bin 0 -> 5536 bytes .../task2/RemoteSensing/HSI/TALL_2.tif | Bin 0 -> 5536 bytes .../task2/RemoteSensing/LAS/MLBS_1.las | Bin 0 -> 1188 bytes .../task2/RemoteSensing/LAS/OSBS_15.las | Bin 0 -> 679 bytes .../task2/RemoteSensing/LAS/TALL_2.las | Bin 0 -> 441 bytes .../task2/RemoteSensing/RGB/MLBS_1.tif | Bin 0 -> 459 bytes .../task2/RemoteSensing/RGB/OSBS_15.tif | Bin 0 -> 459 bytes .../task2/RemoteSensing/RGB/TALL_2.tif | Bin 0 -> 459 bytes tests/data/idtrees/train/ITC/train_MLBS.dbf | Bin 0 -> 2078 bytes tests/data/idtrees/train/ITC/train_MLBS.shp | Bin 0 -> 3092 bytes tests/data/idtrees/train/ITC/train_MLBS.shx | Bin 0 -> 276 bytes tests/data/idtrees/train/ITC/train_OSBS.dbf | Bin 0 -> 2258 bytes tests/data/idtrees/train/ITC/train_OSBS.shp | Bin 0 -> 3364 bytes tests/data/idtrees/train/ITC/train_OSBS.shx | Bin 0 -> 292 bytes .../train/RemoteSensing/CHM/MLBS_1.tif | Bin 0 -> 423 bytes .../train/RemoteSensing/CHM/OSBS_1.tif | Bin 0 -> 423 bytes .../train/RemoteSensing/CHM/OSBS_39.tif | Bin 0 -> 423 bytes .../train/RemoteSensing/HSI/MLBS_1.tif | Bin 0 -> 5536 bytes .../train/RemoteSensing/HSI/OSBS_1.tif | Bin 0 -> 5536 bytes .../train/RemoteSensing/HSI/OSBS_39.tif | Bin 0 -> 5536 bytes .../train/RemoteSensing/LAS/MLBS_1.las | Bin 0 -> 1188 bytes .../train/RemoteSensing/LAS/OSBS_1.las | Bin 0 -> 441 bytes .../train/RemoteSensing/LAS/OSBS_39.las | Bin 0 -> 441 bytes .../train/RemoteSensing/RGB/MLBS_1.tif | Bin 0 -> 459 bytes .../train/RemoteSensing/RGB/OSBS_1.tif | Bin 0 -> 459 bytes .../train/RemoteSensing/RGB/OSBS_39.tif | Bin 0 -> 459 bytes tests/datasets/test_idtrees.py | 162 +++++ torchgeo/datasets/__init__.py | 2 + torchgeo/datasets/idtrees.py | 553 ++++++++++++++++++ 60 files changed, 758 insertions(+) create mode 100644 tests/data/idtrees/IDTREES_competition_test_v2.zip create mode 100644 tests/data/idtrees/IDTREES_competition_train_v2.zip create mode 100644 tests/data/idtrees/task1/RemoteSensing/CHM/MLBS_4.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/CHM/OSBS_11.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/CHM/TALL_1.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/HSI/MLBS_4.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/HSI/OSBS_11.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/HSI/TALL_1.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/LAS/MLBS_4.las create mode 100644 tests/data/idtrees/task1/RemoteSensing/LAS/OSBS_11.las create mode 100644 tests/data/idtrees/task1/RemoteSensing/LAS/TALL_1.las create mode 100644 tests/data/idtrees/task1/RemoteSensing/RGB/MLBS_4.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/RGB/OSBS_11.tif create mode 100644 tests/data/idtrees/task1/RemoteSensing/RGB/TALL_1.tif create mode 100644 tests/data/idtrees/task2/ITC/test_MLBS.dbf create mode 100644 tests/data/idtrees/task2/ITC/test_MLBS.shp create mode 100644 tests/data/idtrees/task2/ITC/test_MLBS.shx create mode 100644 tests/data/idtrees/task2/ITC/test_OSBS.dbf create mode 100644 tests/data/idtrees/task2/ITC/test_OSBS.shp create mode 100644 tests/data/idtrees/task2/ITC/test_OSBS.shx create mode 100644 tests/data/idtrees/task2/ITC/test_TALL.dbf create mode 100644 tests/data/idtrees/task2/ITC/test_TALL.shp create mode 100644 tests/data/idtrees/task2/ITC/test_TALL.shx create mode 100644 tests/data/idtrees/task2/RemoteSensing/CHM/MLBS_1.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/CHM/OSBS_15.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/CHM/TALL_2.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/HSI/MLBS_1.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/HSI/OSBS_15.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/HSI/TALL_2.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/LAS/MLBS_1.las create mode 100644 tests/data/idtrees/task2/RemoteSensing/LAS/OSBS_15.las create mode 100644 tests/data/idtrees/task2/RemoteSensing/LAS/TALL_2.las create mode 100644 tests/data/idtrees/task2/RemoteSensing/RGB/MLBS_1.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/RGB/OSBS_15.tif create mode 100644 tests/data/idtrees/task2/RemoteSensing/RGB/TALL_2.tif create mode 100644 tests/data/idtrees/train/ITC/train_MLBS.dbf create mode 100644 tests/data/idtrees/train/ITC/train_MLBS.shp create mode 100644 tests/data/idtrees/train/ITC/train_MLBS.shx create mode 100644 tests/data/idtrees/train/ITC/train_OSBS.dbf create mode 100644 tests/data/idtrees/train/ITC/train_OSBS.shp create mode 100644 tests/data/idtrees/train/ITC/train_OSBS.shx create mode 100644 tests/data/idtrees/train/RemoteSensing/CHM/MLBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/CHM/OSBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/CHM/OSBS_39.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/HSI/MLBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/HSI/OSBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/HSI/OSBS_39.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/LAS/MLBS_1.las create mode 100644 tests/data/idtrees/train/RemoteSensing/LAS/OSBS_1.las create mode 100644 tests/data/idtrees/train/RemoteSensing/LAS/OSBS_39.las create mode 100644 tests/data/idtrees/train/RemoteSensing/RGB/MLBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/RGB/OSBS_1.tif create mode 100644 tests/data/idtrees/train/RemoteSensing/RGB/OSBS_39.tif create mode 100644 tests/datasets/test_idtrees.py create mode 100644 torchgeo/datasets/idtrees.py diff --git a/docs/api/datasets.rst b/docs/api/datasets.rst index b5a33461a..0bdf71dd2 100644 --- a/docs/api/datasets.rst +++ b/docs/api/datasets.rst @@ -118,6 +118,11 @@ GID-15 (Gaofen Image Dataset) .. autoclass:: GID15 +IDTReeS +^^^^^^^ + +.. autoclass:: IDTReeS + LandCover.ai (Land Cover from Aerial Imagery) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/environment.yml b/environment.yml index aef403329..17cea448a 100644 --- a/environment.yml +++ b/environment.yml @@ -1,6 +1,7 @@ name: torchgeo channels: - conda-forge + - open3d-admin dependencies: - cudatoolkit - einops @@ -23,11 +24,14 @@ dependencies: - isort[colors]>=5.8 - jupyterlab - kornia>=0.5.4 + - laspy>=2.0.0 - mypy>=0.900 - nbmake>=0.1 - nbsphinx>=0.8.5 - omegaconf>=2.1 + - open3d>=0.11.2 - opencv-python + - pandas>=0.19.1 - pillow>=2.9 - pydocstyle[toml]>=6.1 - pytest>=6 diff --git a/setup.cfg b/setup.cfg index f665c337f..ebcf9b3d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -65,7 +65,14 @@ include = torchgeo* # Optional dataset requirements datasets = h5py + # loading .las point clouds (idtrees) laspy 2+ required for Python 3.6+ support + laspy>=2.0.0 + # open3d will add add support for python 3.9 in pypi in v0.14. v0.11.2 last version for tests to pass + # https://github.com/isl-org/Open3D/issues/1550 + open3d>=0.11.2;python_version<'3.9' opencv-python + # pandas 0.19.1+ required for python 3.6 support + pandas>=0.19.1 pycocotools # radiant-mlhub 0.2.1+ required for api_key bugfix: # https://github.com/radiantearth/radiant-mlhub/pull/48 diff --git a/tests/data/README.md b/tests/data/README.md index 35cca49b9..3884cbf38 100644 --- a/tests/data/README.md +++ b/tests/data/README.md @@ -91,3 +91,28 @@ masks = np.random.randint(low=0, high=num_classes, size=(1, 1)).astype(np.uint8) f.create_dataset("images", data=images) f.create_dataset("masks", data=masks) f.close() +``` + +### LAS Point Cloud files + +```python +import laspy + +num_points = 4 + +las = laspy.read("0.las") +las.points = las.points[:num_points] + +points = np.random.randint(low=0, high=100, size=(num_points,), dtype=las.x.dtype) +las.x = points +las.y = points +las.z = points + +if hasattr(las, "red"): + colors = np.random.randint(low=0, high=10, size=(num_points,), dtype=las.red.dtype) + las.red = colors + las.green = colors + las.blue = colors + +las.write("0.las") +``` diff --git a/tests/data/idtrees/IDTREES_competition_test_v2.zip b/tests/data/idtrees/IDTREES_competition_test_v2.zip new file mode 100644 index 0000000000000000000000000000000000000000..26870fef1904fb6b8f4ccf4dda637bb26adcb63f GIT binary patch literal 16359 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 literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/IDTREES_competition_train_v2.zip b/tests/data/idtrees/IDTREES_competition_train_v2.zip new file mode 100644 index 0000000000000000000000000000000000000000..1385991f498b3c03e1ce1f7dc5b2bc12e98b8738 GIT binary patch literal 12378 zcmdT~2{hF2*B{0flScM^O^O)Cn#fX=B|<`$!5CSy?_{U4CEJj-ETNDkOLkeZ3sLqZ zOM{f9P`xu*qT<)@{lDiu@9UheV@}QIexJ{M?tMPby;oTth(id#{9wD;tN!?7>lYq? z4q#`aZ(_-FMePs(_^LEkAM@Bd5dpAprhx#!k6+3=-{<$(`+c&V@2ePD+^{oJHL|od zu{7R9`&1FDzl47FMQh02i$=8%+6C2%n}~vxar&6&4~S&w6%hb1h`M9F=WwwAfFyDN zfb{@GB~>X^J*Wt`oeAQ{;`agnwA5`d0)xT8=m(B~;XIA;j3K$ziUX&_7Mb!iM`Q>S z5JN(0E(>Rm zO1!pvU`5tN!5518$PB>jfS(Lg1~0d5GtBZ&4DJQCQJZH)CQ_T}h&H6aB%;TmBwa|Pj* zFV2q${oi>AfIDes5wn*egn0BYI==^28ns?eX3+k8cnZ#d`6&Gh!91>Sh5m+MMFsR0 z{(nkv2{$f!2PX}VFA$I#;yg>}qo&eeGMC|Kt$UiM17d5>%Zrbf4Dt#FvJIuTQ~^_< z@6i#KW>f5%>+N^TyF%)EV2;!k58X`fq3OK({6d_2v=w7>-ZZ`Sbwzr;FMp_C*B zx@&5y@E(?U()2!qLOB5a>JlsU4(xa$Ol5?PmfAISOo!m@FyEL|fPV{2*o>V3RS?h# zItK_9zyyOn@(ACM`@zT}?iDmgh=!$3PAqi&kxtS+=qHA-pshe|GkI1iMVyqc`;wpu-?SEQ*ufpq1c| zBZp7pAp5?&AwHgG)dDxb{)KC}lXB9Lt&M#U*Z(pX{h#34R)-M1uO-VGhB2q81Ft9$ zNaWi}kc!k0EV|=0AtG3m4-Mqawf3xAkDzvvutSIu#q_EXJ04Nbg35sS$Nj;$v=6Z)yF*1$ z?L{s+C^BYOR?_@MS?#BV6eLwQv(o9xSbfa1uNKP7vsZm(Q$xavc3#N=SqX! zZGUK?d8)WSfq$|FIh3_{9->0#5u|vg+^;k6WAgK^n?ThqsVO*LtxregP=lzw5LnJF zav&~{GJ9;zSR#+b616_Jpzz% zH?_B)P;XRH<&fiQRKQxx7S^j46KVoZ7{~;cIZMD1<-A#}we*%e{*P%T-7cj$f?Bf! zZnw8^MZ@giebu?a`oY9uywMGJk`eSkAgK>n;|1~D9WXf-$P1LWe3Oo92>S};2Eb1S ztZ{zvUNJ6={5zEg zc!&c4>=!&3{H~nG1pE!Oh?Pjl(>2Ahl-A0Wtd*d7D3(*N1UVQ3c@w~6t$Yh|) zm;VK$wLh1;1vDSdW^Gv(SVl0`O25P&2vHh3Lje2^AYWv0T4hSOeLUbc&)jT=#S~@u zLi6gUad+Kt=L&vNK&jb?a;Uy6?i2EGni7NlXW2Bf^?qJFvK3xSV1Q5F@CnbWk0*zE zo+R?nFixId9;+0qA2JVf8+ac_IQ%YA#BHH20H5vq>^jd^v3b1)Xp`i6k zOVbk_E9&3+(v3Mqm5M&Di?)u}#+r`sFpJu_9eMuzCLPz4drov;Drmz!B0<^Y>E22d zYY&T`YXsh#pQI02XdASQ-sp7n`EGkzVDh6~lc$jmd(Juo3)1{9+1&*-s;Gbn4vsOb z51^cABcNQVpjj9HLpf8)4l9 zX4ed9h0sOU-%&XIR=SOs-&GW6*(?5 zpAlJ~`UE~0>J%J}tJ49R*1AhN_=M?IPOQgBH(kza+Uc;q#yi-}0@0U8)aZza+NMgS z9WF?{wYjTVh;q7#YPTLx)3fQZvDSwB-5u{?iZtO@JQUqb&@@kELF%k#d<`;$-E@=dNCYN_W;n z-h(0%$~YH@L)dBF;}oY@@aNr%yJ&@FsMJm}0u_$Co=pOSKl3|m-S~b}6FulS2$k_0jsP@85amHsMeAfA&n-$nKs9Z`nBGzdVNx zx)@0)F&e(Hao@migWoTu&bx&ysr~jR@&Vg>XC$~0zQu9)=McR-8YazQk+ZG2)?z>) zr!<%pzt*-_>TP7jm|IW?=rz?PX!ewrW%CEfi#SPB;04liFE(cF!Q|0YAy6&{VCD!` ztTpO12S1LcNLNC>%R50CpDT2ReYFs?9;s`10!f7N!5|PFNGga<;ljx_=zW+5!LHNe zZ#Av-1;u}%X<=ond?@t2I{kpt1B1AIdi?E88opQ48gT-6p%JZwb>_-~1VnsKJ!g;- zQe+pkf83&J#F?Tbk3KuE!U!^g`g99?@|?N0tz*w(Ev`8ra<4^r5^mj^FG@I!A7#h8 z$W(>}2hh3Y%BC&?G2)ep)nU=~bWI&qHTQpdk;Bt56f=~EKm_>sd3gn)ynKp1f<>_F zFl7RW{^y2w9UN86On_a-0LC?)7m}2>iQF$Zc8L6Yz52h(qYp-yO3Jv6tlIhB;`P?4 zYpbfl)`v=&utx>Y88A7A1ka&B1|V}frbTfhJO*&QW;=G8KxFNp4>^|t*%G;wv>SQm z$4@Hcpw_RZOeq4O(t4NrC>YEXe%-*p9AN;D&9pGD?x;!v!)iW+wkBWpl$Q9FNAmmk zjQ)nlf2l?PcX`B!PA1j@*A04ogCH2?e_FZmd6JiqcZ@2AhB`KeDh^47Of#ju2H}Tr z;KV>S3~jHIjexa(8+-f zog99ORSd?Df}$QKDRA3fclx0x&!N#|en&KfDNh+x5wn-W5HJ`nAE8CblS)nIQ3AN2 zIeME#+;FEaHVyV=Rqp!&3=5S$%Ll7Ptp=U zhdYrJv{`b7jiVeSSLxls<)U0lAa}#3tCER7i$22AyQ_hTpO!LW1=zVOJ~3;#+rVqm zhF(h-!2xT58*J7I_gPC}4=+&-1dKc4c`q<4Q1xcb;A9tnR%vlYN2}uW*civ>g~Fot zWMT0e1YT7_Yzy4^QwD{^%-XfbHZrfR4l=A8iHlV_hR)|MJS+1MQEH3sN#~anFMO+h zZh#X}K8XnH@jWFv(HKPvH*6MwA8S7OAY0&?dgCoFd9RjB@iY@PQLONfL~_*gB8X$@ zZI7uZBl>N;725o-c_}2&S_;5zW(Pog&MYM&a2LTb4pC>z;xXL*o3~}X6y`0TflBn7 zr!CWs$6aYB%s2E|EOdV!?ihY;B32W1m`ePwRsrWdRwK6UO8Z9G4V_vs8!6td)6N3~^PXJ>wN zcJDXsqG=VeVUJH8?`!#)*4{3xR*0{+wFr^oh^k7WI_;(mSqNwrU#FUzdYEwCt4(~J zP{8og;*^>zdyN7y-=nD7C+0&!EFYy5b?at0c-^#HDc8dm z&Eq|(60LC6tZDXMr_C|j52E^yp9dB?M#`n)a*HQEmLs-rvMY?r7NWa8 zR1vdK)&GWWHhjkPl%_kMmLQ~IqT0_5+RJbluG*4&XRGO1JwMHxXz{|Efpp=^EUz6N)cR+ehrkoS2}^pO0!-yl9glpO#e! zUv9dNgcA#~3W+){xL0Y>)V}|iAEyzqW7{LgfVd^MygIui`)$=#u;x@UH}Xkuc#n`s zEOOlbasGl(8+B2Vu17$fQ@s4?Bld@*$4n1r4b#R)N3<^5ntwrsXNYN;4wFRJmcmQ5 zjVovjkB2Dfx9Yr)t{x=%BKC^+ zQ|_k0;}0fj-c^B!DBa%Szv?XHCtEY~kl9(p=kh3#0j|(5L6vDKlFI`#7^rFTT$=U)zzO+ETZDg7!44ZE^W3U2pF4 zTzs%;fZMxuqoytDjz2JZlMp@sVOTWH^{NPC?=Z8eD(_V4+w4d32%fpe%TcTwTdnzw~({A%Svd3kkh5X^-%pCq>7}*Jaq=Ua{)qLB#6F)eXZbLDBiJmFh9)Wc+XGAlpD*EyS1Vs~(P- zI;80>13+#B$~*6lqpfZSt(bZ3o=|>e@PWM|+b3=6Jh(bRgPo-u?K^ za*RtH9K1$F&E|vk0z8NLT(uI*J_cL)=*cPaFT=J>sb(E_)nHiiKOy8S-*ZJ^UEo>Y z$k9u0MK3o_RdZ8u8&{)zdd`P3IM=`F#zF@7p>DDmQB(+uFJUznGJO*O@4e1#ar;AKNT0Fs2j^TO|7K0d zI!tq>Vbx+O=Ek$wxlZR(b6^L7cN>oJH7|m6AP$R`ZA7yjtmz+>Yw(f%wY@8xtIO#@ zZIb$C2ur#|{4!Yaecb#bQIYwbtltg4gm@r>OJ6?k=9;|RbXR}$-jf^i7iPDYVl+v& zq+#Qdkt&af%3C_L9F+0=TH4ik(t-0{B)f>axpciF&U>f{9{eVyhm*)TEsQf+s+>uT)mU%dL{u0oEQ2w%k`{?7-!dtM2?6R6s1o^nz=_U^R-ZP7;zrmxR zykIghTQN9W_7XYy#!38$L$#F%5rT1pa&JV(Rtr71LaPz7eU#N!L~kmqeIj~GS(WNS z(2YSUk*QKLI?9!oPt;J0j%OClRswP)#FdLBX&W1o8L6qT@_^uI$r&BxHAY{Aop+ao z@*0^hV$7#YQrU$jq5?M=Ihka4G(s0IRZsFdg&2npPUMpaU3Gz*F&AW$_ zje+qjsMypVg_~0Z$oRvOv%Z5TdMD0BooAALnjK?*EB>%_Yf~Q|LI>`6yFk;J_DOc- z>tbid{_kYf+09;RpVo*lzTH&bDrjcVr0o2Fc9ge_iIF*cQ+vlT$DvzE2WW3$G>W@@ zYAQFnq}W3N0MPt`vZ=n|`gZ!3 zafAKT$L)Jt?a~|xGl~$B#TSa--yCLz5MNRV*9qbYyd~|#*tRM;V;<-J)i>zrQSt20 zmrYmAG9MPrGDZ&Fkt))=NxCMQX|d?|=vbF??BGNV6`O4dxq2kc84^u%gI>~Wm;I20X}OvV(R``m-E!z{O2;YX`B3O5mk84zRy{Dl!L6! z8BDZwpb*w~5RbB-2X`Z)Z}_R3=s4LjB7<=}d?1yw)b|ngnzSR~^(+e3BjY#>8aXyI zVv!$e)m2imO5Vs#SoDfMP`NuL%0ud>(S#!W)}W+30TqU%c=*oC8IEG-Qy^PTL)y}? z%!&yX4awG->2M%5SU6`yiKH)2sek>3jrHvu2|i~X%nQtAbCmfZsTP*#7o7XxFOs(R zWtiw|o;6S;XC`KLzQHLFIpct`ts4=3L_Z2O8;;i1oh2Wb9@d%&yK4m`zzc!kQ4k;J zrlClAntABWE$dR)Mv}+C%y6smb@dAzL^E1gGTamD)C)Yvu1Xmj4%A2_NZTj}UTP5V zJ&Jegu&sIA>8i+G}TbGL%<(r+(db~989z>h}a8+kbW07L%)YArXN76L7fj*o&Y zIl+%dzAUIEFuF=v8Wmz6E;lBSP7JFyjXc5Xceh-HTp~iMoRCBc-*@6E0o55SI!Q+6 z(gO0&oH%C=Nm2SfpF7&_Vzmf&3G-yG8O>veR}S?eo6f5kaz<%NWUfSL3qq%P;)y5q zg;fI9=x&~|3;e|L89L7zBrs><+gm{O(5f4HYSP?%dSikWAEL_7Jm5N!Coq1TL>n65 zEw`>tz-@v1F57dRz|Ag5dXhGSnyQ9wd|cwr@oplWq)x$(Hwjb?&*yF(3C$OA$uNra zPHIf7bNz%B3OqXslq2(jGKXSc^)|dWl!8;2N+V)(+VBl~qR zN+pF8a56yM*Ip9sqe)0ys=o4QV>nwCEk(n_c(L#bUH*6m>o&zV4exuaisj8>)H<`Mh6BX~O=QBaZQL;X(od)~ndc^Y=iHACguTBz%i;;iu*NZbPr9 z)rSN$H9u?wTiY@@;R-bqxSHXPy`u6ieS&SxSO66Ys22>ZY75C#a!n0Zn6o{bo-H6> zmKbj?<;w2Q^P;=-B{|zFOKs0XW_B8#pc9tyQ$z2Il^KW`6Yj^)2!}tq!ysWjFQ@!j zhN|HR>jn}r`?W0Mr{6wVZo^ylNlv<;# zmblxNAFIfg3x|u?BB?~e&ub$0FxR&eDeA-cjOdo%D&gG%qe+H z%T=ckZT~FXw#M|%uTmcvmUBK(h}70@sD5WUYu}x}s;4ZEh0OrmZ(56L>+k>r<|HwH zZqIJvZq9D~Lho*@-zT?rV@d2_{W`~mM#ju>9f16M?RhtH$UexMGhJx(KhASuIQThl zw0ELw8y($M^BOq-eNR>T?+oo7Y}#!|3f;upBzrGGm}P9WG=5aS|IW_d`JUZ&9-wb> z``OuD)jY_~eq%1%b}*X<2OYP6va>n#auE7Yx9lBx*+$2>Mg0Ku|At%kj(hAjRExL& zGXA|=_KrI2w!?!Kvis5X@7(fxpMTp9#x1dj{%mKn>yLKJA3Od(qviLW|28_tEvN&~ zx83riF^4vUnE?1>&~N(x_vZF)L*_*LFJp_Ye_>~DYjL+7b>jW)Y`Om*?fgx&^d0%L zoy~UI-$l#bX4>vmoJ7~>|FpSwIzrn+s()u_Z?kE)AzPAvFtpWr`a3&&n=!lXlpWpQ z&cBP6-R@#JP<5si+y z#2>&627PZ?V>fy-{l4hvBFEpM?=3m(MmJ>G2Ys9Oecye*Yx_1jW)HeEZliBs;?eJm zxx^p9#_w17O+>VwM(@JfUl2EU%Fhcu8Xt3^KLG!Cfxa96Jlozk{#CA{@iB7!H+)?5 TSqnfH8|VNrXkxODVLtsAG-@ti literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/CHM/MLBS_4.tif b/tests/data/idtrees/task1/RemoteSensing/CHM/MLBS_4.tif new file mode 100644 index 0000000000000000000000000000000000000000..22369e56f0c774ee829f4da1799f626069a7259d GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5g$bSXFj#T!KW{XsMsFoJ!?&cMI|3>OfD z(o8HH+r@w?7&$h!vonY>umOXSk)cnSfd@oq*0X?VP33U+UQ};1Cc10F+H7oB#j- literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/CHM/OSBS_11.tif b/tests/data/idtrees/task1/RemoteSensing/CHM/OSBS_11.tif new file mode 100644 index 0000000000000000000000000000000000000000..21dc8b0cd235a8757d2f59e361244df613d588c5 GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5hVXpnFOiZ^T*^aRmhzzFskI|Bm?FkC8NgB2`H6!aBBLwpsg^7B#^ w49)#&Afh!4y2g6OmSA9NWN2byY-VC)nyPJV!O*~<5MaXakIkV`fI~n402m!6c>n+a literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/CHM/TALL_1.tif b/tests/data/idtrees/task1/RemoteSensing/CHM/TALL_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..8e3f03ef043bd1c20632c313175fec9a7dd3903f GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5jjFv&Oq#T$Z7dV^>%US`7%m_P zrI}bZwu=E(Fmh~cXJ-&&U;_ptBSW7u0}qJKsAmDwn#$qs!3q{83i=A6A-)P#`FW`d whGu>>5YZY2U1L3COE54sGBhzUHZw6YP1QEGU}#`a2ryyz$L7!|z#$+20L)G${r~^~ literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/HSI/MLBS_4.tif b/tests/data/idtrees/task1/RemoteSensing/HSI/MLBS_4.tif new file mode 100644 index 0000000000000000000000000000000000000000..5a2dd440811ba7ac307f15afb4f44a69ea58c167 GIT binary patch literal 5536 zcmeH}ze>bF5XQgV4Y^Z;M9^p;LcOa^iQHaX7JqbWlBH@I5j1ZG=X1*G(wO7 literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/HSI/OSBS_11.tif b/tests/data/idtrees/task1/RemoteSensing/HSI/OSBS_11.tif new file mode 100644 index 0000000000000000000000000000000000000000..39f61a01708e6691272065c73b5041e724ca2687 GIT binary patch literal 5536 zcmebD)MDUZU|-l@0wvA?#hIbvAa!g=Y(YjAuwDr+1_n{6 zI7p8elnpXd9IB>+n}Iw<;$&cGYT;o3 z3Nds6+3VYx85Dr*T|oB6b|wZnAo~}P4FUqAa5MylbqGMx`6xLW0>d)|n1J=qC>#xe z;Tr;weEY#3mByc&A2dif0#!6@7xVj{C^;Ge!!QJfZyh@7v(XR$g#hy?91VeC9RdK{NJDJ^ literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/HSI/TALL_1.tif b/tests/data/idtrees/task1/RemoteSensing/HSI/TALL_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..e03b7bf76691f2ef57686cda8f28bdf4bbd8828e GIT binary patch literal 5536 zcmeH}ze>bF5XQgV4Y^Z;oQ2WikcF$X5kbUGu98EvxKn%r!ODN794IP8M8r4Iw7!9j z&th%mH=8YD;{zTVP`a2Ll-~~Fb;;w@-i)LsV{lsi*Xm@l9#vig2_Vd z@+`0z<5E*bEzewvIgfKdzg0V?zF_XV118i`_nHIq_XQZj)eLa=P=FykQNL9Vp8A8j zt}Irlf2d{k)RaKd1e*U`cO{S}0n778O$nq+py}K1oxlG7Zhp4?Av12rpL^oNGGXHC z02${svFL49S0yt{x2`%Uk#oE=cp0D&e|nPzzdAVHo%rodzwVC?_x<(x*@@rSI+!-m kX?%w|C6FqC&@)Zv=&l43B#`bIs$ZrAgn(010!b721sI}2@&Et; literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/LAS/MLBS_4.las b/tests/data/idtrees/task1/RemoteSensing/LAS/MLBS_4.las new file mode 100644 index 0000000000000000000000000000000000000000..f185afc1152e5034922ea54e291de5b807577937 GIT binary patch literal 1188 zcmah{J#5oJ7`+rK3PPxV;y;pg8^~0)n>4W)sMWfTQ5{>3BLP-qInE_MwhkS*OH?3DT28XRyYGGP-MjB@S}IH>K<7vt z=*+ceaKnullbLh9>uwFTV{|1aJ|Fr9QU?L(A*;k$KxTld?%r4Uz~Xp58YlF zr<~T9CWmzl9L^vw_QL=?Tj~epn+!?EVc3Y6u*zU}&aaS@MIocimUGlC^-I2^^zSbM zuv=xkZ@uS#Du$_Fo+#L`Xlcx~FhC5S$ukNH{R%HgkX2+|*$1AKtfFQ^)iS3Xj)QE$ z$jPRzPTEj03t5sF8_i_-(aZ?PajYO_M}%}n5CopAq@tD6P1U+;L)j=OkcAv9mG(*- zrmD$gCj}!EEcjj>vK$p{8(qj*k>jasF|S%SoJ2LkZ&LY8Dm^NQLYf!F6q%$`nJ#pZ z=Y)M+wNy0}BMe!-!w)srO_XbSqOF%FELG3j(86vI%_AHkM?=_i;}F9VYe=S~$(C%| zu;Ipje4~P#uN-rj_b{`=CzS~zM_&>C-0DDsbK9S^M7jdx@qNh z(mCYU>M>-C#$K-kod1#phhLR}b7m0SO`Pi8S{nw{x&FS52T5uEYZAPFQ3gW;gWw!E v-MjVpFSbgs4=>B$BH?{X4D@cj8wTvjfhJG>o&=8w_L^XCDMmC)>n-dzjjB5A literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/LAS/OSBS_11.las b/tests/data/idtrees/task1/RemoteSensing/LAS/OSBS_11.las new file mode 100644 index 0000000000000000000000000000000000000000..19f0159cac33406b5e8e435f373e39b02c90338d GIT binary patch literal 441 zcmeZq40gNBfC(6xF~v|tigFT*6@nBB5|gtN(^FC9Ks;B*r|hp83>g_1fQW%viGc-3 zfiODwv-0C5-=qM0bQ-KKOTrOEJFt0zX%PGDnKM4G8YCQ}QmpSk1kxT4t4g*DdOCXX zFlB>iNtQ4^-9sJ@ZvuD7wa)f%0O^4MAE0OB1B&vqQj<$E^Ya+iw+k>NRVo05OY-w` ziWTzH6p9iHGE;y)E6!JN&rR|GIUeMEAm#y)ne|g5#2CIziDbw<$iTqO;K}-RvpvUx z2hMVR=Ddy!Rt(+@n%dD~44hLV8SD>0Rcx^5U|s47QDMN~#9+x8C&r)xRM7@h!3i>B oqdmv(7(a*#a|T}qz4HlT40b>j-ar*xP!$@pgCQy`7`zxP0smKF{Qv*} literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/LAS/TALL_1.las b/tests/data/idtrees/task1/RemoteSensing/LAS/TALL_1.las new file mode 100644 index 0000000000000000000000000000000000000000..723efb46dd5baf79eada9251a3357b1ba2d2b5b9 GIT binary patch literal 441 zcmeZq40gNBfC(6xF~v|tigFT*6@nBB5|gtN(^FC9Ks;B*r|hp83>g_1fQW%viGc-3 zfiOC#7Fp;1u&Tu#od&DRl5hmk4&~lp8pO8ay1w`}lZ@k%0$Z-@K)Mh}d7Sii^mtfx z1w=or0vcEt?hv#?uJv_dxC2NJ1o!|w8y`@VpOu%qhaf6S85$Y# b7=S8X09AAXRR};;++#ihQ8Ari0Ye)AOPpe` literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/RGB/MLBS_4.tif b/tests/data/idtrees/task1/RemoteSensing/RGB/MLBS_4.tif new file mode 100644 index 0000000000000000000000000000000000000000..d6b3d6dcb2777f9a61a227d55c7a2e49b0bb8488 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=1-cX* zfhrnA`u#yP7%+l;%+A2T0t_z@gwjkb8{5TzDi}F7wzD&cF|YxHmyw}QnSlpHXV$ZT zX-(yD_h1DJ69s*R&=6mRs{FiE1w(Vc8i;5OgRZfju_YLo8X20H7@L_GnWkzRTQD>* dC@vDF~I-; literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task1/RemoteSensing/RGB/OSBS_11.tif b/tests/data/idtrees/task1/RemoteSensing/RGB/OSBS_11.tif new file mode 100644 index 0000000000000000000000000000000000000000..7525657c0f5841f19c30bd3cacdaf32d1fbbeec8 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=A2dif z0#!6@7xV-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=dzfS# zfhrn;PI`lAFkl4xn4N)v1sGl+2&I`=HnxiaRWNdFY-eW>V_*XYFC#;rG6N5Y&ZuVr z)0)cR?!gKcCJOorp&`BsRrz_T3WjEWH4xDn23=!4V@ohFH8M0YF*Y+XGELPswqR&r dPzbP45oEA$bbM&gA;`qe*LFCGg<~cY69A=&G9v&0 literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_MLBS.dbf b/tests/data/idtrees/task2/ITC/test_MLBS.dbf new file mode 100644 index 0000000000000000000000000000000000000000..e7d06021b062e5414217ae6d8e23ded25ed996f5 GIT binary patch literal 302 zcmZRsjONCT3XATuweEXC6WB;gDq0zd>3D9Fh#fk;E-MUmt{OkM?FAE#hf h0|NsS3k9kGka_WjdL@}@$i~5Ku|yUnpN-WPDF7j%CnNv> literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_MLBS.shp b/tests/data/idtrees/task2/ITC/test_MLBS.shp new file mode 100644 index 0000000000000000000000000000000000000000..45d90f102c06ad2c5623a150abeba889b204e8b6 GIT binary patch literal 372 zcmZQzQ0HR64tBj@W?*0i%4KYPH#4JC!Euk@{|cvmf5#I^FJ`p?`7(zBTit+s?AlR8 z8G$?pkXZ^dRcvdqnDs*^jT>YZNFhia2%xKl@nLFV?m*|m+zC?;;{%Ok0%EW`V180r kyf(oFXs?1o=jS{mKOwsVT`i0c^FO*?7$0UfOg)Sb0Lun%cK`qY literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_MLBS.shx b/tests/data/idtrees/task2/ITC/test_MLBS.shx new file mode 100644 index 0000000000000000000000000000000000000000..f9e0f0ae98476fa38e27e6e060e02c4b541e2f18 GIT binary patch literal 116 zcmZQzQ0HR64y;}jONClFZATuweEXC6WB;gDq0zd>3D9Fh#fk;E-MUdn`OkM^5V5eYL g10XO_pb7w)7jI~)SCW~AFb{5tDME}A7B)+y0OS5AAOHXW literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_OSBS.shp b/tests/data/idtrees/task2/ITC/test_OSBS.shp new file mode 100644 index 0000000000000000000000000000000000000000..fb4ce2f424dfa0b771e3d974180fd5318494ccac GIT binary patch literal 372 zcmZQzQ0HR64tBj@W?*0i%Ebuh8fQ02I2y0;czi+7(@|MNu6R3;?|4OS(KR3+yLJ>& zMj+1t*{rv_ciy@P)VoeWd4CU*Ss;ZVbszw<7p4}*huMp+7sf|d590%kWCCKaJ6^rb kn78PwBSG*m_cf2CE=o*lZT|0`X M5s>Eq#AP5F0Akz?{r~^~ literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_TALL.dbf b/tests/data/idtrees/task2/ITC/test_TALL.dbf new file mode 100644 index 0000000000000000000000000000000000000000..c8e498acb9a28c0a45308434d50535e31738db13 GIT binary patch literal 302 zcmZRsjONCT3XATuweEXC6WB;gDq0zd>3D9Fh#fk;E-MUmt{OkRZ$M;{+o h0|P??V+E=Jka_V&dL@}@$i~5KF+mn3pN-WPDF7FdClUYv literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_TALL.shp b/tests/data/idtrees/task2/ITC/test_TALL.shp new file mode 100644 index 0000000000000000000000000000000000000000..573fc771e2bc6d29ba8ee6dcf020add991539d6d GIT binary patch literal 372 zcmZQzQ0HR64tBj@W?*0i%Dr>@mD0;37fd|lz|>)e2R?AlR8 z8G$?pkXbSf1>Gxwdhb+j(09gS7DypT9SESSh4EqbqPqjehnWpi590%kWCCKaJJ9`f kJYVqhJfOQWSUq>R0NsV`4s^9JK1?mTUKk%9UTEP503IZ1zyJUM literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/ITC/test_TALL.shx b/tests/data/idtrees/task2/ITC/test_TALL.shx new file mode 100644 index 0000000000000000000000000000000000000000..5dccd1df858ddb9d559dd5648db0e8345a498618 GIT binary patch literal 116 zcmZQzQ0HR64y;}AV9#zOL}~b#6dDcI_yl MMnIke5SM{y0NE!GrvLx| literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/CHM/MLBS_1.tif b/tests/data/idtrees/task2/RemoteSensing/CHM/MLBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..5c9f2b201b8fffdbd208beeca23cb88eb688c6fb GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5h-bSgLk#T&f){XsMsFoJ!?&cMI|3>OfD z(o8HH+r@w?7&$h!vonY>umOXSk)cnSfd@oq*0X?VP33U+UQ};1Cc10OxcjDgXcg literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/CHM/OSBS_15.tif b/tests/data/idtrees/task2/RemoteSensing/CHM/OSBS_15.tif new file mode 100644 index 0000000000000000000000000000000000000000..6af65d61039f4113671a2ed5cd81bc36dc7ee587 GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5h-G)Xvu#BT_Cf@u(&5$rQ|1_l-g8|V$N z7!%9Jb}^7V$HsPc1~CRUU@$T=^eHp&fauJ67BH=;9PS>hU}2)5uMisIt5B7nm#ScB u?pFg5tzpnL)-$#Q15+bI6BA=I6C=}9ZDR|D1_p%y6NZ0m4vhjF0s;V=HzlM1 literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/CHM/TALL_2.tif b/tests/data/idtrees/task2/RemoteSensing/CHM/TALL_2.tif new file mode 100644 index 0000000000000000000000000000000000000000..5f973638c75652275d874b518de469c232076b7c GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5jjFv&QA#63=WgJ}?(5$rQ|1_l-g8|V$N z7!%9Jb}^7V$HsPc1~CRUU@$T=^eHp&far{R7BH=;9PS>hU}2)5uMisIt5B7nm#ScB u=2rs|tzpnL)-$#Q15+bI6BA=I6C=}9ZDR|D1_p%y6NZ0m4vhjF0s;Viq$P9! literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/HSI/MLBS_1.tif b/tests/data/idtrees/task2/RemoteSensing/HSI/MLBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..2976621ccdb5a130dfd78ba0ad5e22375c2434cf GIT binary patch literal 5536 zcmeH}ze>bF5XQgV4Y^Z;oQ2UsWZ^1pL=d!-tK<+BJjEvvwDDgl2Z{<&3-wJjtq(LVje}84j1x1<(VGgQ2p#OiNqpOWycm+{L)$D%wkzyAMjK6&y3X54PS?uie}go&#I zWSrN;qPJOHmCP{Ry6T`r&hgIRrH?}V=}i{=QvYac{P$9{EvcicqB j@g3@vK&k{n&orH*yAnu{K)PqBewh*w0!~c{Bu(HKD3U`S literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/HSI/OSBS_15.tif b/tests/data/idtrees/task2/RemoteSensing/HSI/OSBS_15.tif new file mode 100644 index 0000000000000000000000000000000000000000..592c7f7fa1c031f0493a0eaf0a1cf2aa573d9aa7 GIT binary patch literal 5536 zcmeH}y-LJD6ot>sgzT;c*)5D)hzx9{jR=BvVkIfGaCh+u1a15)WkFblh=uqjn%0M~ z_A#ujJa>{JHv0g>ol9~*&fJ@jLx!26*g_XTA21GvWOj;_T%ZDW7&C%J>*zfQ9Z9knH`}^ywbHB59GON{@ hyg-LK5U~TYel*m9cpZp$57jSI2gCuVp$sgzTz8*1~A9$iPOj;ERR6i&)qyx2u-tz%)PZ;%sMhVz!T&seHNV*Ip;R1y4MG++;l$Mf zl8Sk}3YtyIBA5AgS+tNNpoBGi9Cy?N@*gQqH@Ac){m@n|mvp?DC%f-}2Ry4`JkA@gRw?|pCHyqOuPGMxau zmN?Xlbt7TRjTnm#r}OETKhOCO8R_`_&5KRqlbMxw(qmmZ^Xc9TJJ%9Gb>clH`B$-)W zouHbI&5*-7Mu_K;7yCE>Yb!^<+)akSF~+Tk!F48dm;4$zS%ev7ajrxPCm$Wb2p#=9 z0PM{&G4wI{zbuBSUzn`euxe?{RU9COFO(StVZX);5@Z!wR}P@3B&({~P_@iyhvOhy zF-o$jt5Y^q%u0~}GhsybOU6r&Kbd`~^JxPD2wPEEA+@}#BeWgA+d8$`<}jF6+D&~sxP!dcdkOi7b1 z*|cHHjs5sq4LP{(;LvaQLB9@kNjwz9q1$p6QRvrvmk3T5dyDLnM4sD1PTlqB{-ndA zD30%LaaVBNZIN=38^wOmfNZ`UNSn|nD!^gfY=^b4jokBPmApE6M=VPfYX>a zRoa0|$Zs@b$X1R0UI~ED(jf3^;O^ik_>>qP*nX1&%+RIr4^NIskDjMNlW+^eqaXwL zf$jGxu(8(R)}N+g_s A^8f$< literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/LAS/OSBS_15.las b/tests/data/idtrees/task2/RemoteSensing/LAS/OSBS_15.las new file mode 100644 index 0000000000000000000000000000000000000000..9edbe69c2bdd1932581ece11a84f3c7eccdb950b GIT binary patch literal 679 zcmeZq40dC{0vMUG2%-oT1Su3GCTAz6r=m!KIj)RP* zF+ms^REw;0e^}LGk4}TtWJx$eXb%Vt78PeE z<)kWP=B1=oC};rfDap)9)kL_Ex~vcO5cAM!Ok47cGSf5j5_6y~OG-`4FG^JaIxaIW z9i;kHCKJOqiDm{rGmmnXQ$Ir49-O=aDvTeIdcsxKMyDm E0CwVoE&u=k literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/LAS/TALL_2.las b/tests/data/idtrees/task2/RemoteSensing/LAS/TALL_2.las new file mode 100644 index 0000000000000000000000000000000000000000..f26b1000a55b0daf2ad7bd60196b650d78f8d204 GIT binary patch literal 441 zcmeZq40gNBfC(6xF~v|tigFT*6@nBB5|gtN(^FC9Ks;B*r|hp83>g_1fQW%viGc;G zh6zZ6Fbq_StaE=@)nboMgXOa%96_{0xi^>wu^(26th>b|zc|c@Fee~N%hJHQ=hBOtBP!N*-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=GddL< zfhroj`~5*Q7%+l;%+A2T0t_z@gwjkb8{5TzDi}F7wzD&cF|YxHmyw}QnSlpHXV$ZT zX-(yD_h1DJ69s*R&=6mRs{FiE1w(Vc8i;5OgRZfju_YLo8X20H7@L_GnWkzRTQD>* dC`;sGEV>i literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/RGB/OSBS_15.tif b/tests/data/idtrees/task2/RemoteSensing/RGB/OSBS_15.tif new file mode 100644 index 0000000000000000000000000000000000000000..1635be5450ad55c5845fe3e025d86a9bf95ee497 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=GnynE zK`L$tdV*;Xn-T0|b_NC(2pi}m&Xzo`75v^g+HP$n>1OrneLlYBYGZQ1zRBdAmh6V7W0B|}n$p8QV literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/task2/RemoteSensing/RGB/TALL_2.tif b/tests/data/idtrees/task2/RemoteSensing/RGB/TALL_2.tif new file mode 100644 index 0000000000000000000000000000000000000000..9a07eaff9ace18fdc403c910ac3f8c1b69128790 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=dzfS# zK`J~>dV^^Yn-T0|b_NC(2pi}m&Xy#V~5v^g+HP$n>1OrneLlYBYGZQ1zRBdAmh6V7W08nQ!nE(I) literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/ITC/train_MLBS.dbf b/tests/data/idtrees/train/ITC/train_MLBS.dbf new file mode 100644 index 0000000000000000000000000000000000000000..d88f8c20c0d7a791577e356188d528440c787b9e GIT binary patch literal 2078 zcmb`Iy$S*`5QGn`L~O6H@)45D?j~3I(+Dbp*w_izTw^Kt_8QFu!FEz)lP$gso5^PK zoV9J!G`HsZp8v-<)Z8(zrgi&WjQ8O&?6!Hc$)e$a6t*`6t`qi4u}TgkPXHr=YII1EqN3LYwl@r#gORiXW9W4q`=vR*-{NF#e5uB{OYOWk#*d(?0wk?Yt)=DoG znQ2{El*nA01Ex1VEOk-x8_6M`F8xkfIf>pB$+5NnfuDEvJGE@c(eLEUfy*`P!t}zo zOubx}mHm?=@UCj!eVH3w89Fmm<`%ciZGJ9Xm2ja}*TNKHB*&n!sRKR=*Y`aX)O~WU zhjn3GTMN@0AC?U5$+0*i#@`@w`_(z%@R%%^J->^ZeAjB9IQdgH^Ap#wRRZzu$=qPl*w>ol!ON_>Z-o81c_Zw4SQo~%wJ^Q$Vad>u z99v%$&v`9#n?prWnj`(syB^ksacwP3Z+uuXbR|bj z;Q9`o>%XzQI!V@pdvE9XxbM0?&V8{ijB9IQdgH^AA&wk*vuZthpC(Q`a$Iv9D?e4Q z&%Nbg_fWeY)`f9xElh8GSTe+uW90tS6O z*Ve-H#)lKRpAzux-hP-h3SnCOI;6goX_6u)BA;ci8EgDP^y8J(bX8%c= literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/ITC/train_OSBS.dbf b/tests/data/idtrees/train/ITC/train_OSBS.dbf new file mode 100644 index 0000000000000000000000000000000000000000..ad8161f5c9d8576576b237671c833c39718677cd GIT binary patch literal 2258 zcmcJQF$)4Q422JzL|jFj{0T{#yWXwp;-bQBZo)0MI12uIQ)3Rn^_d~G9X?9>-s|kS zSgot7x>Z-}zcmii*{Q7N)7BT`eRvGVL)q+0yOj1-%k8^3Q9k#(o`_Wa2-J<-I+=w) zOve~mgPhRhI>>@1H$i4Fndw9322JK5*I+VrK}M^NCCDB4&YXe_enIMd&M7lkbt>+` zlXWnkz2G=!rcnj#xxDEHvW)En-?CjG}{0G literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/ITC/train_OSBS.shp b/tests/data/idtrees/train/ITC/train_OSBS.shp new file mode 100644 index 0000000000000000000000000000000000000000..9ee65c3e2a2286444dba3c785d3a66f27dd60b66 GIT binary patch literal 3364 zcmZ{mO>9hI6oxOY86Bm5ri@KVN{m>tl1ySEqBf#pK~gLv^%IHOSQIe}O$5n^m5PXj z1rw>?>XgKyc2q38o{hh$pG}Dk-t&z|J||<6o7?B!_nhLS}0EcCM&DOZPjr6;}+^dni zx}KP?zVbw;%{cbqwJ=&A3zHi?tot_dFc;0X{7Y*sGj;RQXVo=M$;WhWb>A1~qnXxX z(fU|8&K=j1hZH`f8Ca#dwZZI&tq*$Juippf!)sx*J{Bf7dRQLP_^=+?9K@x!s+(2w z?0wPoiusD?;tkW+qK9>E!^ex6 zeBV#aSX}ltahz# z<|Eb;i`K`&sugCA9@cptK6Fm5XfM{OG%g>}-rW7ntq-)?AHqGag~^59Gx56KuH$b% z4$6o2xIGV4*Vg_CaZhbtkWn3c1n4IxoP7^>SCEA3v-gw)Y$mD`t!Dhu6YreJrf!54q99^3Z_~^)%bB zx_bB0kLAPI`lB58d_S6LEf%ehh2z|DEqPdo4_niRl*_)ER(GvF7H7p8*M%`R7q5lU z`dFCU=wW$Sgb$sQ?W(I;KBpi18$Mn=aX!2jmXDyd=Z|Aya-)xUv95*p#r`KarMmL^ z(bj{l>9W{n>3w)DjMm4({1)TE2VDeAqp- PJ{DGrlEwO5F!8?upg}bc literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/ITC/train_OSBS.shx b/tests/data/idtrees/train/ITC/train_OSBS.shx new file mode 100644 index 0000000000000000000000000000000000000000..c514c85441f01e1a6802548a6d46f19b150b23b2 GIT binary patch literal 292 zcmZQzQ0HR64ko=|W?*0i$`#sjU4OGe!V$=9*ud}UDBt=zaMcaaTE`rkA cp!6>Y&1wgwo1pYD2+alx69)zcHa`#z0IeY(v;Y7A literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/CHM/MLBS_1.tif b/tests/data/idtrees/train/RemoteSensing/CHM/MLBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..bfa936c9e2fc311818bbe31d7b7e93ed09cc59b2 GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5ig>r!w8iZ^KY_=9LLUS`7%m_P zrI}bZwu=E(Fmh~cXJ-&&U;_ptBSW7u0}qJKtY-n!n#$qs!3q{83i=A6A-)P#`FW`d whUR`X5YZY2U1L3COE54sGBhzUHZw6YP1QEGU}#`a2ryyz$L7!|z#$+201-hZZU6uP literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/CHM/OSBS_1.tif b/tests/data/idtrees/train/RemoteSensing/CHM/OSBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..a072fc25e72a8b2838cc234f3aa554c7506f77af GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5jLSs~#F6mR&z?+K#8fD!C7b_NC(V7P!F zlxAYt*e(WC!N{?(ot;69fejdpj0}Cs3_Kt@vz`S^YbuAk2P;^ZDCjGMhWIK}<>#d; w7@GUlKtyX8bdB|lEy2Ll$k4>Z*v!PpG*#Q!f}w#yA;5&;ADcs?0Ed7808FAL)Bpeg literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/CHM/OSBS_39.tif b/tests/data/idtrees/train/RemoteSensing/CHM/OSBS_39.tif new file mode 100644 index 0000000000000000000000000000000000000000..191d9a16aa8611902b2515fcacbed94d6cfe4ce0 GIT binary patch literal 423 zcmebD)MDUZU|-pD0L7W1Y*rwf4ax@T5oBZm>zxVYh$4xL zLD?WP#i45WfNU8gaa$zy!B93cPz_%*4+Dt41;kA)JPd3=`V$bZZ)avu0Mbf8yEe8n zF~|YgNkDev5+(*oAiDv`Mt~3Y5H^fP$Y5i6tdVd8iZ|@y_XN>kzzFskI|Bm?FkC8NgB2`H6!aBBLwpsg^7B#^ w49)#&Afh!4y2g6OmSA9NWN2byY-VC)nyPJV!O*~<5MaXakIkV`fI~n40N3dz4gdfE literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/HSI/MLBS_1.tif b/tests/data/idtrees/train/RemoteSensing/HSI/MLBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..0fad804da622356a848b4b15c6b9d17220de0ca9 GIT binary patch literal 5536 zcmeH}ze>bF5XQgV4Y^Z;oQ2UsWZ^2c5JAxHu#ziO@D!iG!p48494IP84iVo()A|ti zEY? zka1oUi{55+RWieL>#BniImbJLw>}E-7x!84YyGqRk>B0+oBs6Vz~5b6p8Ktx!*LTG j$9Jex0;v)RJ=1iK?n)p*0_mQi`ejN$2skw*kTii`SV2Q# literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/HSI/OSBS_1.tif b/tests/data/idtrees/train/RemoteSensing/HSI/OSBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..6b78adbbdafaeb3cdccb4f9ecfafe39512ac7837 GIT binary patch literal 5536 zcmebD)MDUZU|-l@0wvA?#hIbvAa!g=Y(YjAuwDr+1_n{6 zI7p8elnpXd9IB>+n}Iw<;$&cGYT;o3 z3Nds6+3VYx85Dr*T|oB6b|wZnAo~}P4FUqAa5MylbqGMx`6xLW0>d)|n1J=qC>#xe z;Tr;weEY#3mByc&@2rq;1gdEG!0!p7!GICm;$mlDU;(z*KoCkZv21J?1FB%;*x1g_ zAjZH3Y~C?4^eHp&fauJ67BH=;9PS>hU}2)5uMisIt5B7nm#ScB?pFg5t%2GxN{xoV ma18-)o`K|?QF1f{hG7T{-#T>EXQLqi3IXO(I2r=OIs^b5lS9Y= literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/HSI/OSBS_39.tif b/tests/data/idtrees/train/RemoteSensing/HSI/OSBS_39.tif new file mode 100644 index 0000000000000000000000000000000000000000..03ea53f1be8b4f3d9b21be1d0e49502e2c53afb3 GIT binary patch literal 5536 zcmeH}ze>bF5XQgV4Y@lFdKQb~7<5gQ+1nH{q8F_}roj|@9Sv4u8(E}#q)mFA@yT2tTf#y8_8#9dz6&}%C3 zwSy&~7vioa586GmA97xnf%Qi1G4(ZbzYH*?c6IM5u=13FA>4L=$L9Z;V(p#G(Ht1Bi1q9#!P=dufdI0B*tJyShBLJ9|g7 lIywvQ5GMp;CE$Ce?i|^LK!gP1JwxTogn%Pp#DqZ91Qyy&LjnK* literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/LAS/MLBS_1.las b/tests/data/idtrees/train/RemoteSensing/LAS/MLBS_1.las new file mode 100644 index 0000000000000000000000000000000000000000..4960fe63c7416f514d5f04ac20a0268174cede15 GIT binary patch literal 1188 zcmah{y>HV%6u*=P5bD5SBnC>{tEfaVmF*@?%tB1-CZ=_4Iga=+#d4iX;^NwsZA#k( z(FF;y(E&Cjgj6Or1|%f@06>TZiJbv4MNCwHxJwiuO&2KdUizX^os4=uT$aC<9%|x@fw3ak^~6ilNX~10T^`wnP_D;8r-U>L7sNq+9NM0>j6%2S+8A&)*GsYs5;?YqteWkR^YMfQ zL74h?#9hTT+rxB`9mTF+hfJnbNQTg-$`8W0(JE^_8-C}@GX8Z^2P}zYqZ2IssV%9x zQS5|^$gMYG$W*kwehK(xrag_1fQW%viGc-3 zftes#B=BeD$4kCR0ru!LSV@+IBZzij^90i%_B!{6RVphb9B-cc9xMZ->$CPudB*SQ zXl!iU45A|}x11=O=j|{>f+dWv*3AK=2LgP6o{bME%FjwoF3HT#V_4rVz>rj_02D6C z&(A4V$WK!!N-W4s0s5>sU%@>$$phqKkeh*+2SjGpFIZ{Ikd^AquyH*D11m!i69>a{ z)+Y6b_1&50h)2_Ixt`u v1X(%Kp0j%9Hb#TZIL5GtVb-dJwhU@O6*?Ok7&sV&7&(}bRGeox!LSejneSuZ literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/LAS/OSBS_39.las b/tests/data/idtrees/train/RemoteSensing/LAS/OSBS_39.las new file mode 100644 index 0000000000000000000000000000000000000000..760476898d7c8bc28d00bc23a428a32360d375be GIT binary patch literal 441 zcmeZq40gNBfC(6xF~v|tigFT*6@nBB5|gtN(^FC9Ks;B*r|hp83>g_1fQW%viGc-3 zftes#B=BeD$4kCR0ru!LSV@+IBZzij^90i%_P+U2>!+-da6EWw;n8j&J{H)aElFa-(hV|_N3`vy=K;e@7 z{G4Ki{4|B4#DdHepwEi)72I=^JU}i6xfzIgKxAgUF{>lP9d0j%ts6mnPNsJZ&8&xt z&&({BV02{I!LW^CjSi48m&c2tW*q|q6N3;~#SZO@5ETIofef+re;gRz0#z_?00s<0 s5LiX=leh(7GyXCBV0h^M-+{pnsG?^*12EhmDsHSufv9L=sAOmd0H^3^_y7O^ literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/RGB/MLBS_1.tif b/tests/data/idtrees/train/RemoteSensing/RGB/MLBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..064644fc98853d115778ebcd39435f7a1e9cb290 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=&vhv{ z0#!6<_xOWoFkl4xn4N)v1sGl+2&I`=HnxiaRWNdFY-eW>V_*XYFC#;rG6N5Y&a7tv z)0)cR?!gKcCJOorp&`BsRrz_T3Wnx>H4xDn23=!4V@ohFH8M0YF*Y+XGELPswqR&r dPzbP45oEA$bbM&gA;`qe*LFCGg<~cY69Dh6GLrxR literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/RGB/OSBS_1.tif b/tests/data/idtrees/train/RemoteSensing/RGB/OSBS_1.tif new file mode 100644 index 0000000000000000000000000000000000000000..b499dc59e4cfa343cc2c7dc0c2d2634dcb3d8fa3 GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=@2rq; z1gdEG!0!p7!GIC$V|E4x7GQXRAe3fe+1M@yRKduxv7MbkjDZapyo?Ne$_zXpIy)X31p#MsQl$TU^k*n**f dK_S3GMUcV1(ea@{haeL>U)$j%7LJ)rOaL$CGWh@i literal 0 HcmV?d00001 diff --git a/tests/data/idtrees/train/RemoteSensing/RGB/OSBS_39.tif b/tests/data/idtrees/train/RemoteSensing/RGB/OSBS_39.tif new file mode 100644 index 0000000000000000000000000000000000000000..f4eb86a0b63b984f83bf2056cf29f96211f76c7b GIT binary patch literal 459 zcmebD)MDUZU|-mK1I3;JF*8)07070TvO#(T8Ck%3Hv&1L zP;rnRF(?~krZ`lM6p$^0q}LXS9Ry^9)Pg`TkPR~94G{A+^MKi(fb6Cg9tJib%>}e; zeLFLQ0+4M5WN&O|Vvqx}tAOmrB}@#GK=uqE8w3=<5M&-R!>pMgu+tt!LsZ}=J=RD# z0#!8Z;`ap6V896WF*^eT3oyJu5K1$#Y-|?;s$k^U*v`%%#=r&)UPgvKWd None: + shutil.copy(url, root) + + +class TestIDTReeS: + @pytest.fixture(params=zip(["train", "test", "test"], ["task1", "task1", "task2"])) + def dataset( + self, + monkeypatch: Generator[MonkeyPatch, None, None], + tmp_path: Path, + request: SubRequest, + ) -> IDTReeS: + pytest.importorskip("pandas") + pytest.importorskip("laspy") + monkeypatch.setattr( # type: ignore[attr-defined] + torchgeo.datasets.idtrees, "download_url", download_url + ) + data_dir = os.path.join("tests", "data", "idtrees") + metadata = { + "train": { + "url": os.path.join(data_dir, "IDTREES_competition_train_v2.zip"), + "md5": "5ddfa76240b4bb6b4a7861d1d31c299c", + "filename": "IDTREES_competition_train_v2.zip", + }, + "test": { + "url": os.path.join(data_dir, "IDTREES_competition_test_v2.zip"), + "md5": "b108931c84a70f2a38a8234290131c9b", + "filename": "IDTREES_competition_test_v2.zip", + }, + } + split, task = request.param + monkeypatch.setattr(IDTReeS, "metadata", metadata) # type: ignore[attr-defined] + root = str(tmp_path) + transforms = nn.Identity() # type: ignore[attr-defined] + return IDTReeS(root, split, task, transforms, download=True, checksum=True) + + @pytest.fixture(params=["pandas", "laspy", "open3d"]) + def mock_missing_module( + self, monkeypatch: Generator[MonkeyPatch, None, None], request: SubRequest + ) -> str: + import_orig = builtins.__import__ + package = str(request.param) + + def mocked_import(name: str, *args: Any, **kwargs: Any) -> Any: + if name == package: + raise ImportError() + return import_orig(name, *args, **kwargs) + + monkeypatch.setattr( # type: ignore[attr-defined] + builtins, "__import__", mocked_import + ) + return package + + def test_getitem(self, dataset: IDTReeS) -> None: + x = dataset[0] + assert isinstance(x, dict) + assert isinstance(x["image"], torch.Tensor) + assert isinstance(x["chm"], torch.Tensor) + assert isinstance(x["hsi"], torch.Tensor) + assert isinstance(x["las"], torch.Tensor) + assert x["image"].shape == (3, 200, 200) + assert x["chm"].shape == (1, 200, 200) + assert x["hsi"].shape == (369, 200, 200) + assert x["las"].ndim == 2 + assert x["las"].shape[0] == 3 + + if "label" in x: + assert isinstance(x["label"], torch.Tensor) + if "boxes" in x: + assert isinstance(x["boxes"], torch.Tensor) + if x["boxes"].ndim != 1: + assert x["boxes"].ndim == 2 + assert x["boxes"].shape[-1] == 4 + + def test_len(self, dataset: IDTReeS) -> None: + assert len(dataset) == 3 + + def test_already_downloaded(self, dataset: IDTReeS) -> None: + IDTReeS(root=dataset.root, download=True) + + def test_not_downloaded(self, tmp_path: Path) -> None: + err = "Dataset not found in `root` directory and `download=False`, " + "either specify a different `root` directory or use `download=True` " + "to automaticaly download the dataset." + with pytest.raises(RuntimeError, match=err): + IDTReeS(str(tmp_path)) + + def test_not_extracted(self, tmp_path: Path) -> None: + pathname = os.path.join("tests", "data", "idtrees", "*.zip") + root = str(tmp_path) + for zipfile in glob.iglob(pathname): + shutil.copy(zipfile, root) + IDTReeS(root) + + def test_mock_missing_module( + self, dataset: IDTReeS, mock_missing_module: str + ) -> None: + package = mock_missing_module + + if package in ["pandas", "laspy"]: + with pytest.raises( + ImportError, + match=f"{package} is not installed and is required to use this dataset", + ): + IDTReeS(dataset.root, download=True, checksum=True) + else: + with pytest.raises( + ImportError, + match=f"{package} is not installed and is required to use this dataset", + ): + dataset.plot_las(0) + + def test_plot(self, dataset: IDTReeS) -> None: + x = dataset[0].copy() + dataset.plot(x, suptitle="Test") + plt.close() + dataset.plot(x, show_titles=False) + plt.close() + + if "boxes" in x: + x["prediction_boxes"] = x["boxes"] + dataset.plot(x, show_titles=True) + plt.close() + if "label" in x: + x["prediction_label"] = x["label"] + dataset.plot(x, show_titles=False) + plt.close() + + @pytest.mark.skipif( + sys.platform in ["darwin", "win32"], + reason="segmentation fault on macOS and windows", + ) + def test_plot_las(self, dataset: IDTReeS) -> None: + pytest.importorskip("open3d") + vis = dataset.plot_las(index=0, colormap="BrBG") + vis.close() + vis = dataset.plot_las(index=0, colormap=None) + vis.close() + vis = dataset.plot_las(index=1, colormap=None) + vis.close() diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index ccd5cefc2..4e29a516e 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -37,6 +37,7 @@ from .geo import ( VisionDataset, ) from .gid15 import GID15 +from .idtrees import IDTReeS from .landcoverai import LandCoverAI, LandCoverAIDataModule from .landsat import ( Landsat, @@ -115,6 +116,7 @@ __all__ = ( "EuroSAT", "EuroSATDataModule", "GID15", + "IDTReeS", "LandCoverAI", "LandCoverAIDataModule", "LEVIRCDPlus", diff --git a/torchgeo/datasets/idtrees.py b/torchgeo/datasets/idtrees.py new file mode 100644 index 000000000..aff6ac82d --- /dev/null +++ b/torchgeo/datasets/idtrees.py @@ -0,0 +1,553 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""IDTReeS dataset.""" + +import glob +import os +from typing import Any, Callable, Dict, List, Optional, Tuple + +import fiona +import matplotlib.pyplot as plt +import numpy as np +import rasterio +import torch +from rasterio.enums import Resampling +from torch import Tensor +from torchvision.utils import draw_bounding_boxes + +from .geo import VisionDataset +from .utils import download_url, extract_archive + + +class IDTReeS(VisionDataset): + """IDTReeS dataset. + + The `IDTReeS `_ + dataset is a dataset for tree crown detection. + + Dataset features: + + * RGB Image, Canopy Height Model (CHM), Hyperspectral Image (HSI), LiDAR Point Cloud + * Remote sensing and field data generated by the + `National Ecological Observatory Network (NEON) `_ + * 0.1 - 1m resolution imagery + * Task 1 - object detection (tree crown delination) + * Task 2 - object classification (species classification) + * Train set contains 85 images + * Test set (task 1) contains 153 images + * Test set (task 2) contains 353 images and tree crown polygons + + Dataset format: + + * optical - three-channel RGB 200x200 geotiff + * canopy height model - one-channel 20x20 geotiff + * hyperspectral - 369-channel 20x20 geotiff + * point cloud - Nx3 LAS file (.las), some files contain RGB colors per point + * shapely files (.shp) containing polygons + * csv file containing species labels and other metadata for each polygon + + Dataset classes: + + 0. ACPE + 1. ACRU + 2. ACSA3 + 3. AMLA + 4. BETUL + 5. CAGL8 + 6. CATO6 + 7. FAGR + 8. GOLA + 9. LITU + 10. LYLU3 + 11. MAGNO + 12. NYBI + 13. NYSY + 14. OXYDE + 15. PEPA37 + 16. PIEL + 17. PIPA2 + 18. PINUS + 19. PITA + 20. PRSE2 + 21. QUAL + 22. QUCO2 + 23. QUGE2 + 24. QUHE2 + 25. QULA2 + 26. QULA3 + 27. QUMO4 + 28. QUNI + 29. QURU + 30. QUERC + 31. ROPS + 32. TSCA + + If you use this dataset in your research, please cite the following paper: + + * https://doi.org/10.7717/peerj.5843 + + .. versionadded:: 0.2 + """ + + classes = { + "ACPE": "Acer pensylvanicum L.", + "ACRU": "Acer rubrum L.", + "ACSA3": "Acer saccharum Marshall", + "AMLA": "Amelanchier laevis Wiegand", + "BETUL": "Betula sp.", + "CAGL8": "Carya glabra (Mill.) Sweet", + "CATO6": "Carya tomentosa (Lam.) Nutt.", + "FAGR": "Fagus grandifolia Ehrh.", + "GOLA": "Gordonia lasianthus (L.) Ellis", + "LITU": "Liriodendron tulipifera L.", + "LYLU3": "Lyonia lucida (Lam.) K. Koch", + "MAGNO": "Magnolia sp.", + "NYBI": "Nyssa biflora Walter", + "NYSY": "Nyssa sylvatica Marshall", + "OXYDE": "Oxydendrum sp.", + "PEPA37": "Persea palustris (Raf.) Sarg.", + "PIEL": "Pinus elliottii Engelm.", + "PIPA2": "Pinus palustris Mill.", + "PINUS": "Pinus sp.", + "PITA": "Pinus taeda L.", + "PRSE2": "Prunus serotina Ehrh.", + "QUAL": "Quercus alba L.", + "QUCO2": "Quercus coccinea", + "QUGE2": "Quercus geminata Small", + "QUHE2": "Quercus hemisphaerica W. Bartram ex Willd.", + "QULA2": "Quercus laevis Walter", + "QULA3": "Quercus laurifolia Michx.", + "QUMO4": "Quercus montana Willd.", + "QUNI": "Quercus nigra L.", + "QURU": "Quercus rubra L.", + "QUERC": "Quercus sp.", + "ROPS": "Robinia pseudoacacia L.", + "TSCA": "Tsuga canadensis (L.) Carriere", + } + metadata = { + "train": { + "url": "https://zenodo.org/record/3934932/files/IDTREES_competition_train_v2.zip?download=1", # noqa: E501 + "md5": "5ddfa76240b4bb6b4a7861d1d31c299c", + "filename": "IDTREES_competition_train_v2.zip", + }, + "test": { + "url": "https://zenodo.org/record/3934932/files/IDTREES_competition_test_v2.zip?download=1", # noqa: E501 + "md5": "b108931c84a70f2a38a8234290131c9b", + "filename": "IDTREES_competition_test_v2.zip", + }, + } + directories = {"train": ["train"], "test": ["task1", "task2"]} + image_size = (200, 200) + + def __init__( + self, + root: str = "data", + split: str = "train", + task: str = "task1", + transforms: Optional[Callable[[Dict[str, Tensor]], Dict[str, Tensor]]] = None, + download: bool = False, + checksum: bool = False, + ) -> None: + """Initialize a new IDTReeS dataset instance. + + Args: + root: root directory where dataset can be found + split: one of "train" or "test" + task: 'task1' for detection, 'task2' for detection + classification + (only relevant for split='test') + 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) + + Raises: + ImportError: if laspy or pandas are are not installed + """ + assert split in ["train", "test"] + assert task in ["task1", "task2"] + self.root = root + self.split = split + self.task = task + self.transforms = transforms + self.download = download + self.checksum = checksum + self.class2idx = {c: i for i, c in enumerate(self.classes)} + self.idx2class = {i: c for i, c in enumerate(self.classes)} + self.num_classes = len(self.classes) + self._verify() + + try: + import pandas as pd # noqa: F401 + except ImportError: + raise ImportError( + "pandas is not installed and is required to use this dataset" + ) + try: + import laspy # noqa: F401 + except ImportError: + raise ImportError( + "laspy is not installed and is required to use this dataset" + ) + + self.images, self.geometries, self.labels = self._load(root) + + def __getitem__(self, index: int) -> Dict[str, Tensor]: + """Return an index within the dataset. + + Args: + index: index to return + + Returns: + data and label at that index + """ + path = self.images[index] + image = self._load_image(path).to(torch.uint8) # type:ignore[attr-defined] + hsi = self._load_image(path.replace("RGB", "HSI")) + chm = self._load_image(path.replace("RGB", "CHM")) + las = self._load_las(path.replace("RGB", "LAS").replace(".tif", ".las")) + sample = {"image": image, "hsi": hsi, "chm": chm, "las": las} + + if self.split == "test": + if self.task == "task2": + sample["boxes"] = self._load_boxes(path) + else: + sample["boxes"] = self._load_boxes(path) + sample["label"] = self._load_target(path) + + 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.images) + + def _load_image(self, path: str) -> Tensor: + """Load a tiff file. + + Args: + path: path to .tif file + + Returns: + the image + """ + with rasterio.open(path) as f: + array = f.read(out_shape=self.image_size, resampling=Resampling.bilinear) + tensor: Tensor = torch.from_numpy(array) # type: ignore[attr-defined] + return tensor + + def _load_las(self, path: str) -> Tensor: + """Load a single point cloud. + + Args: + path: path to .las file + + Returns: + the point cloud + """ + import laspy + + las = laspy.read(path) + array = np.stack([las.x, las.y, las.z], axis=0) + tensor: Tensor = torch.from_numpy(array) # type: ignore[attr-defined] + return tensor + + def _load_boxes(self, path: str) -> Tensor: + """Load object bounding boxes. + + Args: + path: path to .tif file + + Returns: + the bounding boxes + """ + base_path = os.path.basename(path) + + # Find object ids and geometries + if self.split == "train": + indices = self.labels["rsFile"] == base_path + ids = self.labels[indices]["id"].tolist() + geoms = [self.geometries[i]["geometry"]["coordinates"][0][:4] for i in ids] + # Test set - Task 2 has no mapping csv. Mapping is inside of geometry + else: + ids = [ + k + for k, v in self.geometries.items() + if v["properties"]["plotID"] == base_path + ] + geoms = [self.geometries[i]["geometry"]["coordinates"][0][:4] for i in ids] + + # Convert to pixel coords + boxes = [] + with rasterio.open(path) as f: + for geom in geoms: + coords = [f.index(x, y) for x, y in geom] + xmin = min([coord[0] for coord in coords]) + xmax = max([coord[0] for coord in coords]) + ymin = min([coord[1] for coord in coords]) + ymax = max([coord[1] for coord in coords]) + boxes.append([xmin, ymin, xmax, ymax]) + + tensor: Tensor = torch.tensor(boxes) # type: ignore[attr-defined] + return tensor + + def _load_target(self, path: str) -> Tensor: + """Load target label for a single sample. + + Args: + path: path to image + + Returns: + the label + """ + # Find indices for objects in the image + base_path = os.path.basename(path) + indices = self.labels["rsFile"] == base_path + + # Load object labels + classes = self.labels[indices]["taxonID"].tolist() + labels = [self.class2idx[c] for c in classes] + tensor: Tensor = torch.tensor(labels) # type: ignore[attr-defined] + return tensor + + def _load(self, root: str) -> Tuple[List[str], Dict[int, Dict[str, Any]], Any]: + """Load files, geometries, and labels. + + Args: + root: root directory + + Returns: + the image path, geometries, and labels + """ + import pandas as pd + + if self.split == "train": + directory = os.path.join(root, self.directories[self.split][0]) + labels: pd.DataFrame = self._load_labels(directory) + geoms = self._load_geometries(directory) + else: + directory = os.path.join(root, self.task) + if self.task == "task1": + geoms = None # type: ignore[assignment] + labels = None + else: + geoms = self._load_geometries(directory) + labels = None + + images = glob.glob(os.path.join(directory, "RemoteSensing", "RGB", "*.tif")) + + return images, geoms, labels + + def _load_labels(self, directory: str) -> Any: + """Load the csv files containing the labels. + + Args: + directory: directory containing csv files + + Returns: + a pandas DataFrame containing the labels for each image + """ + import pandas as pd + + path_mapping = os.path.join(directory, "Field", "itc_rsFile.csv") + path_labels = os.path.join(directory, "Field", "train_data.csv") + df_mapping = pd.read_csv(path_mapping) + df_labels = pd.read_csv(path_labels) + df_mapping = df_mapping.set_index("indvdID", drop=True) + df_labels = df_labels.set_index("indvdID", drop=True) + df = df_labels.join(df_mapping, on="indvdID") + df = df.drop_duplicates() + df.reset_index() + return df + + def _load_geometries(self, directory: str) -> Dict[int, Dict[str, Any]]: + """Load the shape files containing the geometries. + + Args: + directory: directory containing .shp files + + Returns: + a dict containing the geometries for each object + """ + filepaths = glob.glob(os.path.join(directory, "ITC", "*.shp")) + + features: Dict[int, Dict[str, Any]] = {} + for path in filepaths: + with fiona.open(path) as src: + for i, feature in enumerate(src): + if self.split == "train": + features[feature["properties"]["id"]] = feature + # Test set task 2 has no id + else: + features[i] = feature + return features + + def _verify(self) -> None: + """Verify the integrity of the dataset. + + Raises: + RuntimeError: if ``download=False`` but dataset is missing or checksum fails + """ + url = self.metadata[self.split]["url"] + md5 = self.metadata[self.split]["md5"] + filename = self.metadata[self.split]["filename"] + directories = self.directories[self.split] + + # Check if the files already exist + exists = [ + os.path.exists(os.path.join(self.root, directory)) + for directory in directories + ] + if all(exists): + return + + # Check if zip file already exists (if so then extract) + filepath = os.path.join(self.root, filename) + if os.path.exists(filepath): + extract_archive(filepath) + return + + # Check if the user requested to download the dataset + if not self.download: + raise RuntimeError( + "Dataset not found in `root` directory and `download=False`, " + "either specify a different `root` directory or use `download=True` " + "to automaticaly download the dataset." + ) + + # Download and extract the dataset + download_url( + url, self.root, filename=filename, md5=md5 if self.checksum else None + ) + filepath = os.path.join(self.root, filename) + extract_archive(filepath) + + def plot( + self, + sample: Dict[str, Tensor], + show_titles: bool = True, + suptitle: Optional[str] = None, + hsi_indices: Tuple[int, int, int] = (0, 1, 2), + ) -> plt.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 string to use as a suptitle + hsi_indices: tuple of indices to create HSI false color image + + Returns: + a matplotlib Figure with the rendered sample + """ + assert len(hsi_indices) == 3 + + def normalize(x: Tensor) -> Tensor: + return (x - x.min()) / (x.max() - x.min()) + + ncols = 3 + + hsi = normalize(sample["hsi"][hsi_indices, :, :]).permute((1, 2, 0)).numpy() + chm = normalize(sample["chm"]).permute((1, 2, 0)).numpy() + + if "boxes" in sample: + labels = ( + [self.idx2class[int(i)] for i in sample["label"]] + if "label" in sample + else None + ) + image = draw_bounding_boxes( + image=sample["image"], boxes=sample["boxes"], labels=labels + ) + image = image.permute((1, 2, 0)).numpy() + else: + image = sample["image"].permute((1, 2, 0)).numpy() + + if "prediction_boxes" in sample: + ncols += 1 + labels = ( + [self.idx2class[int(i)] for i in sample["prediction_label"]] + if "prediction_label" in sample + else None + ) + preds = draw_bounding_boxes( + image=sample["image"], boxes=sample["prediction_boxes"], labels=labels + ) + preds = preds.permute((1, 2, 0)).numpy() + + fig, axs = plt.subplots(ncols=ncols, figsize=(ncols * 10, 10)) + axs[0].imshow(image) + axs[0].axis("off") + axs[1].imshow(hsi) + axs[1].axis("off") + axs[2].imshow(chm) + axs[2].axis("off") + if ncols > 3: + axs[3].imshow(preds) + axs[3].axis("off") + + if show_titles: + axs[0].set_title("Ground Truth") + axs[1].set_title("Hyperspectral False Color Image") + axs[2].set_title("Canopy Height Model") + if ncols > 3: + axs[3].set_title("Predictions") + + if suptitle is not None: + plt.suptitle(suptitle) + + return fig + + def plot_las(self, index: int, colormap: Optional[str] = None) -> Any: + """Plot a sample point cloud at the index. + + Args: + index: index to plot + colormap: a valid matplotlib colormap + + Returns: + a open3d.visualizer.Visualizer object. Use + Visualizer.run() to display + + Raises: + ImportError: if open3d is not installed + """ + try: + import open3d # noqa: F401 + except ImportError: + raise ImportError( + "open3d is not installed and is required to use this dataset" + ) + import laspy + + path = self.images[index] + path = path.replace("RGB", "LAS").replace(".tif", ".las") + las = laspy.read(path) + points = np.stack([las.x, las.y, las.z], axis=0).transpose((1, 0)) + + if colormap: + cm = plt.cm.get_cmap(colormap) + norm = plt.Normalize() + colors = cm(norm(points[:, 2]))[:, :3] + else: + # Some point cloud files have no color->points mapping + if hasattr(las, "red"): + colors = np.stack([las.red, las.green, las.blue], axis=0) + colors = colors.transpose((1, 0)) / 65535 + # Default to no colormap if no colors exist in las file + else: + colors = np.zeros_like(points) + + pcd = open3d.geometry.PointCloud() + pcd.points = open3d.utility.Vector3dVector(points) + pcd.colors = open3d.utility.Vector3dVector(colors) + vis = open3d.visualization.Visualizer() + vis.create_window() + vis.add_geometry(pcd) + return vis