Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
KMarkert committed Oct 3, 2021
2 parents d880857 + e759b50 commit a47e0f3
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 42 deletions.
79 changes: 59 additions & 20 deletions hydrafloods/geeutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_geoms(img):
args:
img (ee.Image): image to get geometry from
returns:
ee.Geometry: geometry of image
"""
Expand All @@ -53,47 +53,78 @@ def get_geoms(img):
def export_image(
image,
region,
asset_id,
asset_id=None,
description=None,
scale=1000,
crs="EPSG:4326",
pyramiding=None,
export_type='toAsset',
folder=None,
):
"""Function to wrap image export with EE Python API
args:
image (ee.Image): image to export
region (ee.Geometry): region to export image
asset_id (str): asset ID to export image to
asset_id (str | None, optional): asset ID to export image to\
if None then asset_id will be a random string. default = None
description (str | None, optional): description to identify image export/
if None then description will be random string. default = None
scale (int, optional): resolution in meters to export image to. default = 1000
crs (str, optional): epsg code to export image to. default = "EPSG:4326"
pyramiding (dict | None, optional): dictionary defining band pyramiding scheme.
if None then "mean" will be used as default for all bands. default = None
export_type (str, optional) : method by which to export the image.
Default is 'toAsset', can also be 'toDrive'.
folder (str | None, optional): target folder to export for 'toDrive'/
if None then export should go to root of Drive
"""
if (description == None) or (type(description) != str):
if (asset_id is None) or (type(asset_id) != str):
asset_id = "".join(
random.SystemRandom().choice(string.ascii_letters) for _ in range(8)
).lower()
if (description is None) or (type(description) != str):
description = "".join(
random.SystemRandom().choice(string.ascii_letters) for _ in range(8)
).lower()
if (type(export_type) != str):
raise TypeError(f'Input for export_type not a string, was a '
f'{type(export_type)}.')
elif (export_type != 'toAsset') and (export_type != 'toDrive'):
raise ValueError('Invalid input for export_type, must be '
'"toAsset" or "toDrive".')
if (folder is not None) and (type(folder) != str):
raise TypeError(f'Input for folder was not a string, was a '
f'{type(folder)}')
# get serializable geometry for export
export_region = region.bounds(maxError=10).getInfo()["coordinates"]

if pyramiding is None:
pyramiding = {".default": "mean"}

# set export process
export = ee.batch.Export.image.toAsset(
image,
description=description,
assetId=asset_id,
scale=scale,
region=export_region,
maxPixels=1e13,
crs=crs,
pyramidingPolicy=pyramiding,
)
if export_type == 'toAsset':
export = ee.batch.Export.image.toAsset(
image,
description=description,
assetId=asset_id,
scale=scale,
region=export_region,
maxPixels=1e13,
crs=crs,
pyramidingPolicy=pyramiding,
)
elif export_type == 'toDrive':
export = ee.batch.Export.image.toDrive(
image,
description=description,
folder=folder,
scale=scale,
region=export_region,
maxPixels=1e13,
crs=crs,
)
# start export process
export.start()

Expand All @@ -109,6 +140,8 @@ def batch_export(
scale=1000,
crs="EPSG:4326",
pyramiding=None,
export_type='toAsset',
folder=None,
metadata=None,
verbose=False,
):
Expand All @@ -125,6 +158,10 @@ def batch_export(
crs (str, optional): epsg code to export image to. default = "EPSG:4326"
pyramiding (dict | None, optional): dictionary defining band pyramiding scheme.
if None then "mean" will be used as default for all bands. default = None
export_type (str, optional) : method by which to export the image.
Default is 'toAsset', can also be 'toDrive'.
folder (str | None, optional): target folder to export for 'toDrive'/
if None then export should go to root of Drive
metadata (dict | None, optional):
verbose (bool, optional):
Expand Down Expand Up @@ -171,11 +208,13 @@ def batch_export(
export_image(
img,
region,
exportName,
assed_id=exportName,
description=description,
scale=scale,
crs=crs,
pyramiding=pyramiding,
export_type=export_type,
folder=folder,
)

return
Expand Down Expand Up @@ -228,7 +267,7 @@ def add_indices(img, indices=["mndwi"]):
args:
img (ee.Image): image to calculate indices from
indices (list[str], optional): list of strings of index names to calculate.
indices (list[str], optional): list of strings of index names to calculate.
can use any named index function in geeutils. default = ["mndwi"]
returns:
Expand All @@ -251,9 +290,9 @@ def tile_region(region, grid_size=0.1, intersect_geom=None, contain_geom=None,ce
args:
region (ee.Geometry): region to create tile grid over
grid_size (float, optional): resolution in decimal degrees to create tiles. default = 0.1
intersect_geom (ee.Geometry | None, optional): geometry object to filter tiles that intesect with
intersect_geom (ee.Geometry | None, optional): geometry object to filter tiles that intesect with
geometry useful for filtering tiles that are created over oceans with no data. default = None
contain_geom (ee.Geometry | None, optional): geometry object to filter tiles that are contained within
contain_geom (ee.Geometry | None, optional): geometry object to filter tiles that are contained within
geometry useful for filtering tiles that are only in an area. default = None
returns:
Expand Down Expand Up @@ -336,11 +375,11 @@ def country_bbox(country_name, max_error=1000):
args:
country_name (str): US-recognized country name
max_error (float,optional): The maximum amount of error tolerated when
max_error (float,optional): The maximum amount of error tolerated when
performing any necessary reprojection. default = 100
returns:
ee.Geometry: geometry of country bounding box
ee.Geometry: geometry of country bounding box
"""

all_countries = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017")
Expand Down
28 changes: 14 additions & 14 deletions hydrafloods/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,50 @@ class TestFilters:
def test_lee_sigma(self):
filtered = hf.lee_sigma(IMG, keep_bands=None)
result = filtered.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result = {k: round(v, 6) for k, v in result.items()}
result = {k: round(v, 3) for k, v in result.items()}

expected = {"b1": 2.750624}
expected = {"b1": 2.751}

assert result == expected

def test_gamma_map(self):
filtered = hf.gamma_map(IMG)
filtered = hf.gamma_map(IMG,keep_bands=None)
result = filtered.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result = {k: round(v, 6) for k, v in result.items()}
result = {k: round(v, 3) for k, v in result.items()}

expected = {"b1": 0.517725}
expected = {"b1": 0.518}

assert result == expected

def test_refined_lee(self):
filtered = hf.refined_lee(IMG)
filtered = hf.refined_lee(IMG, keep_bands= None)
result = filtered.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result = {k: round(v, 6) for k, v in result.items()}
result = {k: round(v, 3) for k, v in result.items()}

expected = {"b1": 0.513949}
expected = {"b1": 0.514}

assert result == expected

def test_p_median(self):
filtered = hf.p_median(IMG)
result = filtered.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result = {k: round(v, 6) for k, v in result.items()}
result = {k: round(v, 3) for k, v in result.items()}

expected = {"b1": 0.503379}
expected = {"b1": 0.503}

assert result == expected

def test_perona_malik(self):
filtered1 = hf.perona_malik(IMG, method=1)
result1 = filtered1.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result1 = {k: round(v, 6) for k, v in result1.items()}
result1 = {k: round(v, 3) for k, v in result1.items()}

expected1 = {"constant": -0.252697}
expected1 = {"constant": -0.253}

filtered2 = hf.perona_malik(IMG, method=2)
result2 = filtered2.reduceRegion(ee.Reducer.mean(), GEOM, SCALE).getInfo()
result2 = {k: round(v, 6) for k, v in result2.items()}
result2 = {k: round(v, 3) for k, v in result2.items()}

expected2 = {"constant": -0.328251}
expected2 = {"constant": -0.328}

assert (result1 == expected1) and (result2 == expected2)
67 changes: 67 additions & 0 deletions hydrafloods/tests/test_geeutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Unit tests for geeutils.py."""
import pytest
import unittest.mock as mock

import ee
import hydrafloods as hf


def test_export_image_toAsset():
"""Test that the toAsset export gets called by default."""
# mock ee export functionality
ee.batch.Export.image.toAsset = mock.MagicMock()
ee.Image = mock.MagicMock()
ee.Geometry = mock.MagicMock()
# call export function
hf.geeutils.export_image(ee.Image, ee.Geometry, 'assid', 'desc')
# assert that the ee.export call within the function was called
assert ee.batch.Export.image.toAsset.call_count == 1


def test_export_image_toDrive():
"""Test that the toDrive export gets called when specified."""
# mock ee export functionality
ee.batch.Export.image.toDrive = mock.MagicMock()
ee.Image = mock.MagicMock()
ee.Geometry = mock.MagicMock()
# call export function
hf.geeutils.export_image(ee.Image, ee.Geometry, 'assid', 'desc',
export_type='toDrive')
# assert that the ee.export call within the function was called
assert ee.batch.Export.image.toDrive.call_count == 1


def test_export_image_export_type_invalid_string():
"""Test ValueError for export_type."""
# mock ee export functionality
ee.batch.Export.image.toAsset = mock.MagicMock()
ee.Image = mock.MagicMock()
ee.Geometry = mock.MagicMock()
# call export function
with pytest.raises(ValueError):
hf.geeutils.export_image(ee.Image, ee.Geometry, 'assid', 'desc',
export_type='invalid string')


def test_export_image_export_type_invalid_type():
"""Test TypeError for export_type."""
# mock ee export functionality
ee.batch.Export.image.toAsset = mock.MagicMock()
ee.Image = mock.MagicMock()
ee.Geometry = mock.MagicMock()
# call export function
with pytest.raises(TypeError):
hf.geeutils.export_image(ee.Image, ee.Geometry, 'assid', 'desc',
export_type=123)


def test_export_image_folder_invalid_type():
"""Test TypeError for folder."""
# mock ee export functionality
ee.batch.Export.image.toAsset = mock.MagicMock()
ee.Image = mock.MagicMock()
ee.Geometry = mock.MagicMock()
# call export function
with pytest.raises(TypeError):
hf.geeutils.export_image(ee.Image, ee.Geometry, 'assid', 'desc',
folder=123)
16 changes: 8 additions & 8 deletions hydrafloods/tests/test_thresholds.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def test_edge_otsu(self):
.reduceRegion(ee.Reducer.mean(), GEOM, REDUCTION_SCALE)
.getInfo()
)
threshold = {k: round(v, 6) for k, v in threshold.items()}
threshold = {k: round(v, 3) for k, v in threshold.items()}

thresh_expected = {"constant": -14.205071}
thresh_expected = {"constant": -14.205}

kwargs["return_threshold"] = False
water = (
Expand All @@ -54,18 +54,18 @@ def test_bmax_otsu(self):
.reduceRegion(ee.Reducer.mean(), GEOM, REDUCTION_SCALE)
.getInfo()
)
threshold1 = {k: round(v, 6) for k, v in threshold1.items()}
threshold1 = {k: round(v, 3) for k, v in threshold1.items()}

thresh1_expected = {"constant": -13.950876}
thresh1_expected = {"constant": -13.951}

threshold2 = (
hf.bmax_otsu(S1, **{**kwargs,**{"iters":3}})
.reduceRegion(ee.Reducer.mean(), GEOM, REDUCTION_SCALE)
.getInfo()
)
threshold2 = {k: round(v, 6) for k, v in threshold1.items()}
threshold2 = {k: round(v, 3) for k, v in threshold1.items()}

thresh2_expected = {"constant": -13.950876}
thresh2_expected = {"constant": -13.951}

kwargs["return_threshold"] = False
water = (
Expand Down Expand Up @@ -105,9 +105,9 @@ def test_multidim_semisupervised(self):
water_proba = multidim.reduceRegion(
ee.Reducer.mean(), GEOM, REDUCTION_SCALE
).getInfo()
water_proba = {k: round(v, 6) for k, v in water_proba.items()}
water_proba = {k: round(v, 3) for k, v in water_proba.items()}

proba_expected = {"water_proba": 0.006551}
proba_expected = {"water_proba": 0.006}

multidim = hf.multidim_semisupervised(
index_img,
Expand Down

0 comments on commit a47e0f3

Please sign in to comment.