|
5 | 5 | import ctypes as ctp |
6 | 6 | from typing import ClassVar |
7 | 7 |
|
8 | | -from pygmt.datatypes.grid import _GMT_GRID_HEADER |
| 8 | +import numpy as np |
| 9 | +import xarray as xr |
| 10 | +from pygmt.datatypes.header import _GMT_GRID_HEADER |
9 | 11 |
|
10 | 12 |
|
11 | 13 | class _GMT_IMAGE(ctp.Structure): # noqa: N801 |
@@ -63,6 +65,8 @@ class _GMT_IMAGE(ctp.Structure): # noqa: N801 |
63 | 65 | array([-179.5, -178.5, ..., 178.5, 179.5]) |
64 | 66 | >>> y # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
65 | 67 | array([ 89.5, 88.5, ..., -88.5, -89.5]) |
| 68 | + >>> data.dtype |
| 69 | + dtype('uint8') |
66 | 70 | >>> data.shape |
67 | 71 | (180, 360, 3) |
68 | 72 | >>> data.min(), data.max() |
@@ -91,3 +95,118 @@ class _GMT_IMAGE(ctp.Structure): # noqa: N801 |
91 | 95 | # Book-keeping variables "hidden" from the API |
92 | 96 | ("hidden", ctp.c_void_p), |
93 | 97 | ] |
| 98 | + |
| 99 | + def to_dataarray(self) -> xr.DataArray: |
| 100 | + """ |
| 101 | + Convert a _GMT_IMAGE object to an :class:`xarray.DataArray` object. |
| 102 | +
|
| 103 | + Returns |
| 104 | + ------- |
| 105 | + dataarray |
| 106 | + A :class:`xarray.DataArray` object. |
| 107 | +
|
| 108 | + Examples |
| 109 | + -------- |
| 110 | + >>> from pygmt.clib import Session |
| 111 | + >>> with Session() as lib: |
| 112 | + ... with lib.virtualfile_out(kind="image") as voutimg: |
| 113 | + ... lib.call_module("read", ["@earth_day_01d_p", voutimg, "-Ti"]) |
| 114 | + ... # Read the image from the virtual file |
| 115 | + ... image = lib.read_virtualfile(voutimg, kind="image") |
| 116 | + ... # Convert to xarray.DataArray and use it later |
| 117 | + ... da = image.contents.to_dataarray() |
| 118 | + >>> da # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 119 | + <xarray.DataArray 'z' (band: 3, y: 180, x: 360)>... |
| 120 | + array([[[ 10, 10, 10, ..., 10, 10, 10], |
| 121 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 122 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 123 | + ..., |
| 124 | + [192, 193, 193, ..., 193, 192, 191], |
| 125 | + [204, 206, 206, ..., 205, 206, 204], |
| 126 | + [208, 210, 210, ..., 210, 210, 208]], |
| 127 | + <BLANKLINE> |
| 128 | + [[ 10, 10, 10, ..., 10, 10, 10], |
| 129 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 130 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 131 | + ..., |
| 132 | + [186, 187, 188, ..., 187, 186, 185], |
| 133 | + [196, 198, 198, ..., 197, 197, 196], |
| 134 | + [199, 201, 201, ..., 201, 202, 199]], |
| 135 | + <BLANKLINE> |
| 136 | + [[ 51, 51, 51, ..., 51, 51, 51], |
| 137 | + [ 51, 51, 51, ..., 51, 51, 51], |
| 138 | + [ 51, 51, 51, ..., 51, 51, 51], |
| 139 | + ..., |
| 140 | + [177, 179, 179, ..., 178, 177, 177], |
| 141 | + [185, 187, 187, ..., 187, 186, 185], |
| 142 | + [189, 191, 191, ..., 191, 191, 189]]], dtype=uint8) |
| 143 | + Coordinates: |
| 144 | + * y (y) float64... 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 |
| 145 | + * x (x) float64... -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 |
| 146 | + * band (band) uint8... 1 2 3 |
| 147 | + Attributes: |
| 148 | + long_name: z |
| 149 | +
|
| 150 | + >>> da.coords["x"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 151 | + <xarray.DataArray 'x' (x: 360)>... |
| 152 | + array([-179.5, -178.5, -177.5, ..., 177.5, 178.5, 179.5]) |
| 153 | + Coordinates: |
| 154 | + * x (x) float64... -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 |
| 155 | + Attributes: |
| 156 | + long_name: x |
| 157 | + axis: X |
| 158 | + actual_range: [-180. 180.] |
| 159 | + >>> da.coords["y"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 160 | + <xarray.DataArray 'y' (y: 180)>... |
| 161 | + array([ 89.5, 88.5, 87.5, 86.5, ..., -87.5, -88.5, -89.5]) |
| 162 | + Coordinates: |
| 163 | + * y (y) float64... 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 |
| 164 | + Attributes: |
| 165 | + long_name: y |
| 166 | + axis: Y |
| 167 | + actual_range: [-90. 90.] |
| 168 | + >>> da.gmt.registration, da.gmt.gtype |
| 169 | + (1, 1) |
| 170 | + """ |
| 171 | + # The image header |
| 172 | + header = self.header.contents |
| 173 | + |
| 174 | + if header.n_bands != 3: |
| 175 | + msg = ( |
| 176 | + f"The raster image has {header.n_bands} band(s). " |
| 177 | + "Currently only 3-band images are supported. " |
| 178 | + "Please consider submitting a feature request to us. " |
| 179 | + ) |
| 180 | + raise NotImplementedError(msg) |
| 181 | + |
| 182 | + # Get dimensions and their attributes from the header. |
| 183 | + dims, dim_attrs = header.dims, header.dim_attrs |
| 184 | + # The coordinates, given as a tuple of the form (dims, data, attrs) |
| 185 | + x = np.ctypeslib.as_array(self.x, shape=(header.n_columns,)).copy() |
| 186 | + y = np.ctypeslib.as_array(self.y, shape=(header.n_rows,)).copy() |
| 187 | + coords = [ |
| 188 | + (dims[0], y, dim_attrs[0]), |
| 189 | + (dims[1], x, dim_attrs[1]), |
| 190 | + ("band", np.array([1, 2, 3], dtype=np.uint8), None), |
| 191 | + ] |
| 192 | + |
| 193 | + # Get DataArray without padding |
| 194 | + data = np.ctypeslib.as_array( |
| 195 | + self.data, shape=(header.my, header.mx, header.n_bands) |
| 196 | + ).copy() |
| 197 | + pad = header.pad[:] |
| 198 | + data = data[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] |
| 199 | + |
| 200 | + # Create the xarray.DataArray object |
| 201 | + image = xr.DataArray( |
| 202 | + data=data, |
| 203 | + coords=coords, |
| 204 | + name=header.name, |
| 205 | + attrs=header.data_attrs, |
| 206 | + ).transpose("band", dims[0], dims[1]) |
| 207 | + |
| 208 | + # Set GMT accessors. |
| 209 | + # Must put at the end, otherwise info gets lost after certain image operations. |
| 210 | + image.gmt.registration = header.registration |
| 211 | + image.gmt.gtype = header.gtype |
| 212 | + return image |
0 commit comments