From b59efe01e2885d02f5c9abc0be08713821c9b265 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 23 Mar 2024 10:53:25 +0800 Subject: [PATCH 01/13] Wrap GMT's standard data type GMT_CUBE for cubes --- pygmt/clib/session.py | 15 +++--- pygmt/datatypes/__init__.py | 1 + pygmt/datatypes/cube.py | 93 +++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 pygmt/datatypes/cube.py diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 1b8b5483a28..de162d124c0 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -26,7 +26,7 @@ vectors_to_arrays, ) from pygmt.clib.loading import load_libgmt -from pygmt.datatypes import _GMT_DATASET, _GMT_GRID +from pygmt.datatypes import _GMT_CUBE, _GMT_DATASET, _GMT_GRID from pygmt.exceptions import ( GMTCLibError, GMTCLibNoSessionError, @@ -1789,7 +1789,9 @@ def virtualfile_from_data( @contextlib.contextmanager def virtualfile_out( - self, kind: Literal["dataset", "grid"] = "dataset", fname: str | None = None + self, + kind: Literal["dataset", "grid", "cube"] = "dataset", + fname: str | None = None, ) -> Generator[str, None, None]: r""" Create a virtual file or an actual file for storing output data. @@ -1846,6 +1848,7 @@ def virtualfile_out( family, geometry = { "dataset": ("GMT_IS_DATASET", "GMT_IS_PLP"), "grid": ("GMT_IS_GRID", "GMT_IS_SURFACE"), + "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME"), }[kind] with self.open_virtualfile(family, geometry, "GMT_OUT", None) as vfile: yield vfile @@ -1880,9 +1883,7 @@ def inquire_virtualfile(self, vfname: str) -> int: return c_inquire_virtualfile(self.session_pointer, vfname.encode()) def read_virtualfile( - self, - vfname: str, - kind: Literal["dataset", "grid", "image", "cube", None] = None, + self, vfname: str, kind: Literal["dataset", "grid", "cube", None] = None ): """ Read data from a virtual file and optionally cast into a GMT data container. @@ -1943,9 +1944,9 @@ def read_virtualfile( # _GMT_DATASET). if kind is None: # Return the ctypes void pointer return pointer - if kind in {"image", "cube"}: + if kind == "image": raise NotImplementedError(f"kind={kind} is not supported yet.") - dtype = {"dataset": _GMT_DATASET, "grid": _GMT_GRID}[kind] + dtype = {"dataset": _GMT_DATASET, "grid": _GMT_GRID, "cube": _GMT_CUBE}[kind] return ctp.cast(pointer, ctp.POINTER(dtype)) def virtualfile_to_dataset( diff --git a/pygmt/datatypes/__init__.py b/pygmt/datatypes/__init__.py index 237a050a9f7..16627ed5798 100644 --- a/pygmt/datatypes/__init__.py +++ b/pygmt/datatypes/__init__.py @@ -2,5 +2,6 @@ Wrappers for GMT data types. """ +from pygmt.datatypes.cube import _GMT_CUBE from pygmt.datatypes.dataset import _GMT_DATASET from pygmt.datatypes.grid import _GMT_GRID diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py new file mode 100644 index 00000000000..b51341c36ab --- /dev/null +++ b/pygmt/datatypes/cube.py @@ -0,0 +1,93 @@ +""" +Wrapper for the GMT_CUBE 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, + GMT_GRID_UNIT_LEN80, + GMT_GRID_VARNAME_LEN80, + _parse_nameunits, + gmt_grdfloat, +) + + +class _GMT_CUBE(ctp.Structure): # noqa: N801 + """ + GMT cube data structure for 3D data. + """ + + _fields_: ClassVar = [ + # Pointer to full GMT 2-D header for a layer (common to all layers) + ("header", ctp.POINTER(_GMT_GRID_HEADER)), + # Pointer to the gmt_grdfloat 3-D cube - a stack of 2-D padded grids + ("data", ctp.POINTER(gmt_grdfloat)), + # Vector of x coordinates common to all layers + ("x", ctp.POINTER(ctp.c_double)), + # Vector of y coordinates common to all layers + ("y", ctp.POINTER(ctp.c_double)), + # Low-level information for GMT use only + ("hidden", ctp.c_void_p), + # GMT_CUBE_IS_STACK if input dataset was a list of 2-D grids rather than a + # single cube + ("mode", ctp.c_uint), + # Minimum/max z values (complements header->wesn[4]) + ("z_range", ctp.c_double * 2), + # z increment (complements inc[2]) (0 if variable z spacing) + ("z_inc", ctp.c_double), + # Array of z values (complements x, y) + ("z", ctp.POINTER(ctp.c_double)), + # Name of the 3-D variable, if read from file (or empty if just one) + ("name", ctp.c_char * GMT_GRID_VARNAME_LEN80), + # Units in 3rd direction (complements x_units, y_units, z_units) + ("units", ctp.c_char * GMT_GRID_UNIT_LEN80), + ] + + def to_dataarray(self): + """ + Convert the GMT_CUBE to an xarray.DataArray. + + Returns + ------- + xarray.DataArray: The data array representation of the GMT_CUBE. + """ + # The grid header + header = self.header.contents + + name = "cube" + # Dimensions and attributes + dims = header.dims + dim_attrs = header.dim_attrs + + # Patch for the 3rd dimension + dims.append("z") + z_attrs = {"actual_range": np.array(self.z_range[:]), "axis": "Z"} + long_name, units = _parse_nameunits(self.units.decode()) + if long_name: + z_attrs["long_name"] = long_name + if units: + z_attrs["units"] = units + dim_attrs.append(z_attrs) + + # The coordinates, given as a tuple of the form (dims, data, attrs) + coords = [ + (dims[0], self.y[: header.n_rows], dim_attrs[0]), + (dims[1], self.x[: header.n_columns], dim_attrs[1]), + # header->n_bands is used for the number of layers for 3-D cubes + (dims[2], self.z[: header.n_bands], dim_attrs[1]), + ] + + # The data array without paddings + pad = header.pad[:] + data = np.reshape( + self.data[: header.mx * header.my * header.n_bands], + (header.my, header.mx, header.n_bands), + )[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] + + # Create the xarray.DataArray object + grid = xr.DataArray(data, coords=coords, name=name, attrs=header.dataA_attrs) + return grid From 277a30d4566df1859a766e8c855e0dcc8bd41891 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 19 Jul 2024 16:52:35 +0800 Subject: [PATCH 02/13] Remove the to_dataarray method --- pygmt/datatypes/cube.py | 48 ----------------------------------------- 1 file changed, 48 deletions(-) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index b51341c36ab..15ca63adec5 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -5,13 +5,10 @@ import ctypes as ctp from typing import ClassVar -import numpy as np -import xarray as xr from pygmt.datatypes.header import ( _GMT_GRID_HEADER, GMT_GRID_UNIT_LEN80, GMT_GRID_VARNAME_LEN80, - _parse_nameunits, gmt_grdfloat, ) @@ -46,48 +43,3 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 # Units in 3rd direction (complements x_units, y_units, z_units) ("units", ctp.c_char * GMT_GRID_UNIT_LEN80), ] - - def to_dataarray(self): - """ - Convert the GMT_CUBE to an xarray.DataArray. - - Returns - ------- - xarray.DataArray: The data array representation of the GMT_CUBE. - """ - # The grid header - header = self.header.contents - - name = "cube" - # Dimensions and attributes - dims = header.dims - dim_attrs = header.dim_attrs - - # Patch for the 3rd dimension - dims.append("z") - z_attrs = {"actual_range": np.array(self.z_range[:]), "axis": "Z"} - long_name, units = _parse_nameunits(self.units.decode()) - if long_name: - z_attrs["long_name"] = long_name - if units: - z_attrs["units"] = units - dim_attrs.append(z_attrs) - - # The coordinates, given as a tuple of the form (dims, data, attrs) - coords = [ - (dims[0], self.y[: header.n_rows], dim_attrs[0]), - (dims[1], self.x[: header.n_columns], dim_attrs[1]), - # header->n_bands is used for the number of layers for 3-D cubes - (dims[2], self.z[: header.n_bands], dim_attrs[1]), - ] - - # The data array without paddings - pad = header.pad[:] - data = np.reshape( - self.data[: header.mx * header.my * header.n_bands], - (header.my, header.mx, header.n_bands), - )[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] - - # Create the xarray.DataArray object - grid = xr.DataArray(data, coords=coords, name=name, attrs=header.dataA_attrs) - return grid From 757321a263c89b39622732f0200823014f16aa5d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 19 Jul 2024 16:58:41 +0800 Subject: [PATCH 03/13] Finalize the GMT_CUBE wrapper --- pygmt/datatypes/cube.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index 15ca63adec5..8bac0d14659 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -15,7 +15,12 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 """ - GMT cube data structure for 3D data. + GMT cube data structure for 3-D data. + + The GMT_CUBE structure is a extension of the GMT_GRID structure to handle 3-D data + cubes. It requires a 2-D grid header and extended parameters for the 3rd dimension. + + header->n_bands is used for the number of layers in 3-D cubes. """ _fields_: ClassVar = [ @@ -29,10 +34,10 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 ("y", ctp.POINTER(ctp.c_double)), # Low-level information for GMT use only ("hidden", ctp.c_void_p), - # GMT_CUBE_IS_STACK if input dataset was a list of 2-D grids rather than a - # single cube + # mode=GMT_CUBE_IS_STACK means the input dataset was a list of 2-D grids, rather + # than a single cube. ("mode", ctp.c_uint), - # Minimum/max z values (complements header->wesn[4]) + # Minimum/maximum z values (complements header->wesn[4]) ("z_range", ctp.c_double * 2), # z increment (complements inc[2]) (0 if variable z spacing) ("z_inc", ctp.c_double), From f77d4128f8d8e4e13d8926c781a03abffea06567 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 19 Jul 2024 17:06:10 +0800 Subject: [PATCH 04/13] Update the docstrings --- pygmt/clib/session.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index de162d124c0..ee55359c016 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1804,8 +1804,8 @@ def virtualfile_out( Parameters ---------- kind - The data kind of the virtual file to create. Valid values are ``"dataset"`` - and ``"grid"``. Ignored if ``fname`` is specified. + The data kind of the virtual file to create. Valid values are ``"dataset"``, + ``"grid"`` and ``"cube"``. Ignored if ``fname`` is specified. fname The name of the actual file to write the output data. No virtual file will be created. @@ -1894,7 +1894,8 @@ def read_virtualfile( Name of the virtual file to read. kind Cast the data into a GMT data container. Valid values are ``"dataset"``, - ``"grid"`` and ``None``. If ``None``, will return a ctypes void pointer. + ``"grid"``, ``"cube"`` and ``None``. If ``None``, will return a ctypes void + pointer. Returns ------- From ecf57116d9d2b680fd584057feff85d6bcb9660f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 19 Jul 2024 17:10:59 +0800 Subject: [PATCH 05/13] Add cube support in Session.read_data --- pygmt/clib/session.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index ee55359c016..6ed97ddf1fd 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1070,7 +1070,7 @@ def put_matrix(self, dataset, matrix, pad=0): def read_data( self, infile: str, - kind: Literal["dataset", "grid"], + kind: Literal["dataset", "grid", "cube"], family: str | None = None, geometry: str | None = None, mode: str = "GMT_READ_NORMAL", @@ -1088,8 +1088,8 @@ def read_data( infile The input file name. kind - The data kind of the input file. Valid values are ``"dataset"`` and - ``"grid"``. + The data kind of the input file. Valid values are ``"dataset"``, ``"grid"`` + and ``"cube"``. family A valid GMT data family name (e.g., ``"GMT_IS_DATASET"``). See the ``FAMILIES`` attribute for valid names. If ``None``, will determine the data @@ -1140,6 +1140,7 @@ def read_data( _family, _geometry, dtype = { "dataset": ("GMT_IS_DATASET", "GMT_IS_PLP", _GMT_DATASET), "grid": ("GMT_IS_GRID", "GMT_IS_SURFACE", _GMT_GRID), + "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME", _GMT_CUBE), }[kind] if family is None: family = _family From 810fb12a248333ecad75bf34b1ffaeab967447e8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 19 Jul 2024 18:16:03 +0800 Subject: [PATCH 06/13] Add two tests for reading grid/image as GMT_CUBE --- pygmt/tests/test_clib_read_data.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pygmt/tests/test_clib_read_data.py b/pygmt/tests/test_clib_read_data.py index 43978b291c2..57660c74b53 100644 --- a/pygmt/tests/test_clib_read_data.py +++ b/pygmt/tests/test_clib_read_data.py @@ -132,6 +132,24 @@ def test_clib_read_data_grid_actual_image(): ) +def test_clib_read_data_cube_actual_grid(): + """ + Test the Session.read_data method for cube, but actually the file is a grid. + """ + with Session() as lib: + with pytest.raises(GMTCLibError): + lib.read_data("@earth_relief_01d_p", kind="cube", mode="GMT_CONTAINER_ONLY") + + +def test_clib_read_data_cube_actual_image(): + """ + Test the Session.read_data method for cube, but actually the file is an image. + """ + with Session() as lib: + with pytest.raises(GMTCLibError): + lib.read_data("@earth_day_01d_p", kind="cube", mode="GMT_CONTAINER_ONLY") + + def test_clib_read_data_fails(): """ Test that the Session.read_data method raises an exception if there are errors. From 2823168a6f1bc1b03ef7cd38587e0e552f770c95 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 27 Jul 2024 16:42:55 +0800 Subject: [PATCH 07/13] Fix some typos --- pygmt/clib/session.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 757a7552703..77ef28e7f75 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1090,7 +1090,7 @@ def read_data( The input file name. kind The data kind of the input file. Valid values are ``"dataset"``, ``"grid"``, - ``"image"`` and ``"cube"``, + ``"image"`` and ``"cube"``. family A valid GMT data family name (e.g., ``"GMT_IS_DATASET"``). See the ``FAMILIES`` attribute for valid names. If ``None``, will determine the data @@ -1895,7 +1895,9 @@ def inquire_virtualfile(self, vfname: str) -> int: return c_inquire_virtualfile(self.session_pointer, vfname.encode()) def read_virtualfile( - self, vfname: str, kind: Literal["dataset", "grid", "cube", None] = None + self, + vfname: str, + kind: Literal["dataset", "grid", "image", "cube", None] = None, ): """ Read data from a virtual file and optionally cast into a GMT data container. From 6b1bc4091256a237e56f2918c958add71a9b3e09 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 27 Jul 2024 16:44:54 +0800 Subject: [PATCH 08/13] Sort data kind by importance --- pygmt/clib/session.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 77ef28e7f75..cd903d24efc 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1071,7 +1071,7 @@ def put_matrix(self, dataset, matrix, pad=0): def read_data( self, infile: str, - kind: Literal["dataset", "grid", "cube", "image"], + kind: Literal["dataset", "grid", "image", "cube"], family: str | None = None, geometry: str | None = None, mode: str = "GMT_READ_NORMAL", @@ -1141,8 +1141,8 @@ def read_data( _family, _geometry, dtype = { "dataset": ("GMT_IS_DATASET", "GMT_IS_PLP", _GMT_DATASET), "grid": ("GMT_IS_GRID", "GMT_IS_SURFACE", _GMT_GRID), - "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME", _GMT_CUBE), "image": ("GMT_IS_IMAGE", "GMT_IS_SURFACE", _GMT_IMAGE), + "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME", _GMT_CUBE), }[kind] if family is None: family = _family @@ -1800,7 +1800,7 @@ def virtualfile_from_data( @contextlib.contextmanager def virtualfile_out( self, - kind: Literal["dataset", "grid", "cube", "image"] = "dataset", + kind: Literal["dataset", "grid", "image", "cube"] = "dataset", fname: str | None = None, ) -> Generator[str, None, None]: r""" @@ -1858,8 +1858,8 @@ def virtualfile_out( family, geometry = { "dataset": ("GMT_IS_DATASET", "GMT_IS_PLP"), "grid": ("GMT_IS_GRID", "GMT_IS_SURFACE"), - "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME"), "image": ("GMT_IS_IMAGE", "GMT_IS_SURFACE"), + "cube": ("GMT_IS_CUBE", "GMT_IS_VOLUME"), }[kind] direction = "GMT_OUT|GMT_IS_REFERENCE" if kind == "image" else "GMT_OUT" with self.open_virtualfile(family, geometry, direction, None) as vfile: @@ -1962,8 +1962,8 @@ def read_virtualfile( dtype = { "dataset": _GMT_DATASET, "grid": _GMT_GRID, - "cube": _GMT_CUBE, "image": _GMT_IMAGE, + "cube": _GMT_CUBE, }[kind] return ctp.cast(pointer, ctp.POINTER(dtype)) From fec544cb45fce2c5963b7e4fa6e8e5ddb96bc0e9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 9 Aug 2024 14:11:01 +0800 Subject: [PATCH 09/13] Cache cube.nc --- pygmt/helpers/caching.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index 714f12d890e..572eca691c1 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -74,6 +74,7 @@ def cache_data(): "@Table_5_11_mean.xyz", "@capitals.gmt", "@circuit.png", + "@cube.nc", "@earth_relief_20m_holes.grd", "@fractures_06.txt", "@hotspots.txt", From 2647e41239d3525216fa2227704003ece2197b94 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 19 Sep 2024 22:17:29 +0800 Subject: [PATCH 10/13] Add a doctest --- pygmt/datatypes/cube.py | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index 8bac0d14659..e56f372f02f 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -21,6 +21,62 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 cubes. It requires a 2-D grid header and extended parameters for the 3rd dimension. header->n_bands is used for the number of layers in 3-D cubes. + + Examples + -------- + >>> import numpy as np + >>> from pygmt import which + >>> from pygmt.clib import Session + >>> cubefile = which("@cube.nc", download="c") + >>> with Session() as lib: + ... with lib.virtualfile_out(kind="cube") as voutcube: + ... lib.call_module("read", [cubefile, voutcube, "-Tu", "-Vd"]) + ... # Read the cube from the virtual file + ... cube = lib.read_virtualfile(vfname=voutcube, kind="cube").contents + ... # The cube header + ... header = cube.header.contents + ... # Access the header properties + ... print(header.n_rows, header.n_columns, header.registration) + ... print(header.wesn[:], header.inc[:]) + ... print(header.z_scale_factor, header.z_add_offset) + ... print(header.x_units, header.y_units, header.z_units) + ... print(header.nm, header.size, header.complex_mode) + ... print(header.type, header.n_bands, header.mx, header.my) + ... print(header.pad[:]) + ... print(header.mem_layout, header.xy_off) + ... # Cube-specific attributes. + ... print(cube.mode, cube.z_range[:], cube.z_inc, cube.name, cube.units) + ... # The x, y, and z coordinates + ... x = cube.x[: header.n_columns] + ... y = cube.y[: header.n_rows] + ... z = cube.z[: header.n_bands] + ... # The data array (with paddings) + ... data = np.reshape( + ... cube.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], :] + 11 11 0 + [0.0, 10.0, 0.0, 10.0] [1.0, 1.0] + 1.0 0.0 + b'x' b'y' b'cube' + 121 226 0 + 18 4 15 15 + [2, 2, 2, 2] + b'' 0.0 + 0 [1.0, 5.0] 0.0 b'' b'z' + >>> x + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + >>> y + [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + >>> z + [1.0, 2.0, 3.0, 5.0] + >>> data.shape + (11, 11, 4) + >>> #data.min(), data.max() # The min/max are wrong. Upstream bug? + >>> #(-29.399999618530273, 169.39999389648438) """ _fields_: ClassVar = [ From 0b64d70e23ab982a571a2674f81f570625060725 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 19 Sep 2024 22:20:43 +0800 Subject: [PATCH 11/13] Fix styles --- pygmt/datatypes/cube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index e56f372f02f..09c48d0b62f 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -75,8 +75,8 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 [1.0, 2.0, 3.0, 5.0] >>> data.shape (11, 11, 4) - >>> #data.min(), data.max() # The min/max are wrong. Upstream bug? - >>> #(-29.399999618530273, 169.39999389648438) + >>> # data.min(), data.max() # The min/max are wrong. Upstream bug? + >>> # (-29.399999618530273, 169.39999389648438) """ _fields_: ClassVar = [ From 80999c614f19a9dd184d8582879102fa5e7db3fc Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 30 Sep 2024 21:38:00 +0800 Subject: [PATCH 12/13] Updates --- pygmt/datatypes/cube.py | 21 ++++++++++----------- pygmt/tests/test_clib_read_data.py | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index 09c48d0b62f..363febd1fc0 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -47,14 +47,13 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 ... # Cube-specific attributes. ... print(cube.mode, cube.z_range[:], cube.z_inc, cube.name, cube.units) ... # The x, y, and z coordinates - ... x = cube.x[: header.n_columns] - ... y = cube.y[: header.n_rows] - ... z = cube.z[: header.n_bands] + ... x = np.ctypeslib.as_array(cube.x, shape=(header.n_columns,)).copy() + ... y = np.ctypeslib.as_array(cube.y, shape=(header.n_rows,)).copy() + ... z = np.ctypeslib.as_array(cube.z, shape=(header.n_bands,)).copy() ... # The data array (with paddings) - ... data = np.reshape( - ... cube.data[: header.n_bands * header.mx * header.my], - ... (header.my, header.mx, header.n_bands), - ... ) + ... data = np.ctypeslib.as_array( + ... cube.data, shape=(header.my, header.mx, header.n_bands) + ... ).copy() ... # The data array (without paddings) ... pad = header.pad[:] ... data = data[pad[2] : header.my - pad[3], pad[0] : header.mx - pad[1], :] @@ -68,15 +67,15 @@ class _GMT_CUBE(ctp.Structure): # noqa: N801 b'' 0.0 0 [1.0, 5.0] 0.0 b'' b'z' >>> x - [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]) >>> y - [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + array([10., 9., 8., 7., 6., 5., 4., 3., 2., 1., 0.]) >>> z - [1.0, 2.0, 3.0, 5.0] + array([1., 2., 3., 5.]) >>> data.shape (11, 11, 4) >>> # data.min(), data.max() # The min/max are wrong. Upstream bug? - >>> # (-29.399999618530273, 169.39999389648438) + >>> # (-29.4, 169.4) """ _fields_: ClassVar = [ diff --git a/pygmt/tests/test_clib_read_data.py b/pygmt/tests/test_clib_read_data.py index 00f8fba0f5b..4da7fa61b30 100644 --- a/pygmt/tests/test_clib_read_data.py +++ b/pygmt/tests/test_clib_read_data.py @@ -216,7 +216,7 @@ def test_clib_read_data_cube_actual_image(): """ with Session() as lib: with pytest.raises(GMTCLibError): - lib.read_data("@earth_day_01d_p", kind="cube", mode="GMT_CONTAINER_ONLY") + lib.read_data("@earth_day_01d", kind="cube", mode="GMT_CONTAINER_ONLY") def test_clib_read_data_fails(): From 723d9f2dbc7617e38fa5bc1ca255e928fb40ddb2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Aug 2026 12:30:59 +0800 Subject: [PATCH 13/13] Fix styling issue --- pygmt/datatypes/cube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/datatypes/cube.py b/pygmt/datatypes/cube.py index 363febd1fc0..af584a1d488 100644 --- a/pygmt/datatypes/cube.py +++ b/pygmt/datatypes/cube.py @@ -13,7 +13,7 @@ ) -class _GMT_CUBE(ctp.Structure): # noqa: N801 +class _GMT_CUBE(ctp.Structure): # ruff: ignore[invalid-class-name] """ GMT cube data structure for 3-D data.