Repository navigation
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 13 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,182 @@ | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
seisman marked this conversation as 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 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| >>> 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: | ||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe don't add these attributes if they're empty?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These come from the header no? For GeoTIFF files, they don't make sense since GMT is not parsing the TIFF tags, but the title/history/description might show up for 3-band NetCDF files (if those can be passed into this
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If those are only needed for CF-1.7, then maybe we can indent the 3 pygmt/pygmt/datatypes/header.py Lines 207 to 217 in cf48764
Same here, we can put pygmt/pygmt/datatypes/header.py Line 223 in cf48764
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done at 62f0ce0. |
||||||||||||||||||||||||||
| long_name: z | ||||||||||||||||||||||||||
| actual_range: [ 1.79769313e+308 -1.79769313e+308] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| >>> da.coords["x"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS | ||||||||||||||||||||||||||
| <xarray.DataArray 'x' (x: 360)> Size: 3kB | ||||||||||||||||||||||||||
| array([-179.5, -178.5, -177.5, ..., 177.5, 178.5, 179.5]) | ||||||||||||||||||||||||||
| Coordinates: | ||||||||||||||||||||||||||
| * x (x) float64 3kB -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| >>> da.coords["y"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS | ||||||||||||||||||||||||||
| <xarray.DataArray 'y' (y: 180)> Size: 1kB | ||||||||||||||||||||||||||
| array([ 89.5, 88.5, 87.5, 86.5, 85.5, 84.5, 83.5, 82.5, 81.5, 80.5, | ||||||||||||||||||||||||||
| 79.5, 78.5, 77.5, 76.5, 75.5, 74.5, 73.5, 72.5, 71.5, 70.5, | ||||||||||||||||||||||||||
| 69.5, 68.5, 67.5, 66.5, 65.5, 64.5, 63.5, 62.5, 61.5, 60.5, | ||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||
| -0.5, -1.5, -2.5, -3.5, -4.5, -5.5, -6.5, -7.5, -8.5, -9.5, | ||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||
| -60.5, -61.5, -62.5, -63.5, -64.5, -65.5, -66.5, -67.5, -68.5, -69.5, | ||||||||||||||||||||||||||
| -70.5, -71.5, -72.5, -73.5, -74.5, -75.5, -76.5, -77.5, -78.5, -79.5, | ||||||||||||||||||||||||||
| -80.5, -81.5, -82.5, -83.5, -84.5, -85.5, -86.5, -87.5, -88.5, -89.5]) | ||||||||||||||||||||||||||
| Coordinates: | ||||||||||||||||||||||||||
| * y (y) float64 1kB 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| >>> da.gmt.registration, da.gmt.gtype | ||||||||||||||||||||||||||
| (1, 0) | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The gtype for
Suggested change
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
Or I can manually override the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How easy would it be to make that C function public actually?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another possibility is to wrap the "hidden", private
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 The logic of codes come from: https://github.com/GenericMappingTools/gmt/blob/7809736ba32d87a4a96b15444419eb176c6a35f3/src/gmt_grdio.c#L3778 |
||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Get image header | ||||||||||||||||||||||||||
| header: _GMT_GRID_HEADER = self.header.contents | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Get DataArray without padding | ||||||||||||||||||||||||||
| pad = header.pad[:] | ||||||||||||||||||||||||||
| data: np.ndarray = np.reshape( | ||||||||||||||||||||||||||
| a=self.data[: header.n_bands * header.mx * header.my], | ||||||||||||||||||||||||||
|
seisman marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||
| newshape=(header.my, header.mx, header.n_bands), | ||||||||||||||||||||||||||
| )[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Get x and y coordinates | ||||||||||||||||||||||||||
| coords: dict[str, list | np.ndarray] = { | ||||||||||||||||||||||||||
| "x": self.x[: header.n_columns], | ||||||||||||||||||||||||||
| "y": self.y[: header.n_rows], | ||||||||||||||||||||||||||
| "band": np.array([0, 1, 2], dtype=np.uint8), | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Create the xarray.DataArray object | ||||||||||||||||||||||||||
| image = xr.DataArray( | ||||||||||||||||||||||||||
| data=data, | ||||||||||||||||||||||||||
| coords=coords, | ||||||||||||||||||||||||||
| dims=("y", "x", "band"), | ||||||||||||||||||||||||||
| name=header.name, | ||||||||||||||||||||||||||
| attrs=header.data_attrs, | ||||||||||||||||||||||||||
|
Comment on lines
+204
to
+205
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The image pygmt/pygmt/datatypes/header.py Lines 193 to 198 in ac44706
The
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. The GMT's image support was likely added by Joaquim so that you may ping him for more information. |
||||||||||||||||||||||||||
| ).transpose("band", "y", "x") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Set GMT accessors. | ||||||||||||||||||||||||||
| # Must put at the end, otherwise info gets lost after certain image operations. | ||||||||||||||||||||||||||
| image.gmt.registration = header.registration | ||||||||||||||||||||||||||
| image.gmt.gtype = header.gtype | ||||||||||||||||||||||||||
| return image | ||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.