|
| 1 | +""" |
| 2 | +Wrapper for the GMT_IMAGE data type. |
| 3 | +""" |
| 4 | + |
| 5 | +import ctypes as ctp |
| 6 | +from typing import ClassVar |
| 7 | + |
| 8 | +import numpy as np |
| 9 | +import xarray as xr |
| 10 | +from pygmt.datatypes.header import _GMT_GRID_HEADER |
| 11 | + |
| 12 | + |
| 13 | +class _GMT_IMAGE(ctp.Structure): # noqa: N801 |
| 14 | + """ |
| 15 | + GMT image data structure. |
| 16 | +
|
| 17 | + Examples |
| 18 | + -------- |
| 19 | + >>> from pygmt.clib import Session |
| 20 | + >>> import numpy as np |
| 21 | + >>> import xarray as xr |
| 22 | +
|
| 23 | + >>> with Session() as lib: |
| 24 | + ... with lib.virtualfile_out(kind="image") as voutimg: |
| 25 | + ... lib.call_module("read", f"@earth_day_01d {voutimg} -Ti") |
| 26 | + ... # Read the image from the virtual file |
| 27 | + ... image = lib.read_virtualfile(vfname=voutimg, kind="image").contents |
| 28 | + ... # The image header |
| 29 | + ... header = image.header.contents |
| 30 | + ... # Access the header properties |
| 31 | + ... print(image.type, header.n_bands, header.n_rows, header.n_columns) |
| 32 | + ... print(header.pad[:]) |
| 33 | + ... # The x and y coordinates |
| 34 | + ... x = image.x[: header.n_columns] |
| 35 | + ... y = image.y[: header.n_rows] |
| 36 | + ... # The data array (with paddings) |
| 37 | + ... data = np.reshape( |
| 38 | + ... image.data[: header.n_bands * header.mx * header.my], |
| 39 | + ... (header.my, header.mx, header.n_bands), |
| 40 | + ... ) |
| 41 | + ... # The data array (without paddings) |
| 42 | + ... pad = header.pad[:] |
| 43 | + ... data = data[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] |
| 44 | + ... print(data.shape) |
| 45 | + 1 3 180 360 |
| 46 | + [2, 2, 2, 2] |
| 47 | + (180, 360, 3) |
| 48 | + """ |
| 49 | + |
| 50 | + _fields_: ClassVar = [ |
| 51 | + # Data type, e.g. GMT_FLOAT |
| 52 | + ("type", ctp.c_int), |
| 53 | + # Array with color lookup values |
| 54 | + ("colormap", ctp.POINTER(ctp.c_int)), |
| 55 | + # Number of colors in a paletted image |
| 56 | + ("n_indexed_colors", ctp.c_int), |
| 57 | + # Pointer to full GMT header for the image |
| 58 | + ("header", ctp.POINTER(_GMT_GRID_HEADER)), |
| 59 | + # Pointer to actual image |
| 60 | + ("data", ctp.POINTER(ctp.c_ubyte)), |
| 61 | + # Pointer to an optional transparency layer stored in a separate variable |
| 62 | + ("alpha", ctp.POINTER(ctp.c_ubyte)), |
| 63 | + # Color interpolation |
| 64 | + ("color_interp", ctp.c_char_p), |
| 65 | + # Pointer to the x-coordinate vector |
| 66 | + ("x", ctp.POINTER(ctp.c_double)), |
| 67 | + # Pointer to the y-coordinate vector |
| 68 | + ("y", ctp.POINTER(ctp.c_double)), |
| 69 | + # Book-keeping variables "hidden" from the API |
| 70 | + ("hidden", ctp.c_void_p), |
| 71 | + ] |
| 72 | + |
| 73 | + def to_dataarray(self) -> xr.DataArray: |
| 74 | + """ |
| 75 | + Convert a _GMT_IMAGE object to an :class:`xarray.DataArray` object. |
| 76 | +
|
| 77 | + Returns |
| 78 | + ------- |
| 79 | + dataarray |
| 80 | + A :class:`xarray.DataArray` object. |
| 81 | +
|
| 82 | + Examples |
| 83 | + -------- |
| 84 | + >>> from pygmt.clib import Session |
| 85 | + >>> with Session() as lib: |
| 86 | + ... with lib.virtualfile_out(kind="image") as voutimg: |
| 87 | + ... lib.call_module("read", ["@earth_day_01d", voutimg, "-Ti"]) |
| 88 | + ... # Read the image from the virtual file |
| 89 | + ... image = lib.read_virtualfile(voutimg, kind="image") |
| 90 | + ... # Convert to xarray.DataArray and use it later |
| 91 | + ... da = image.contents.to_dataarray() |
| 92 | + >>> da # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 93 | + <xarray.DataArray 'z' (band: 3, y: 180, x: 360)> Size: 2MB |
| 94 | + array([[[ 10, 10, 10, ..., 10, 10, 10], |
| 95 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 96 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 97 | + ..., |
| 98 | + [192, 193, 193, ..., 193, 192, 191], |
| 99 | + [204, 206, 206, ..., 205, 206, 204], |
| 100 | + [208, 210, 210, ..., 210, 210, 208]], |
| 101 | + <BLANKLINE> |
| 102 | + [[ 10, 10, 10, ..., 10, 10, 10], |
| 103 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 104 | + [ 10, 10, 10, ..., 10, 10, 10], |
| 105 | + ..., |
| 106 | + [186, 187, 188, ..., 187, 186, 185], |
| 107 | + [196, 198, 198, ..., 197, 197, 196], |
| 108 | + [199, 201, 201, ..., 201, 202, 199]], |
| 109 | + <BLANKLINE> |
| 110 | + [[ 51, 51, 51, ..., 51, 51, 51], |
| 111 | + [ 51, 51, 51, ..., 51, 51, 51], |
| 112 | + [ 51, 51, 51, ..., 51, 51, 51], |
| 113 | + ..., |
| 114 | + [177, 179, 179, ..., 178, 177, 177], |
| 115 | + [185, 187, 187, ..., 187, 186, 185], |
| 116 | + [189, 191, 191, ..., 191, 191, 189]]]) |
| 117 | + Coordinates: |
| 118 | + * x (x) float64 3kB -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 |
| 119 | + * y (y) float64 1kB 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 |
| 120 | + * band (band) uint8 3B 0 1 2 |
| 121 | + Attributes: |
| 122 | + title: |
| 123 | + history: |
| 124 | + description: |
| 125 | + long_name: z |
| 126 | + actual_range: [ 1.79769313e+308 -1.79769313e+308] |
| 127 | +
|
| 128 | + >>> da.coords["x"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 129 | + <xarray.DataArray 'x' (x: 360)> Size: 3kB |
| 130 | + array([-179.5, -178.5, -177.5, ..., 177.5, 178.5, 179.5]) |
| 131 | + Coordinates: |
| 132 | + * x (x) float64 3kB -179.5 -178.5 -177.5 -176.5 ... 177.5 178.5 179.5 |
| 133 | +
|
| 134 | + >>> da.coords["y"] # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS |
| 135 | + <xarray.DataArray 'y' (y: 180)> Size: 1kB |
| 136 | + array([ 89.5, 88.5, 87.5, 86.5, 85.5, 84.5, 83.5, 82.5, 81.5, 80.5, |
| 137 | + 79.5, 78.5, 77.5, 76.5, 75.5, 74.5, 73.5, 72.5, 71.5, 70.5, |
| 138 | + 69.5, 68.5, 67.5, 66.5, 65.5, 64.5, 63.5, 62.5, 61.5, 60.5, |
| 139 | + ... |
| 140 | + -0.5, -1.5, -2.5, -3.5, -4.5, -5.5, -6.5, -7.5, -8.5, -9.5, |
| 141 | + ... |
| 142 | + -60.5, -61.5, -62.5, -63.5, -64.5, -65.5, -66.5, -67.5, -68.5, -69.5, |
| 143 | + -70.5, -71.5, -72.5, -73.5, -74.5, -75.5, -76.5, -77.5, -78.5, -79.5, |
| 144 | + -80.5, -81.5, -82.5, -83.5, -84.5, -85.5, -86.5, -87.5, -88.5, -89.5]) |
| 145 | + Coordinates: |
| 146 | + * y (y) float64 1kB 89.5 88.5 87.5 86.5 ... -86.5 -87.5 -88.5 -89.5 |
| 147 | +
|
| 148 | + >>> da.gmt.registration, da.gmt.gtype |
| 149 | + (1, 0) |
| 150 | + """ |
| 151 | + |
| 152 | + # Get image header |
| 153 | + header: _GMT_GRID_HEADER = self.header.contents |
| 154 | + |
| 155 | + # Get DataArray without padding |
| 156 | + pad = header.pad[:] |
| 157 | + data: np.ndarray = np.reshape( |
| 158 | + a=self.data[: header.n_bands * header.mx * header.my], |
| 159 | + newshape=(header.my, header.mx, header.n_bands), |
| 160 | + )[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] |
| 161 | + |
| 162 | + # Get x and y coordinates |
| 163 | + coords: dict[str, list | np.ndarray] = { |
| 164 | + "x": self.x[: header.n_columns], |
| 165 | + "y": self.y[: header.n_rows], |
| 166 | + "band": np.array([0, 1, 2], dtype=np.uint8), |
| 167 | + } |
| 168 | + |
| 169 | + # Create the xarray.DataArray object |
| 170 | + image = xr.DataArray( |
| 171 | + data=data, |
| 172 | + coords=coords, |
| 173 | + dims=("y", "x", "band"), |
| 174 | + name=header.name, |
| 175 | + attrs=header.data_attrs, |
| 176 | + ).transpose("band", "y", "x") |
| 177 | + |
| 178 | + # Set GMT accessors. |
| 179 | + # Must put at the end, otherwise info gets lost after certain image operations. |
| 180 | + image.gmt.registration = header.registration |
| 181 | + image.gmt.gtype = header.gtype |
| 182 | + return image |
0 commit comments