Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update integral method #183

Merged
merged 2 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions geetools/ImageCollection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,65 @@ def iloc(self, index: int) -> ee.Image:
ic2018.geetools.iloc(0).getInfo()
"""
return ee.Image(self._obj.toList(self._obj.size()).get(index))

def integral(self, band: str, time: str = "system:time_start", unit: str = "") -> ee.Image:
"""Compute the integral of a band over time or a specified property.

Args:
band: the name of the band to integrate
time: the name of the property to use as time. It must be a date property of the images.
unit: the time unit use to compute the integral. It can be one of the following: ["year", "month", "day", "hour", "minute", "second"]. If non is set, the time will be normalized on the integral length.

Returns:
An Image object with the integrated band for each pixel

Examples:
.. jupyter-execute::
12rambau marked this conversation as resolved.
Show resolved Hide resolved

import ee, LDCGEETools

collection = (
ee.ImageCollection("LANDSAT/LC08/C01/T1_TOA")
.filterBounds(ee.Geometry.Point(-122.262, 37.8719))
.filterDate("2014-01-01", "2014-12-31")
)

integral = collection.ldc.integral("B1")
print(integral.getInfo())
"""
# compute the intervals along the x axis
# the GEE time is stored as a milliseconds timestamp. If the time unit is not set,
# the integral is normalized on the total time length of the time series
minTime = self._obj.aggregate_min(time)
maxTime = self._obj.aggregate_max(time)
intervals = {
"year": ee.Number(1000 * 60 * 60 * 24 * 365), # 1 year in milliseconds
"month": ee.Number(1000 * 60 * 60 * 24 * 30), # 1 month in milliseconds
"day": ee.Number(1000 * 60 * 60 * 24), # 1 day in milliseconds
"hour": ee.Number(1000 * 60 * 60), # 1 hour in milliseconds
"minute": ee.Number(1000 * 60), # 1 minute in milliseconds
"second": ee.Number(1000), # 1 second in milliseconds
"": ee.Number(maxTime).subtract(ee.Number(minTime)),
}
interval = intervals[unit]

# initialize the sum with a 0 value initial item
# all the properties of the first image of the collection are copied
first = self._obj.first()
zero = ee.Image.constant(0).copyProperties(first, first.propertyNames())
s = ee.Image(zero).rename("integral").set("last", zero)

# compute the approximation of the integral using the trapezoidal method
# each local interval is aproximated by the corresponding trapez and the
# sum is updated
def computeIntegral(image, integral):
image = ee.Image(image).select(band)
integral = ee.Image(integral)
last = ee.Image(integral.get("last"))
locMinTime = ee.Number(last.get(time))
locMaxTime = ee.Number(image.get(time))
locInterval = locMaxTime.subtract(locMinTime).divide(interval)
locIntegral = last.add(image).multiply(locInterval).divide(2)
return integral.add(locIntegral).set("last", image)

return ee.Image(self._obj.iterate(computeIntegral, s))
36 changes: 4 additions & 32 deletions geetools/tools/_deprecated_imagecollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,38 +1074,10 @@
return masksum.divide(totalsum).multiply(100).toInt()


def area_under_curve(collection, band, x_property=None, name="area_under"):
"""Compute the area under the curve taking the x axis from an image.

property. If not specified, it'll use `system:time_start`
.
"""
x_property = x_property or "system:time_start"
max_x = collection.aggregate_max(x_property)
min_x = collection.aggregate_min(x_property)
total_lapsed = ee.Number(max_x).subtract(ee.Number(min_x))

def cumulative(image, cumm):
cumm = ee.List(cumm)

def true(i, c):
c = ee.List(c)
last = ee.Image(c.get(-1))
lapsed = ee.Number(image.get(x_property)).subtract(ee.Number(last.get(x_property)))
lapsed_percent = lapsed.divide(total_lapsed)
rise = i.select(band).subtract(last.select(band)).divide(2)
toadd = i.select(band).add(rise).multiply(lapsed_percent).rename(name).toFloat()
return c.add(i.addBands(toadd))

def false(i, c):
toadd = i.addBands(ee.Image(0).rename(name).toFloat())
return c.add(toadd)

return ee.List(ee.Algorithms.If(cumm.size(), true(image, cumm), false(image, cumm)))

final = ee.List(collection.iterate(cumulative, ee.List([])))
final_ic = ee.ImageCollection.fromImages(final).select(name)
return ee.Image(final_ic.reduce(ee.Reducer.sum()))
@deprecated(version="1.0.0", reason="Use ee.ImageCollection.geetools.integral instead")
def area_under_curve(collection, band, x_property="system:time_start", name="under_curve"):
"""Compute the area under the curve taking the x axis from an image property."""
return ee.ImageCollection(collection).geetools.integral(band, x_property).rename(name)

Check warning on line 1080 in geetools/tools/_deprecated_imagecollection.py

View check run for this annotation

Codecov / codecov/patch

geetools/tools/_deprecated_imagecollection.py#L1077-L1080

Added lines #L1077 - L1080 were not covered by tests


def moving_average(collection, back=5, reducer=None, use_original=True):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_ImageCollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,20 @@ def test_deprecated_get_image(self, s2_sr, data_regression):
image = geetools.imagecollection.getImage(s2_sr, 0).subtract(s2_sr.first())
ic = ee.ImageCollection([image])
data_regression.check(reduce(ic).getInfo())


class TestIntegral:
"""Test the ``integral`` method."""

def test_integral(self, s2_sr, amazonas, data_regression):
integral = s2_sr.limit(10).geetools.integral("B4").select("integral")
ic = ee.ImageCollection([integral])
data_regression.check(reduce(ic, amazonas).getInfo())

def test_deprecated_integral(self, s2_sr, amazonas, data_regression):
with pytest.deprecated_call():
integral = geetools.imagecollection.area_under_curve(s2_sr.limit(10), "B4").select(
"under_curve"
)
ic = ee.ImageCollection([integral])
data_regression.check(reduce(ic, amazonas).getInfo())
1 change: 1 addition & 0 deletions tests/test_ImageCollection/test_deprecated_integral.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
under_curve: null
1 change: 1 addition & 0 deletions tests/test_ImageCollection/test_integral.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
integral: null
Loading