-
Notifications
You must be signed in to change notification settings - Fork 235
Allow passing RGB xarray.DataArray images into grdimage #2590
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
Conversation
Saving 3-band xarray.DataArray images to a temporary GeoTIFF so that they can be plotted with grdimage.
Summary of changed imagesThis is an auto-generated report of images that have changed on the DVC remote
Image diff(s)Report last updated at commit 15a7207 |
| geotiff = which(fname="@earth_day_01d_p", download="c") | ||
| with rioxarray.open_rasterio(filename=geotiff) as rda: | ||
| if len(rda.band) == 1: | ||
| with rasterio.open(fp=geotiff) as src: | ||
| df_colormap = pd.DataFrame.from_dict( | ||
| data=src.colormap(1), orient="index" | ||
| ) | ||
| array = src.read() | ||
|
|
||
| red = np.vectorize(df_colormap[0].get)(array) | ||
| green = np.vectorize(df_colormap[1].get)(array) | ||
| blue = np.vectorize(df_colormap[2].get)(array) | ||
| # alpha = np.vectorize(df_colormap[3].get)(array) | ||
|
|
||
| rda.data = red | ||
| da_red = rda.astype(dtype=np.uint8).copy() | ||
| rda.data = green | ||
| da_green = rda.astype(dtype=np.uint8).copy() | ||
| rda.data = blue | ||
| da_blue = rda.astype(dtype=np.uint8).copy() | ||
|
|
||
| xr_image = xr.concat(objs=[da_red, da_green, da_blue], dim="band") | ||
| assert xr_image.sizes == {"band": 3, "y": 180, "x": 360} | ||
| return xr_image |
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.
Should replace this with the load_blue_marble function from #2235 once done. Ideally, the rioxarray.open_rasterio function would be able to load this 3-band GeoTIFF from colorinterp directly without the hacky logic here.
pygmt/src/grdimage.py
Outdated
| with GMTTempFile(suffix=".tif") as tmpfile: | ||
| if hasattr(grid, "dims") and len(grid.dims) == 3: | ||
| grid.rio.to_raster(raster_path=tmpfile.name) | ||
| _grid = tmpfile.name | ||
| else: | ||
| _grid = grid |
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.
Should maybe put this logic in lib.virtualfile_from_data, since we'll need to reuse it for grdview, grdcut, and possibly other GMT modules that work with GMT_IMAGE.
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.
Ok, created a new tempfile_from_image function at 89da916 (similar to tempfile_from_geojson). This can be replaced with lib.virtualfile_from_image once that is implemented.
Putting the temporary GeoTIFF creation logic in a dedicated tempfile_from_image helper function, so that it can be reused by other GMT modules besides grdimage. Also ensure that an ImportError is raised when the `.rio` attribute cannot be found when rioxarray is not installed.
Refactor Figure.tilemap to use the same tempfile_from_image function that generates a temporary GeoTIFF file from the 3-band xarray.DataArray images.
Various updates from upstream GMT at GenericMappingTools/gmt#6258, GenericMappingTools/gmt@9069967, GenericMappingTools/gmt#7260.
Plotting a non-uint8 dtype xarray.DataArray works in grdimage, but the results will likely be incorrect. Warning the user about the incorrect dtype, and suggest recasting to uint8 with 0-255 range, e.g. using a histogram equalization function like skimage.exposure.equalize_hist.
| if kind == "image" and data.dtype != "uint8": | ||
| msg = ( | ||
| f"Input image has dtype: {data.dtype} which is unsupported, " | ||
| "and may result in an incorrect output. Please recast image " | ||
| "to a uint8 dtype and/or scale to 0-255 range, e.g. " | ||
| "using a histogram equalization function like " | ||
| "skimage.exposure.equalize_hist." | ||
| ) | ||
| warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) |
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.
Took a look at grdhisteq, but it doesn't appear to support 3-band image inputs, only 1-band grids, so suggesting to use skimage.exposure.equalize_hist instead. Could probably raise a feature request to upstream GMT to let grdhisteq support this too?
pygmt/helpers/utils.py
Outdated
| ------- | ||
| kind : str | ||
| One of: ``'file'``, ``'grid'``, ``'matrix'``, ``'vectors'``. | ||
| One of: ``'file'``, ``'grid'``, ``image``, ``'matrix'``, ``'vectors'``. |
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.
TODO handle merge conflicts after #2493 is merged.
Co-authored-by: Dongdong Tian <[email protected]>
seisman
left a comment
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.
This PR looks good to me. Are there any more changes you want to make into this PR?
Co-authored-by: Dongdong Tian <[email protected]>
Was planning to wait for #2493 (comment) to be finalized so I can merge those changes on top of here, but I can merge this PR first too. Wanted to add some extra tests for Edit: Hmm, and there seems to be a failing test on the Docs build at https://github.com/GenericMappingTools/pygmt/actions/runs/5615992349/job/15217416041?pr=2590#step:7:725 - |
|
On second thought, let's do the unit tests for |
Partially revert a773929 to fix `AttributeError: 'DataArray' object has no attribute 'rio'` in `pygmt/src/tilemap.py`, because the reproject from EPSG:3857 to OGC:CRS84 step requires it.

Description of proposed changes
Enables
grdimageto plot 3-band RGB images stored in anxarray.DataArraywith a shape like (3, Y, X).Example usage based on https://corteva.github.io/rioxarray/stable/examples/COG.html:
produces
Implementation currently saves the 3-band
xarray.DataArrayimage to a temporary GeoTIFF before passing it togrdimage, similar to whattilemapdid in #2394pygmt/pygmt/src/tilemap.py
Lines 151 to 156 in e98efe1
Notes:
xarray.DataArrayto temporary GeoTIFF file requiresrioxarray.grdhisteqWrap grdhisteq #1433 or normalization to 0-255 viagrdmix(Wrap grdmix #1437) is appliedGMT_IMAGEis wrapped in PyGMT so that temporary files can be avoided, see Integrate with rioxarray and implement GMT_IMAGE to read RGB GeoTIFFs #1555. The unit tests in this PR should still be valid though.TODO:
rioxarraydependencygrdviewtoo?Closes #370, partially addresses #1555.
Reminders
make formatandmake checkto make sure the code follows the style guide.doc/api/index.rst.Slash Commands
You can write slash commands (
/command) in the first line of a comment to performspecific operations. Supported slash commands are:
/format: automatically format and lint the code/test-gmt-dev: run full tests on the latest GMT development version