Skip to content

Commit

Permalink
refactor: update integral method
Browse files Browse the repository at this point in the history
  • Loading branch information
12rambau authored Jan 29, 2024
1 parent aab45d0 commit 3a56f16
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 32 deletions.
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::
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 @@ def wrap(i):
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

0 comments on commit 3a56f16

Please sign in to comment.