generated from carbonplan/python-project-template
-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Feature/pyramid reproject map blocks #12
Closed
Closed
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
0d1aaec
factor pyramid_reproject into its own module
4b3543d
[wip] update pyramid reproject to use map_blocks
4927891
fix pytest invocation in gha
aa49e99
updated implementation of make_grid_ds
bbddf14
fix numpy test
7e7b2cd
fix xy ordering
dffcd88
fix test comparison
8115415
update to latest datatree syntax
0cf2e60
update test env
806013e
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] 9887f2e
Bump actions/checkout from 2.4.0 to 3 (#19)
dependabot[bot] 682a6bb
Fix CI: use micromamba & gh action concurrency feature (#21)
andersy005 50df185
Update demo notebook (#20)
andersy005 ac2abf1
Update demo.ipynb (#22)
andersy005 1408888
Update pre-commit hooks (#23)
andersy005 787903a
factor pyramid_reproject into its own module
33d8b67
[wip] update pyramid reproject to use map_blocks
92f0ae7
enable pyupgrade
andersy005 a057963
set periodic=True as default in regridders (#16)
7f681ce
[wip] update pyramid reproject to use map_blocks
45d2ed4
Merge branch 'main' into feature/pyramid-reproject-map-blocks
andersy005 8646539
Merge branch 'main' into feature/pyramid-reproject-map-blocks
andersy005 dcf2e2d
Remove unused imports
andersy005 48cfee0
Merge branch 'main' into feature/pyramid-reproject-map-blocks
andersy005 5107983
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
from __future__ import annotations # noqa: F401 | ||
|
||
from collections import defaultdict | ||
from typing import Dict, TypeVar | ||
|
||
import dask | ||
import datatree as dt | ||
import numpy as np | ||
import xarray as xr | ||
|
||
from .utils import get_version, multiscales_template | ||
|
||
ResamplingType = TypeVar('ResamplingType', str, Dict[str, str]) | ||
|
||
|
||
def _add_x_y_coords(da: xr.DataArray, shape: tuple[int], transform) -> xr.DataArray: | ||
'''helper function to add x/y coordinates to xr.DataArray''' | ||
|
||
bounds_shape = tuple(s + 1 for s in shape) | ||
|
||
xs = np.empty(shape) | ||
ys = np.empty(shape) | ||
for i in range(bounds_shape[0]): | ||
for j in range(bounds_shape[1]): | ||
if i < shape[0] and j < shape[1]: | ||
x, y = transform * [j + 0.5, i + 0.5] | ||
xs[i, j] = x | ||
ys[i, j] = y | ||
|
||
da = da.assign_coords( | ||
{'x': xr.DataArray(xs[0, :], dims=['x']), 'y': xr.DataArray(ys[:, 0], dims=['y'])} | ||
) | ||
|
||
return da | ||
|
||
|
||
def _make_template(shape: tuple[int], dst_transform, attrs: dict) -> xr.DataArray: | ||
'''helper function to make a xr.DataArray template''' | ||
|
||
template = xr.DataArray( | ||
data=dask.array.empty(shape, chunks=shape), dims=('y', 'x'), attrs=attrs | ||
) | ||
template = _add_x_y_coords(template, shape, dst_transform) | ||
template.coords['spatial_ref'] = xr.DataArray(np.array(1.0)) | ||
return template | ||
|
||
|
||
def _reproject(da: xr.DataArray, shape=None, dst_transform=None, resampling='average'): | ||
'''helper function to reproject xr.DataArray objects''' | ||
from rasterio.warp import Resampling | ||
|
||
return da.rio.reproject( | ||
'EPSG:3857', | ||
resampling=Resampling[resampling], | ||
shape=shape, | ||
transform=dst_transform, | ||
) | ||
|
||
|
||
def pyramid_reproject( | ||
ds, | ||
levels: int = None, | ||
pixels_per_tile=128, | ||
resampling: ResamplingType = 'average', | ||
) -> dt.DataTree: | ||
"""[summary] | ||
|
||
Parameters | ||
---------- | ||
ds : xr.Dataset | ||
Input dataset | ||
levels : int, optional | ||
Number of levels in pyramid, by default None | ||
pixels_per_tile : int, optional | ||
Number of pixels to include along each axis in individual tiles, by default 128 | ||
resampling : str or dict, optional | ||
Rasterio resampling method. Can be provided as a string or a per-variable | ||
dict, by default 'average' | ||
|
||
Returns | ||
------- | ||
dt.DataTree | ||
Multiscale data pyramid | ||
""" | ||
import rioxarray # noqa: F401 | ||
from rasterio.transform import Affine | ||
|
||
# multiscales spec | ||
save_kwargs = {'levels': levels, 'pixels_per_tile': pixels_per_tile} | ||
attrs = { | ||
'multiscales': multiscales_template( | ||
datasets=[{'path': str(i)} for i in range(levels)], | ||
type='reduce', | ||
method='pyramid_reproject', | ||
version=get_version(), | ||
kwargs=save_kwargs, | ||
) | ||
} | ||
|
||
resampling_dict: ResamplingType | ||
if isinstance(resampling, str): | ||
resampling_dict = defaultdict(lambda: resampling) | ||
else: | ||
resampling_dict = resampling | ||
|
||
# set up pyramid | ||
root = xr.Dataset(attrs=attrs) | ||
pyramid = dt.DataTree(data_objects={'root': root}) | ||
|
||
for level in range(levels): | ||
lkey = str(level) | ||
dim = 2**level * pixels_per_tile | ||
|
||
dst_transform = Affine.translation(-20026376.39, 20048966.10) * Affine.scale( | ||
(20026376.39 * 2) / dim, -(20048966.10 * 2) / dim | ||
) | ||
|
||
pyramid[lkey] = xr.Dataset(attrs=ds.attrs) | ||
shape = (dim, dim) | ||
chunked_dim_sizes = () | ||
for k, da in ds.items(): | ||
template_shape = (chunked_dim_sizes) + shape # TODO: pick up here. | ||
template = _make_template(template_shape, dst_transform, ds[k].attrs) | ||
print(resampling_dict[k]) | ||
pyramid[lkey].ds[k] = xr.map_blocks( | ||
_reproject, | ||
da, | ||
kwargs=dict( | ||
shape=(dim, dim), dst_transform=dst_transform, resampling=resampling_dict[k] | ||
), | ||
template=template, | ||
) | ||
|
||
return pyramid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is
chunked_dim_sizes
supposed to come from?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. Clearly I didn't finish this part.
If I recall correctly, this is mean to support cases where non-spatial dims are chunked. The most common examples would be chunks along the
band
ortime
dimensions.To do this, we need to construct a template that includes the correct shape for each block.