-
Notifications
You must be signed in to change notification settings - Fork 235
GMT_IMAGE: Implement the GMT_IMAGE.to_dataarray method for 3-band images #3128
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
Changes from 11 commits
bcf43f0
a052a1a
56a6d65
f71e79c
4cce4a2
e02b650
f3d4b1f
4390136
5f25669
a3c6c14
5888e10
798e658
3dbf2f2
6b860bf
0bf9368
7d437be
268e34e
86765e1
86f3ffa
cc28247
1e2c973
734dc28
919dc00
b1eacf1
aa4fdc9
c87a3ec
a20d8a2
926427b
c73328e
fb97daa
15b8d53
8433e78
3e3a6f3
12ef40a
f64fbb8
4f2ae48
d49afed
f70bec0
2fd13fb
9972ba1
62f0ce0
f715aee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
seisman marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| Wrapper for the GMT_IMAGE data type. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import ctypes as ctp | ||||||||||||||||||||||||||
| from typing import ClassVar | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||
| import xarray as xr | ||||||||||||||||||||||||||
| from pygmt.datatypes.header import _GMT_GRID_HEADER | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| class _GMT_IMAGE(ctp.Structure): # noqa: N801 | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| GMT image data structure. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Examples | ||||||||||||||||||||||||||
| -------- | ||||||||||||||||||||||||||
| >>> from pygmt.clib import Session | ||||||||||||||||||||||||||
| >>> import numpy as np | ||||||||||||||||||||||||||
| >>> import xarray as xr | ||||||||||||||||||||||||||
| >>> import rioxarray | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| >>> with Session() as lib: | ||||||||||||||||||||||||||
| ... with lib.virtualfile_out(kind="image") as voutimg: | ||||||||||||||||||||||||||
| ... lib.call_module("read", f"@earth_day_01d {voutimg} -Ti") | ||||||||||||||||||||||||||
| ... # Read the image from the virtual file | ||||||||||||||||||||||||||
| ... image = lib.read_virtualfile(vfname=voutimg, kind="image").contents | ||||||||||||||||||||||||||
| ... # The image header | ||||||||||||||||||||||||||
| ... header = image.header.contents | ||||||||||||||||||||||||||
| ... # Access the header properties | ||||||||||||||||||||||||||
| ... print(image.type, header.n_bands, header.n_rows, header.n_columns) | ||||||||||||||||||||||||||
| ... print(header.pad[:]) | ||||||||||||||||||||||||||
| ... # The x and y coordinates | ||||||||||||||||||||||||||
| ... x = image.x[: header.n_columns] | ||||||||||||||||||||||||||
| ... y = image.y[: header.n_rows] | ||||||||||||||||||||||||||
| ... # The data array (with paddings) | ||||||||||||||||||||||||||
| ... data = np.reshape( | ||||||||||||||||||||||||||
| ... image.data[: header.n_bands * header.mx * header.my], | ||||||||||||||||||||||||||
| ... (header.my, header.mx, header.n_bands), | ||||||||||||||||||||||||||
| ... ) | ||||||||||||||||||||||||||
| ... # The data array (without paddings) | ||||||||||||||||||||||||||
| ... pad = header.pad[:] | ||||||||||||||||||||||||||
| ... data = data[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] | ||||||||||||||||||||||||||
| ... print(data.shape) | ||||||||||||||||||||||||||
| 1 3 180 360 | ||||||||||||||||||||||||||
| [2, 2, 2, 2] | ||||||||||||||||||||||||||
| (180, 360, 3) | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| _fields_: ClassVar = [ | ||||||||||||||||||||||||||
| # Data type, e.g. GMT_FLOAT | ||||||||||||||||||||||||||
| ("type", ctp.c_int), | ||||||||||||||||||||||||||
| # Array with color lookup values | ||||||||||||||||||||||||||
| ("colormap", ctp.POINTER(ctp.c_int)), | ||||||||||||||||||||||||||
| # Number of colors in a paletted image | ||||||||||||||||||||||||||
| ("n_indexed_colors", ctp.c_int), | ||||||||||||||||||||||||||
| # Pointer to full GMT header for the image | ||||||||||||||||||||||||||
| ("header", ctp.POINTER(_GMT_GRID_HEADER)), | ||||||||||||||||||||||||||
| # Pointer to actual image | ||||||||||||||||||||||||||
| ("data", ctp.POINTER(ctp.c_ubyte)), | ||||||||||||||||||||||||||
| # Pointer to an optional transparency layer stored in a separate variable | ||||||||||||||||||||||||||
| ("alpha", ctp.POINTER(ctp.c_ubyte)), | ||||||||||||||||||||||||||
| # Color interpolation | ||||||||||||||||||||||||||
| ("color_interp", ctp.c_char_p), | ||||||||||||||||||||||||||
| # Pointer to the x-coordinate vector | ||||||||||||||||||||||||||
| ("x", ctp.POINTER(ctp.c_double)), | ||||||||||||||||||||||||||
| # Pointer to the y-coordinate vector | ||||||||||||||||||||||||||
| ("y", ctp.POINTER(ctp.c_double)), | ||||||||||||||||||||||||||
| # Book-keeping variables "hidden" from the API | ||||||||||||||||||||||||||
| ("hidden", ctp.c_void_p), | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def to_dataarray(self) -> xr.DataArray: | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| Convert a _GMT_IMAGE object to an :class:`xarray.DataArray` object. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||
| dataarray | ||||||||||||||||||||||||||
| A :class:`xarray.DataArray` object. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Examples | ||||||||||||||||||||||||||
| -------- | ||||||||||||||||||||||||||
| >>> from pygmt.clib import Session | ||||||||||||||||||||||||||
| >>> with Session() as lib: | ||||||||||||||||||||||||||
| ... with lib.virtualfile_out(kind="image") as voutimg: | ||||||||||||||||||||||||||
| ... lib.call_module("read", ["@earth_day_01d", voutimg, "-Ti"]) | ||||||||||||||||||||||||||
| ... # Read the image from the virtual file | ||||||||||||||||||||||||||
| ... image = lib.read_virtualfile(voutimg, kind="image") | ||||||||||||||||||||||||||
| ... # Convert to xarray.DataArray and use it later | ||||||||||||||||||||||||||
| ... da = image.contents.to_dataarray() | ||||||||||||||||||||||||||
| >>> da # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS | ||||||||||||||||||||||||||
| <xarray.DataArray 'z' (band: 3, y: 180, x: 360)> Size: 2MB | ||||||||||||||||||||||||||
| array([[[ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| [ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| [ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| ..., | ||||||||||||||||||||||||||
| [192, 193, 193, ..., 193, 192, 191], | ||||||||||||||||||||||||||
| [204, 206, 206, ..., 205, 206, 204], | ||||||||||||||||||||||||||
| [208, 210, 210, ..., 210, 210, 208]], | ||||||||||||||||||||||||||
| <BLANKLINE> | ||||||||||||||||||||||||||
| [[ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| [ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| [ 10, 10, 10, ..., 10, 10, 10], | ||||||||||||||||||||||||||
| ..., | ||||||||||||||||||||||||||
| [186, 187, 188, ..., 187, 186, 185], | ||||||||||||||||||||||||||
| [196, 198, 198, ..., 197, 197, 196], | ||||||||||||||||||||||||||
| [199, 201, 201, ..., 201, 202, 199]], | ||||||||||||||||||||||||||
| <BLANKLINE> | ||||||||||||||||||||||||||
| [[ 51, 51, 51, ..., 51, 51, 51], | ||||||||||||||||||||||||||
| [ 51, 51, 51, ..., 51, 51, 51], | ||||||||||||||||||||||||||
| [ 51, 51, 51, ..., 51, 51, 51], | ||||||||||||||||||||||||||
| ..., | ||||||||||||||||||||||||||
| [177, 179, 179, ..., 178, 177, 177], | ||||||||||||||||||||||||||
| [185, 187, 187, ..., 187, 186, 185], | ||||||||||||||||||||||||||
| [189, 191, 191, ..., 191, 191, 189]]]) | ||||||||||||||||||||||||||
| Coordinates: | ||||||||||||||||||||||||||
| * x (x) float64 3kB -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 | ||||||||||||||||||||||||||
| * y (y) float64 1kB 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 | ||||||||||||||||||||||||||
| * band (band) uint8 3B 0 1 2 | ||||||||||||||||||||||||||
| Attributes: | ||||||||||||||||||||||||||
| title: | ||||||||||||||||||||||||||
| history: | ||||||||||||||||||||||||||
| description: | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if self.type in { | |
| GridFormat.NB, | |
| GridFormat.NS, | |
| GridFormat.NI, | |
| GridFormat.NF, | |
| GridFormat.ND, | |
| }: # Only set the 'Conventions' attribute for netCDF. | |
| attrs["Conventions"] = "CF-1.7" | |
| attrs["title"] = self.title.decode() | |
| attrs["history"] = self.command.decode() | |
| attrs["description"] = self.remark.decode() |
(2) actual_range is the standard attribution defined by CF-1.7 convention, but it's only for netCDF files, not for images;
Same here, we can put attrs["actual_range"] under the if-block above:
pygmt/pygmt/datatypes/header.py
Line 223 in cf48764
| attrs["actual_range"] = np.array([self.z_min, self.z_max]) |
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.
Done at 62f0ce0.
Outdated
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.
The gtype for earth_day_01d should be Geographic (1), not Cartesian (0).
| >>> da.gmt.registration, da.gmt.gtype | |
| (1, 0) | |
| >>> da.gmt.registration, da.gmt.gtype | |
| (1, 1) |
according to:
$ gmt grdinfo @earth_day_01d
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Title: Grid imported via GDAL
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Command:
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Remark:
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Pixel node registration used [Geographic grid]
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Grid file format: gd = Import/export through GDAL
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: x_min: -180 x_max: 180 x_inc: 1 name: x n_columns: 360
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: y_min: -90 y_max: 90 y_inc: 1 name: y n_rows: 180
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: v_min: 1.79769313486e+308 v_max: -1.79769313486e+308 name: z
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: scale_factor: 1 add_offset: 0
~/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Default CPT: We either need to modify the logic here:
pygmt/pygmt/datatypes/header.py
Line 254 in cf48764
| gtype = 1 if dims[0] == "lat" and dims[1] == "lon" else 0 |
Or I can manually override the gtype in the load_blue_marble function which I'm trying to finish up at #2235.
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 is the function (again, not a public API function) that GMT uses to determine the grid type. The logic here is not that complicated to duplicate in Python.
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.
How easy would it be to make that C function public actually?
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.
How easy would it be to make that C function public actually?
It doesn't seem too difficult. But we still need to implement our own functions if we want to be compatible with GMT 6.4-6.5.
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.
Yeah, it'll be some time before GMT 6.6 comes out. I just want the gtype determination to be consistent between GMT and PyGMT, and the best case is to bind to the GMT C function. But we can also reimplement it in PyGMT for now (in a separate PR), maybe in header.py?
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.
Another possibility is to wrap the "hidden", private GMT_GRID_HEADER_HIDDEN structure https://github.com/GenericMappingTools/gmt/blob/7809736ba32d87a4a96b15444419eb176c6a35f3/src/gmt_hidden.h#L151. The structure has many members but we can only wrap the first few members. The members that are most useful to us are: grdtype (Cartesian or geographic), BC (boundary condition), varname (the actual variable name so that we don't have to hardcode the grid name to z), and cpt (the default CPT for this 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.
In f715aee, I've updated the logic for determining the grid/image gtype based on ProjRefPROJ4.
The logic of codes come from: https://github.com/GenericMappingTools/gmt/blob/7809736ba32d87a4a96b15444419eb176c6a35f3/src/gmt_grdio.c#L3778
seisman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
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.
The image name is currently hardcoded to z, is that ok for an RGB image?
pygmt/pygmt/datatypes/header.py
Lines 193 to 198 in ac44706
| @property | |
| def name(self) -> str: | |
| """ | |
| Name of the grid. | |
| """ | |
| return "z" |
The attrs fields might need some work. I'm getting 'actual_range': array([ 1.79769313e+308, -1.79769313e+308])} when loading the @earth_day_01d 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.
I agree they make no sense, but they're consistent with the behavior in GMT.
gmt grdinfo @earth_day_01d
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Title: Grid imported via GDAL
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Command:
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Remark:
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Pixel node registration used [Geographic grid]
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Grid file format: gd = Import/export through GDAL
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: x_min: -180 x_max: 180 x_inc: 1 name: x n_columns: 360
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: y_min: -90 y_max: 90 y_inc: 1 name: y n_rows: 180
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: v_min: 1.79769313486e+308 v_max: -1.79769313486e+308 name: z
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: scale_factor: 1 add_offset: 0
/Users/seisman/.gmt/server/earth/earth_day/earth_day_01d_p.tif: Default CPT:
+proj=longlat +R=6378137 +no_defs
The GMT's image support was likely added by Joaquim so that you may ping him for more information.
Uh oh!
There was an error while loading. Please reload this page.