From 4d691db6cd8c260d8749dbcf21b79c00f7350ef9 Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Thu, 2 May 2024 13:49:18 -0600 Subject: [PATCH] DAS-2115: Change how the default scale_extent is computed (#9) --- CHANGELOG.md | 19 ++- bin/extract-release-notes.sh | 2 +- docker/service_version.txt | 2 +- docs/HyBIG-Example-Usage.ipynb | 3 + harmony_browse_image_generator/sizes.py | 85 ++-------- pip_requirements.txt | 2 +- tests/fixtures/RGB.byte.small.tif | Bin 0 -> 6447 bytes tests/test_adapter.py | 41 +++-- tests/unit/test_browse.py | 3 + tests/unit/test_sizes.py | 200 ++++++++---------------- 10 files changed, 124 insertions(+), 233 deletions(-) create mode 100644 tests/fixtures/RGB.byte.small.tif diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1720d..66f7ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,24 @@ -## v1.0.2 -### 2024-04-05 +## v1.1.0 - 2024-04-30 + +### Changed +Changes the computation for an output image's default scale extent. Previously +we considered ICD preferred ScaleExtents as covering the entire globe or pole. +This change now takes just the input image bounds and converts them to the target crs +and uses that transformed boundry as the default region to make a scale extent from. + +Upgraded harmony-service-lib to v1.0.26 + +## v1.0.2 - 2024-04-05 This version of HyBIG correctly handles missing/bad input data marked by _FillValue or NoData. Anytime a bad value occurs in the input raster, the output png image will set to transparent. -## v1.0.1 -### 2024-04-05 +## v1.0.1 - 2024-04-05 This version of HyBIG updates the repository to use `black` code formatting throughout. There should be no functional change to the service. -## v1.0.0 -### 2024-01-22 +## v1.0.0 - 2024-01-22 This version of the Harmony Browse Image Generator (HyBIG) contains all functionality previously released internally to EOSDIS as sds/harmony-browse-image-generator:0.0.11. diff --git a/bin/extract-release-notes.sh b/bin/extract-release-notes.sh index 3da532c..bb15f23 100755 --- a/bin/extract-release-notes.sh +++ b/bin/extract-release-notes.sh @@ -19,4 +19,4 @@ VERSION_PATTERN="^## v" result=$(awk "/$VERSION_PATTERN/{c++; if(c==2) exit;} c==1" "$CHANGELOG_FILE") # Print the result -echo "$result" | grep -v "^#" +echo "$result" | grep -v "$VERSION_PATTERN" diff --git a/docker/service_version.txt b/docker/service_version.txt index 6d7de6e..9084fa2 100644 --- a/docker/service_version.txt +++ b/docker/service_version.txt @@ -1 +1 @@ -1.0.2 +1.1.0 diff --git a/docs/HyBIG-Example-Usage.ipynb b/docs/HyBIG-Example-Usage.ipynb index a72fa04..ecf34fd 100644 --- a/docs/HyBIG-Example-Usage.ipynb +++ b/docs/HyBIG-Example-Usage.ipynb @@ -233,6 +233,7 @@ " collection=aster_collection,\n", " granule_id=aster_granule,\n", " scale_extent=scale_extent,\n", + " crs='EPSG:4326',\n", " format='image/jpeg',\n", ")\n", "\n", @@ -312,6 +313,7 @@ " collection=measures_collection,\n", " granule_id=measures_granule,\n", " scale_size=scale_sizes,\n", + " crs='EPSG:4326',\n", " format='image/png',\n", ")\n", "\n", @@ -435,6 +437,7 @@ " granule_id=measures_granule,\n", " scale_extent=iceland_extent,\n", " scale_size=iceland_scale_size,\n", + " crs='EPSG:4326',\n", " format='image/png',\n", ")\n", "\n", diff --git a/harmony_browse_image_generator/sizes.py b/harmony_browse_image_generator/sizes.py index d7c9f25..f6e9473 100644 --- a/harmony_browse_image_generator/sizes.py +++ b/harmony_browse_image_generator/sizes.py @@ -125,19 +125,22 @@ def get_target_grid_parameters(message: Message, data_array: DataArray) -> GridP """ target_crs = choose_target_crs(message.format.srs, data_array) - target_scale_extent = choose_scale_extent(message, target_crs) + target_scale_extent = choose_scale_extent(message, target_crs, data_array) target_dimensions = choose_target_dimensions( message, data_array, target_scale_extent, target_crs ) return get_rasterio_parameters(target_crs, target_scale_extent, target_dimensions) -def choose_scale_extent(message: Message, target_crs: CRS) -> ScaleExtent: - """Return the scaleExtent of the target image. +def choose_scale_extent( + message: Message, target_crs: CRS, data_array: DataArray +) -> ScaleExtent: + """Return the scaleExtent for the target image. - Check the message for a defined scale extent and returns that or returns - the best alternative based on the target_crs either returning it from a - lookup based on the ICD, or computed with pyproj.area_of_use + Returns a scale extent found in the input Message. + + Otherwise, computes a bounding box in the target CRS based on the input + granule extent. """ if has_scale_extents(message): @@ -150,11 +153,11 @@ def choose_scale_extent(message: Message, target_crs: CRS) -> ScaleExtent: 'ymax': message.format.scaleExtent.y.max, } ) - elif is_preferred_crs(target_crs): - scale_extent = icd_defined_extent_from_crs(target_crs) else: - # compute a best guess area based on the CRS's region of interest - scale_extent = best_guess_scale_extent(target_crs) + left, bottom, right, top = data_array.rio.transform_bounds(target_crs) + scale_extent = ScaleExtent( + {'xmin': left, 'ymin': bottom, 'xmax': right, 'ymax': top} + ) return scale_extent @@ -322,68 +325,6 @@ def needs_tiling(grid_parameters: GridParams) -> bool: return grid_parameters['height'] * grid_parameters['width'] > MAX_UNTILED_GRIDCELLS -def icd_defined_extent_from_crs(crs: CRS) -> ScaleExtent: - """return the predefined scaleExtent for a GIBS image. - - looks up which projetion is being used and returns the scaleExtent. - - """ - if crs.to_string() == PREFERRED_CRS['global']: - scale_extent = ScaleExtent( - {'xmin': -180.0, 'ymin': -90.0, 'xmax': 180.0, 'ymax': 90.0} - ) - elif crs.to_string() in [PREFERRED_CRS['north'], PREFERRED_CRS['south']]: - # both north and south preferred CRSs have same extents. - scale_extent = ScaleExtent( - { - 'xmin': -4194304.0, - 'ymin': -4194304.0, - 'xmax': 4194304.0, - 'ymax': 4194304.0, - } - ) - else: - raise HyBIGValueError(f'Invalid input CRS: {crs.to_string()}') - - return scale_extent - - -def best_guess_scale_extent(in_crs: CRS) -> ScaleExtent: - """Guess the best scale extent. - - This routine will try to guess what a user intended if they did not include - a scaleExtent and also used a non-preferred CRS. We convert the CRS into a - pyproj crs check for an area of use. If this exists we return the bounds, - projecting them if the crs is a projected crs. - - if no area_of_use exists, we return the ICD defined scale extent that - relates to the closest prefered CRS. - - """ - crs = pyCRS(in_crs.to_wkt(version='WKT2')) - if crs.area_of_use is None: - best_crs = choose_best_crs_from_metadata(crs) - scale_extent = icd_defined_extent_from_crs(best_crs) - elif crs.is_projected: - transformer = Transformer.from_crs(crs.geodetic_crs, crs, always_xy=True) - projected_bounds = transformer.transform_bounds(*crs.area_of_use.bounds) - scale_extent = { - 'xmin': projected_bounds[0], - 'ymin': projected_bounds[1], - 'xmax': projected_bounds[2], - 'ymax': projected_bounds[3], - } - else: - scale_extent = { - 'xmin': crs.area_of_use.bounds[0], - 'ymin': crs.area_of_use.bounds[1], - 'xmax': crs.area_of_use.bounds[2], - 'ymax': crs.area_of_use.bounds[3], - } - - return scale_extent - - def best_guess_target_dimensions( data_array: DataArray, scale_extent: ScaleExtent, target_crs: CRS ) -> Dimensions: diff --git a/pip_requirements.txt b/pip_requirements.txt index f0352af..0353a86 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -1,4 +1,4 @@ -harmony-service-lib~=1.0.25 +harmony-service-lib~=1.0.26 pystac~=0.5.6 matplotlib==3.7.1 rasterio==1.3.6 diff --git a/tests/fixtures/RGB.byte.small.tif b/tests/fixtures/RGB.byte.small.tif new file mode 100644 index 0000000000000000000000000000000000000000..21f3b517b3e1c2f588a3c1bf3bf50ece5aae32dd GIT binary patch literal 6447 zcmc(iTW}lKdB-VHTI>Rg-NoMTi@jiRU4Tmv1PDS92wp%6geZs?Q4~drq9jU`L|GT= zE=#5?ONt}gv86ap?8M_q<2X)gIi1+!X{UL}q_HPX+NxmVpZ9z~QVEnglZfsLQdp~GD@%4>u&G|dP-Tc7o-wl8Tylj4M z{ZZ+^)$0$f3*w*twb}n#{R@MApY-~8EKgh8CZJ36`>@`)>Ft{==(GWT`=+OjZG(V} z8`_>R+t6mce09@$9W{4V#)G-8U@AB{Q43x?v2rlz=C)t{*n0VsNq$p}L|w)0{!MRw z4>l;2grgmBD70CYVbg$E2IBdAsq}k50d9__V5b`~Ej%3cr4nQ=h3FbYh(3%#P!^Pz zuimQHEB`-iNOwP+A3@V;Un+$voaBFZHmG|Wlo^6^RirS2=E_Wcr+abr`gcC})aMppRf4=tWj1+m7!4pO ziTKF&%}~2%^O*}*Kc@NG%ijwS5>6`tQp5-*KngH`COL{0Szh|z;z4c+a*A-K=uRK@ zb(gux{qBkVxnlgvbH}=7D;|u3;Q#`BKt7lJ{-HdVJ%D^EWf4~q9fZa#Am8QQia-KoA6_5FZVBkoSN3DNBRq4UABMG690m=JUW=0a`Gu7|jbbBhj1`sg?vo z7u28{iOTu@J;yE>N6NcOlk=a>6f~tol$CS}UY7;M1dQY!{Jint zZyP(uCqC}xBeVb^;-u2#A%R2^$qE$1Q=G^OieQKMpvFhyYNkgW7?%s%<7d{a`cf+x z$SELBKb)z03e%p-!40H(@6AT)g}46r{dc;$!kkExBEe-M?Heq&;;WBKsKzi7mdJ>C)~o`>QE zPuH+-aLH5HHFx>xU;IaN!k_=L5se$X#_$@&h%|x`2#R07bqBoCLLED&_PLn?%_~YU z2`o54$w6*gwLc(Atg5qdmn+l>JHt8!KGC7(`>aw=Y+=7O&>OBl(nry(C%w(nRq>9k zqEqLw*=y2?yM0$*OwI1Gq9#sy&8RjySrPM{s26QgHX1%m$QOrv7>ly9k7({TT9D4> zoW9Yx|IWLh%;YM~eFkMPg7;yx52aC7;M2)$y<}9nV>1=X zNeoTwm_2d?AhD?qt&mW&5iRTJdDlpUB}=!movmP?%p}x30p&onETgkG{IfU7gSQWS ztMSH%jn$JoBvT1s@UQ-Doo2nrm#a7-BN*iaX9P;3D1~9n2Y=ZJ1O+N?JoWrtIuc?c z24iV_%#aeMoQ-SgsG43u z8logf8qLT|sylh*`c+QnIn@MGKro78G=efH#$XtWG18y>ph3rsfFV$}!Wt?Y(^yLu z95X(!RZf|5Bq-aOW(T#1Wf*!Z5@1Is&!aP!@cq~LQ_qugSHvUdAUSgNJNJX8sOp>~ zvmCg@fT4p8&Bqs91fvZ-ilQ7iI%$^k`8pzdG5#>f8=pxW@2qUq~yZ1 zWrthAAeChRgHE2n=gy;JN9ldXgZt<1-8&t1w2-58cE>rD7gfgR^-3lW=FOIrGf$iX zr;pE1^%VM=n&1Kv;IIHs$Ogf2lq^V=!ODP*5iC^z&Wh;(Mvnj_ki0#X>dyYTgPb9>Wo{N=p$f!wPR>)8LcfbN7uSu zcy(;X9bGC9jb(!&zOyT|@XEEYo3O%~B5|rjJ$TSCBB78aV;nkutcC?>tQoyL;PLu! zl94Dzreu|u6-E&ST^4LjjTxG2DoNYwNrbm_Cu+r3Fi^S-<#)ivNn~I@HhEk+b?eFp z4Kc{`GL!6f0xb2!^S7do3iJV=q48YYjeYZ5uS+sb2zYWlkKt6I)DL_F-jbZS9yuU+ zG2j1517|o^lLS+hli_@|pm*46#E7O7cBy5;52s5=_b`&%h7`xpk^RitDNlZuIoz=~JawzxKsc%1|u!&pvDfcoM;}044SmO4Du{_d@X!oGL?Ukj#J_coCbrP`~%R z#J#t{fPR`PmNTXk`u-1ZfqYF-+3nSy{_AI@fkC}9f|Fofb3{NOD3NLgjOaxPR#15m z>z1k5Ce1VbBZGV9r!j&EC*y#pRlvaU0XRAU1Cr*|2RnU8deoQS?QO=-D!KHmb^SHK zQ(s!#b87ee;>~`v)1aX7oL3nF+vLAI1eJ{!CyBhP65#ZN{-V2kEG~6k_;!q zU~jS0(O5dE#~yVgA<->xybOg407)y3B2L-k41x>k+Kvj-f{{o{BuN>NfDDu2WL^-vdJ2kSA>cd;8D=bO zxv@|-9!bYq$pY+s(~luw>Sz&;_9EdN3{skSGxmslLfz@bfWCdmLnK=iJYNNT&sYlBXSR4|#aGOq4MOSP(7 z&N{trD;OY4-|TTDUPPLaQvg2lM2kqc-~pD@FyapTa+Cgn!~W7dQ9q_#dL?r6wc735 zE8ke-Q(aa>kqw@b2+d&6-=3#93CKe8AZ~-4Xo`x#gG5v_!W}8yO%By}SxMXN$vB0U zFfbqT0a@aGzUWq8GkS--PQ{z5cma%4@;POHb|>09ffgq4p#^g67{7F*TnB}C?oM3Qb5bXXz(>Cpxs@W_jSxKk6)Ynt&I*{x&&b|f`| zWp)BtP`84mhQS(XCloX_a_i+k@VY~&JLJt&(abh~b_ZST=p4xfbXy7VevVhnPy)oQZbqADm}00#m^aLHu>d-sTD!Pqw*L_@=HNV%83!;R zY=A_e!8zcX&hevYcAUu8&@>>a;=N-4#-FX@rD>wN5AUgupFTT0-rK)C5{m@Qox|l) zQ8guyc!IS9WDyiYQcPJhfxj#}4wUJr=0}fBl*YEz4o|j%*_@ezoeG|t1|^)TJa+R- zkQ|SVETZXAG`-!Mt@|^eF@k4D@obgot+iw9;oC3&>4V14e%1KqhmEo6>YW##@%X%{ zWN2$Chmu}(WRMqBLD5wJqiC98tHDrudcqmoQl6@fE=+aQTK*k?9k8)9H#)3)3UaGR zy6Vep$9s37nH{J*ie`79>1{w9ymu#Fn8JJO>T;-`-BQ-_s9XJ7pGccU?_ ze;}>v?Cj!jHXFP3(m^8<1o~SM&qKd{I=q)0IXJj?g&f%@&acJ}Us9JZS;x+;t^4 zz?v0RR7;QzNj412){Icdj&@|aT21;}+Ub!cZshPrOqhE7E?AzBXopsBxHq2eSiLlR z^W^TOS&AV;5oP7<#E!9SX=R__Ci*K?Ac!F7z(?Ru&i=(E-LNe?V%U$`WPVGYU+WfH zx#C`0+j`~n&O6uFw;D2AikB;HIvdpoib79@vzq&!5)5goCCip>hb`MSjL)9r*TBHz z(Ob*;LSpyIRL9KD*!T`D(;@eCa)Qc