Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ce44981
work on integrating temporal resampling
TonioF Sep 17, 2021
666633f
raise error when temporal resampling is requested
TonioF Sep 20, 2021
367f0c2
fix
TonioF Sep 30, 2021
b118f90
integrated temporal resampling
TonioF Oct 4, 2021
ef6a2a3
edited list of supported resampling methods
TonioF Oct 7, 2021
99f9551
introduced temporal resampling
TonioF Oct 7, 2021
6e9ca51
improved temporal subsetting
TonioF Oct 7, 2021
6943c2a
improved setting of bounds
TonioF Oct 7, 2021
3649eb3
adjust upsampled bounds with numpy timedelta correctly
TonioF Oct 8, 2021
8d8d0f2
Merge branch 'master' into toniof-523-allow-temporal-resampling
TonioF Oct 8, 2021
b441a58
edited downsampling methods
TonioF Oct 8, 2021
00eb10f
edited changelog
TonioF Oct 8, 2021
2dd3531
Merge branch 'master' into toniof-523-allow-temporal-resampling
TonioF Oct 11, 2021
6b76be5
resample to center of month
TonioF Oct 13, 2021
89e26bd
avoid use of outdated method
TonioF Oct 13, 2021
8b9a1aa
Merge branch 'master' into toniof-523-allow-temporal-resampling
TonioF Oct 14, 2021
3161ab2
pep 8
TonioF Oct 14, 2021
4177a4d
work on better considering up- and downsampling
TonioF Oct 14, 2021
19907c2
remodeled temporal resampling method parameter
TonioF Oct 15, 2021
9dca667
pass interpolation kind and percentile threshold as additional method…
TonioF Oct 18, 2021
bffe1b3
added error handling
TonioF Oct 18, 2021
a2d629b
edited changelog
TonioF Oct 18, 2021
efbe060
avoid use of deprectated methods
TonioF Oct 18, 2021
5f2a6e5
Merge branch 'master' into toniof-523-allow-temporal-resampling
TonioF Jan 10, 2022
95a3394
added tests
TonioF Jan 11, 2022
e12b348
allow passing in method name without parameters
TonioF Jan 11, 2022
e01c893
added empty line
TonioF Jan 11, 2022
c098f6f
do not pass cube config in constructor
TonioF Jan 11, 2022
15e6cf0
temporal resampling is done purely based on datasetresample and dataa…
TonioF Jan 14, 2022
a551b3c
updated changelog
TonioF Jan 14, 2022
2e559b0
test fix
TonioF Jan 14, 2022
b45051a
test fix
TonioF Jan 14, 2022
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
22 changes: 21 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,27 @@

* Changed the `xcube gen` tool to extract metadata for pre-sorting inputs
from other than NetCDF inputs, e.g. GeoTIFF.

* Cube generator `xcube gen2` allows to use temporal resampling. To use it,
a user must set the parameter `time_period` (in a pandas-interpretable
pattern, e.g., '4D') and the newly introduced parameter `temporal_resampling`,
to which a dictionary with entries for upsampling and/or downsampling can be
passed. Upsampling and downsampling can be used with or without parameters,
depending on the selected method. To sample down to a broader temporal
resolution, you need to specify a downsampling method (any of `['count',
'first', 'last', 'min', 'max', 'sum', 'prod', 'mean', 'median', 'std', 'var',
'percentile']`). If you also want to add parameters, you can pass a tuple
consisting of the method name and a dictionary with parameters. Analogously,
you can sample up to a finer temporal resolution using any of `['asfreq',
'ffill', 'bfill', 'pad', 'nearest', 'interpolate']`.
Example:
```python
temporal_resampling=dict(
downsampling=('percentile', {'threshold': 75}),
upsampling='pad'
),
```
(#523)

## Changes in 0.9.2

### Fixes
Expand Down
293 changes: 293 additions & 0 deletions test/core/gen2/local/test_resamplert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
from xcube.core.new import new_cube
from xcube.core.gen2 import CubeConfig
from xcube.core.gen2.local.resamplert import CubeResamplerT
from xcube.core.gridmapping import GridMapping

import cftime
import numpy as np
import unittest


class CubeResamplerTTest(unittest.TestCase):

@staticmethod
def _get_cube(time_freq: str, time_periods: int, use_cftime: bool = False):

def b3(index1, index2, index3):
return index1 + index2 * 0.1 + index3 * 0.01

return new_cube(variables=dict(B03=b3),
time_periods=time_periods,
time_freq=time_freq,
use_cftime=use_cftime,
time_dtype='datetime64[s]' if not use_cftime else None,
width=10, height=5, time_start='2010-08-04')

def test_transform_cube_no_time_period(self):
cube_config = CubeConfig(time_range=('2010-01-01', '2012-12-31'))
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='M', time_periods=12)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertEqual(cube, resampled_cube)

def test_transform_cube_downsample_to_years(self):
cube_config = CubeConfig(time_range=('2010-01-01', '2014-12-31'),
time_period='2Y',
temporal_resampling=dict(
downsampling=('min', {}))
)
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='M', time_periods=24)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2011-01-01T00:00:00', '2013-01-01T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2010-01-01T00:00:00', '2012-01-01T00:00:00'],
['2012-01-01T00:00:00', '2014-01-01T00:00:00']],
dtype=np.datetime64))
self.assertEqual((2, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.0, resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(16.0, resampled_cube.B03[1].values.min(), 8)

def test_transform_cube_downsample_to_months(self):
cube_config = CubeConfig(time_range=('2010-08-01', '2010-11-30'),
time_period='2M',
temporal_resampling=dict(
downsampling=('min', {}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to be more pythonic here and allow for the no-args case also

Suggested change
downsampling=('min', {}))
downsampling='min'

)
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='W', time_periods=12)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2010-09-01T00:00:00', '2010-11-01T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2010-08-01T00:00:00', '2010-10-01T00:00:00'],
['2010-10-01T00:00:00', '2010-12-01T00:00:00']],
dtype=np.datetime64))
self.assertEqual((2, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.0, resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(8.0, resampled_cube.B03[1].values.min(), 8)

def test_transform_cube_downsample_to_weeks(self):
cube_config = CubeConfig(time_range=('2010-08-03', '2010-09-10'),
time_period='2W',
temporal_resampling=dict(
downsampling=('max', {}))
)
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='D', time_periods=32)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2010-08-08T00:00:00', '2010-08-22T00:00:00',
'2010-09-05T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2010-08-01T00:00:00', '2010-08-15T00:00:00'],
['2010-08-15T00:00:00', '2010-08-29T00:00:00'],
['2010-08-29T00:00:00', '2010-09-12T00:00:00']],
dtype=np.datetime64))
self.assertEqual((3, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(10.0, resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(24.0, resampled_cube.B03[1].values.min(), 8)
self.assertAlmostEqual(31.0, resampled_cube.B03[2].values.min(), 8)

def test_transform_cube_upsample_to_months(self):
cube_config = CubeConfig(time_range=('2011-10-01', '2012-03-31'),
time_period='2M',
temporal_resampling=dict(
upsampling=('interpolate',
{'kind': 'linear'})
))
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='Y', time_periods=2)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2011-11-01T00:00:00', '2012-01-01T00:00:00',
'2012-03-01T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2011-10-01T00:00:00', '2011-12-01T00:00:00'],
['2011-12-01T00:00:00', '2012-02-01T00:00:00'],
['2012-02-01T00:00:00', '2012-04-01T00:00:00']],
dtype=np.datetime64))
self.assertEqual((3, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.33561644,
resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(0.50273973,
resampled_cube.B03[1].values.min(), 8)
self.assertAlmostEqual(0.66712329,
resampled_cube.B03[2].values.min(), 8)

def test_transform_cube_upsample_to_weeks(self):
cube_config = CubeConfig(time_range=('2010-09-01', '2010-10-10'),
time_period='4W',
temporal_resampling=dict(
upsampling=('nearest', {}))
)
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='M', time_periods=4)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2010-09-12T00:00:00', '2010-10-10T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2010-08-29T00:00:00', '2010-09-26T00:00:00'],
['2010-09-26T00:00:00', '2010-10-24T00:00:00']],
dtype=np.datetime64))
self.assertEqual((2, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.0, resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(1.0, resampled_cube.B03[1].values.min(), 8)

def test_transform_cube_upsample_to_days(self):
cube_config = CubeConfig(time_range=('2010-08-14', '2010-08-24'),
time_period='2D',
temporal_resampling=dict(
upsampling=('interpolate',
{'kind': 'linear'})
))
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='W', time_periods=3)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
np.array(['2010-08-15T00:00:00', '2010-08-17T00:00:00',
'2010-08-19T00:00:00', '2010-08-21T00:00:00',
'2010-08-23T00:00:00'],
dtype=np.datetime64))
np.testing.assert_equal(
resampled_cube.time_bnds.values,
np.array([['2010-08-14T00:00:00', '2010-08-16T00:00:00'],
['2010-08-16T00:00:00', '2010-08-18T00:00:00'],
['2010-08-18T00:00:00', '2010-08-20T00:00:00'],
['2010-08-20T00:00:00', '2010-08-22T00:00:00'],
['2010-08-22T00:00:00', '2010-08-24T00:00:00']],
dtype=np.datetime64))
self.assertEqual((5, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.5,
resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(0.78571429,
resampled_cube.B03[1].values.min(), 8)
self.assertAlmostEqual(1.07142857,
resampled_cube.B03[2].values.min(), 8)
self.assertAlmostEqual(1.35714286,
resampled_cube.B03[3].values.min(), 8)
self.assertAlmostEqual(1.64285714,
resampled_cube.B03[4].values.min(), 8)

def test_transform_cube_downsample_to_years_cftimes(self):
cube_config = CubeConfig(time_range=('2010-01-01', '2014-12-31'),
time_period='2Y',
temporal_resampling=dict(
downsampling=('min', {}))
)
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='M', time_periods=24, use_cftime=True)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(resampled_cube.time.values,
[cftime.DatetimeProlepticGregorian(2011, 1, 1),
cftime.DatetimeProlepticGregorian(2013, 1, 1)])
np.testing.assert_equal(
resampled_cube.time_bnds.values,
[[cftime.DatetimeProlepticGregorian(2010, 1, 1),
cftime.DatetimeProlepticGregorian(2012, 1, 1)],
[cftime.DatetimeProlepticGregorian(2012, 1, 1),
cftime.DatetimeProlepticGregorian(2014, 1, 1)]])
self.assertEqual((2, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.0, resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(16.0, resampled_cube.B03[1].values.min(), 8)

def test_transform_cube_upsample_to_months_cftimes(self):
cube_config = CubeConfig(time_range=('2011-10-01', '2012-03-31'),
time_period='2M',
temporal_resampling=dict(
upsampling=('interpolate',
{'kind': 'linear'})
))
temporal_resampler = CubeResamplerT()

cube = self._get_cube(time_freq='Y', time_periods=2, use_cftime=True)

resampled_cube, grid_mapping, cube_config = temporal_resampler.\
transform_cube(cube,
GridMapping.from_dataset(cube),
cube_config)
self.assertIsNotNone(resampled_cube)
np.testing.assert_equal(
resampled_cube.time.values,
[cftime.DatetimeProlepticGregorian(2011, 11, 1),
cftime.DatetimeProlepticGregorian(2012, 1, 1),
cftime.DatetimeProlepticGregorian(2012, 3, 1)])
np.testing.assert_equal(
resampled_cube.time_bnds.values,
[[cftime.DatetimeProlepticGregorian(2011, 10, 1),
cftime.DatetimeProlepticGregorian(2011, 12, 1)],
[cftime.DatetimeProlepticGregorian(2011, 12, 1),
cftime.DatetimeProlepticGregorian(2012, 2, 1)],
[cftime.DatetimeProlepticGregorian(2012, 2, 1),
cftime.DatetimeProlepticGregorian(2012, 4, 1)]])
self.assertEqual((3, 5, 10), resampled_cube.B03.shape)
self.assertAlmostEqual(0.33561644,
resampled_cube.B03[0].values.min(), 8)
self.assertAlmostEqual(0.50273973,
resampled_cube.B03[1].values.min(), 8)
self.assertAlmostEqual(0.66712329,
resampled_cube.B03[2].values.min(), 8)
9 changes: 9 additions & 0 deletions test/core/gen2/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ def test_from_dict(self):
spatial_res=0.05,
time_range=['2018-01-01', None],
time_period='4D',
temporal_resampling=dict(
upsampling=('interpolate', {'kind': 'slinear'})
),
metadata=dict(title='S2L2A subset'),
variable_metadata=dict(
B03=dict(long_name='Band 3'),
Expand All @@ -69,6 +72,8 @@ def test_from_dict(self):
self.assertEqual(0.05, cube_config.spatial_res)
self.assertEqual(('2018-01-01', None), cube_config.time_range)
self.assertEqual('4D', cube_config.time_period)
self.assertEqual(dict(upsampling=('interpolate', {'kind': 'slinear'})),
cube_config.temporal_resampling)
self.assertEqual(dict(title='S2L2A subset'),
cube_config.metadata)
self.assertEqual(
Expand All @@ -86,6 +91,10 @@ def test_to_dict(self):
spatial_res=0.05,
time_range=['2018-01-01', None],
time_period='4D',
temporal_resampling=dict(
downsampling=('percentile', {'threshold': 75}),
upsampling='pad'
),
metadata=dict(title='S2L2A subset'),
variable_metadata=dict(
B03=dict(long_name='Band 3'),
Expand Down
14 changes: 12 additions & 2 deletions test/core/gen2/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ def test_from_dict(self):
bbox=[12.2, 52.1, 13.9, 54.8],
spatial_res=0.05,
time_range=['2018-01-01', None],
time_period='4D'),
time_period='4D',
temporal_resampling=dict(
upsampling=('interpolate',
{'kind': 'slinear'}))
),
output_config=dict(store_id='memory',
data_id='CHL')
)
Expand All @@ -69,6 +73,8 @@ def test_from_dict(self):
self.assertEqual(0.05, gen_config.cube_config.spatial_res)
self.assertEqual(('2018-01-01', None), gen_config.cube_config.time_range)
self.assertEqual('4D', gen_config.cube_config.time_period)
self.assertEqual(dict(upsampling=('interpolate', {'kind': 'slinear'})),
gen_config.cube_config.temporal_resampling)

def test_to_dict(self):
expected_dict = dict(
Expand All @@ -79,7 +85,11 @@ def test_to_dict(self):
bbox=[12.2, 52.1, 13.9, 54.8],
spatial_res=0.05,
time_range=['2018-01-01', None],
time_period='4D'),
time_period='4D',
temporal_resampling=dict(
downsampling=('percentile', {'threshold': 70}),
upsampling='pad'
)),
output_config=dict(store_id='memory',
replace=False,
data_id='CHL')
Expand Down
Loading