-
Notifications
You must be signed in to change notification settings - Fork 220
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
Properly allow for either pixel or gridline registered grids #476
Conversation
Default registration is still "GMT_GRID_NODE_REG" as per upstream, but we might want to override that in some cases.
Ok, it's getting late in my timezone, but I just want to jot down some notes before bedtime. Please correct me if I'm wrong! But I think we should check Line 105 in the code block below. pygmt/pygmt/clib/conversion.py Lines 90 to 113 in 0267dd1
Specifically, the code I've commited a small change at bb7afdc, and have been testing out both import pygmt
import xarray as xr
fig = pygmt.Figure()
grid = xr.DataArray(data=[[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]])
fig.grdimage(grid=grid, region=[-2, 2, -2, 2], projection="X5c", frame=True)
fig.savefig("pixel_or_node_registration.png")
fig.show() produces: This is with the current default gridline settings you can try at https://github.com/GenericMappingTools/try-gmt. Notice how each pixel/square is centred on non-decimal coordinates (i.e. (0,0), (0,1), (0,2), (1,1), etc), and that a 3x3 grid is produced. The same figure is produced if I try using pixel registration, and I feel that it's wrong somehow but my brain can't explain it properly. |
Trying to use pixel registration as the default when converting an xarray.DataArray to a virtual GMT grid, instead of gridline registration.
Found a way to illustrate this problem: when running import pprint
import pygmt
pprint.pprint(pygmt.grdinfo("@earth_relief_60m"), width=1000) produces:
whereas grid = pygmt.datasets.earth_relief.load_earth_relief()
pprint.pprint(pygmt.grdinfo(grid)) produces
Notice how the x_min/x_max of the virtual file isn't at -180/180, ditto with y_min/y_max. Plus it thinks the grid is gridline registered, rather than pixel registered. Also, doesn't seem okay treating the grid as Cartesian instead of Geographic (but that's probably a separate issue with the virtualfile mechanism). Not sure if it's a limitation of the |
There may be a potential bug in GMT. Please follow GenericMappingTools/gmt#3502 and GenericMappingTools/gmt#3503 for upstream feedback. |
It's confirmed that there was a bug when passing a pixel-registered matrix to GMT C API. It was already fixed in GenericMappingTools/gmt#3503 and merged in to master. I can confirm that the current PR works well. Thus what we need to do is:
|
Line 1243 in 86c46f0
Changing |
Previously it thought every xarray.DataArray grid was Cartesian! So the load_earth_relief() grid passed to GMT was treated as a Cartesian virtualfile, whereas the actual `@earth_relief` file was properly read as Geographic. Co-authored-by: Dongdong Tian <[email protected]>
pygmt/clib/session.py
Outdated
@@ -1242,7 +1242,7 @@ def virtualfile_from_grid(self, grid, registration="GMT_GRID_PIXEL_REG"): | |||
gmt_grid = self.create_data( | |||
family, | |||
geometry, | |||
mode="GMT_CONTAINER_ONLY", | |||
mode="GMT_CONTAINER_ONLY|GMT_GRID_IS_GEO", |
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.
No. I didn't mean this change. Users can pass both Cartesian and geographic grids to GMT. We need to use GMT_CONTAINER_ONLY
or GMT_CONTAINER_ONLY|GMT_GRID_IS_GEO
for different grid types.
BTW, GMT_CONTAINER_ONLY
is equivalent to GMT_CONTAINER_ONLY|GMT_GRID_IS_CARTESIAN
.
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.
Ah sorry, I thought it 'knew' automatically what the grid type was. Should actually get some tests in for this.
BTW,
GMT_CONTAINER_ONLY
is equivalent toGMT_CONTAINER_ONLY|GMT_GRID_IS_CARTESIAN
.
Ok, good to know.
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.
Also need to add it as a valid_modifier.
0d61311
to
522ceba
Compare
Should be GMT_IS_OUTPUT instead of GMT_OUTPUT. Also update some docstrings that were missed in the #210 refactor PR.
…ODE_REG" This reverts commit 08edf6f.
For xarray grids read from disk, there is an 'encoding' dictionary, with a 'source' key that gives the path to the file. Running `grdinfo` on that file can give us the grid registration. This works on the earth_relief grids. For grids that are not read from disk, we still default to assuming gridline registration ("GMT_GRID_NODE_REG").
I've added an automatic gridline/pixel registration detector in 6d4ece4. It simply does |
Values go from latitude 89S to 89N. Avoids the `grdimage [INFORMATION]: 359 (of 361) inconsistent grid values at North pole.` message.
…opy" This reverts commit 5a685b2.
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.
Implementation-wise, this PR should be done. However, there are cases on Linux when a black image is returned (hard to reproduce consistently, except when running make test
), possibly because of a flaky issue with our test suite, or one that might be solved with #517? Not too sure about Windows.
We could merge this in first and try to track down the issue properly (see if it's a problem in PyGMT or upstream with the GMT C API), or wait until we are confident that the bug is isolated. I have kept the test_grdimage_over_dateline.png
image small (~10KB) in case we need to change it for any reason.
@pytest.mark.runfirst | ||
@pytest.mark.mpl_image_compare | ||
def test_grdimage_over_dateline(xrgrid): |
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.
Running test_grdimage_over_dateline
first (before the other tests) makes the tests pass on Linux (see commit 3499047 and bf03e5b). This is a hacky workaround for the flakiness I described at https://github.com/GenericMappingTools/pygmt/pull/476/files#r453429872.
inc = [abs(i) for i in inc] | ||
grid = grid.sortby(variables=list(grid.dims), ascending=True) | ||
|
||
matrix = as_c_contiguous(grid.values[::-1]) | ||
matrix = as_c_contiguous(grid[::-1].values) |
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.
Now all the tests pass. Does it mean this PR works? |
Short answer: Yes (and it has always worked for macOS). Long answer: See #476 (comment). The tests pass when I run |
@@ -29,7 +29,8 @@ test: | |||
@echo "" | |||
@cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()" | |||
@echo "" | |||
cd $(TESTDIR); pytest $(PYTEST_ARGS) $(PROJECT) |
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.
Please add some comments here, explain why we need two pytest.
@@ -51,3 +73,19 @@ def test_grdimage_fails(): | |||
fig = Figure() | |||
with pytest.raises(GMTInvalidInput): | |||
fig.grdimage(np.arange(20).reshape((4, 5))) | |||
|
|||
|
|||
@pytest.mark.runfirst |
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.
Also add a brief comment here, explain why we need "runfirst".
Ensure no gaps are plotted over the 180 degree international dateline. | ||
Specifically checking that `xrgrid.gmt.gtype = 1` sets `GMT_GRID_IS_GEO`, | ||
and that `xrgrid.gmt.registration = 0` sets `GMT_GRID_NODE_REG`. Note that | ||
there would be a gap over the dateline if a pixel registered grid is used. |
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.
Add a link to issue #375?
pygmt/tests/test_grdimage.py
Outdated
fig = Figure() | ||
assert xrgrid.gmt.registration == 0 # gridline registration | ||
xrgrid.gmt.gtype = 1 # geographic coordinate system | ||
fig.grdimage(grid=xrgrid, region="g", projection="A0/0/1c", V="d") |
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.
fig.grdimage(grid=xrgrid, region="g", projection="A0/0/1c", V="d") | |
fig.grdimage(grid=xrgrid, region="g", projection="A0/0/1c") |
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.
I'll switch it to V="i"
for now, so we still get some extra information.
Co-Authored-By: Dongdong Tian <[email protected]>
There is a pytest warning:
|
Just for completeness, although this PR does fix #375, it doesn't fix @MarkWieczorek's reported bug at #390. I tried running the example but couldn't get the cylindrical 'Q' projection to plot in xarray (bottom row), and the Mollweide 'W' projection doesn't work too (middle row). Using a NetCDF file works fine though (top row). This results are the same when ran on the code in this PR or the current master branch at f401f85, so it might be an upstream GMT 6.1 issue.
|
I once tried to debug the issue in #390, it seems the boundary conditions are slightly different for an xarray input and a netCDF input. Mostly likely a GMT bug. |
Yeah, the resampling doesn't work properly on virtualfiles perhaps. Will merge and try to track down the problem another time. |
Description of proposed changes
xarray
assumespixel
registration (aka data values represent centre of the pixel), whereas GMT defaults to usinggridline
registration (aka data values represent top left corner of pixel). PyGMT currently defaults togridline
registration, following upstream GMT, but users have no way to easily set it topixel
registration.See http://xarray.pydata.org/en/stable/plotting.html#coordinates and https://docs.generic-mapping-tools.org/latest/cookbook/options.html#grid-registration-the-r-option for context. NOAA also has a good page that talks about grid registration at https://ngdc.noaa.gov/mgg/global/gridregistration.html
This is a major bug, and may cause breaking changes for some users. Users will have to be aware of what type of registration their raster grids' are using, and unfortunately, those using
xarray
may need to set the registration manually to p (for pixel) for correctness, unless we default to usingpixel
registration in PyGMT. Alternatively, thesalem
library has an xarray accessor to represent an xarray grid as a center_grid or corner_grid, as used in weiji14/deepbedmap#150.Will be good to have some discussion on what's the best plan forward.
Fixes #375? #390? Needed for #451.
Reminders
make format
andmake check
to make sure the code follows the style guide.doc/api/index.rst
.