-
Notifications
You must be signed in to change notification settings - Fork 220
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
Wrap GMT's standard data type GMT_IMAGE for images #3338
Changes from 9 commits
921674c
c01d6ab
36430c8
a918cfd
1983b7e
84cbd11
f887a23
95275b0
d43f2e9
47d4e4a
bb0efe3
994de3a
8d9c2ae
75c0f7e
dc1a649
0638dab
7080d8b
8692161
aeb2ea3
dad88ce
3f2f6bf
74b889f
bf7f0b0
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,94 @@ | ||
""" | ||
Wrapper for the GMT_IMAGE data type. | ||
""" | ||
|
||
import ctypes as ctp | ||
from typing import ClassVar | ||
|
||
from pygmt.datatypes.grid import _GMT_GRID_HEADER | ||
|
||
|
||
class _GMT_IMAGE(ctp.Structure): # noqa: N801 | ||
""" | ||
GMT image data structure. | ||
|
||
Examples | ||
-------- | ||
>>> import numpy as np | ||
>>> 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(vfname=voutimg, kind="image").contents | ||
... # The image header | ||
... header = image.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.title) | ||
... print(header.command) | ||
... print(header.remark) | ||
... 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.nan_value, header.xy_off) | ||
... # Image-specific attributes. | ||
... print(image.type, image.n_indexed_colors) | ||
... # 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], :] | ||
180 360 1 | ||
[-180.0, 180.0, -90.0, 90.0] [1.0, 1.0] | ||
1.0 0.0 | ||
b'x' b'y' b'z' | ||
b'' | ||
b'' | ||
b'' | ||
64800 66976 0 | ||
0 3 364 184 | ||
[2, 2, 2, 2] | ||
b'BRPa' 0.0 0.5 | ||
1 0 | ||
>>> x | ||
[-179.5, -178.5, ..., 178.5, 179.5] | ||
>>> y | ||
[89.5, 88.5, ..., -88.5, -89.5] | ||
>>> data.shape | ||
(180, 360, 3) | ||
>>> data.min(), data.max() | ||
(10, 255) | ||
""" | ||
|
||
_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), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,6 +132,60 @@ def test_clib_read_data_grid_actual_image(): | |
) | ||
|
||
|
||
# Note: Simplify the tests for images after GMT_IMAGE.to_dataarray() is implemented. | ||
def test_clib_read_data_image(): | ||
""" | ||
Test the Session.read_data method for images. | ||
""" | ||
with Session() as lib: | ||
image = lib.read_data("@earth_day_01d_p", kind="image").contents | ||
header = image.header.contents | ||
assert header.n_rows == 180 | ||
assert header.n_columns == 360 | ||
assert header.n_bands == 3 | ||
assert header.wesn[:] == [-180.0, 180.0, -90.0, 90.0] | ||
assert image.data | ||
|
||
|
||
def test_clib_read_data_image_two_steps(): | ||
""" | ||
Test the Session.read_data method for images in two steps, first reading the header | ||
and then the data. | ||
""" | ||
infile = "@earth_day_01d_p" | ||
with Session() as lib: | ||
# Read the header first | ||
data_ptr = lib.read_data(infile, kind="image", mode="GMT_CONTAINER_ONLY") | ||
grid = data_ptr.contents | ||
header = grid.header.contents | ||
assert header.n_rows == 180 | ||
assert header.n_columns == 360 | ||
assert header.wesn[:] == [-180.0, 180.0, -90.0, 90.0] | ||
assert header.n_bands == 3 # Explicitly check n_bands | ||
assert not grid.data # The data is not read yet | ||
|
||
# Read the data | ||
lib.read_data(infile, kind="image", mode="GMT_DATA_ONLY", data=data_ptr) | ||
assert grid.data | ||
seisman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def test_clib_read_data_image_actual_grid(): | ||
""" | ||
Test the Session.read_data method for image, but actually the file is a grid. | ||
""" | ||
with Session() as lib: | ||
data_ptr = lib.read_data( | ||
"@earth_relief_01d_p", kind="image", mode="GMT_CONTAINER_ONLY" | ||
) | ||
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. Getting this error at https://github.com/GenericMappingTools/pygmt/actions/runs/10017032324/job/27690729251?pr=3338#step:8:1556: ERROR 4: `/home/runner/.gmt/server/earth/earth_relief/earth_relief_01d_p.grd' not recognized as being in a supported file format. It could have been recognized by driver HDF5, but plugin gdal_HDF5.so is not available in your installation. You may install it with 'conda install -c conda-forge libgdal-hdf5'
Error: ession [ERROR]: Unable to open earth_relief_01d_p.grd.
Error: ession [ERROR]: ERROR reading image with gdalread.
pygmt-session (gmtapi_import_image): Could not read from file [earth_relief_01d_p.grd]
[Session pygmt-session (198)]: Error returned from GMT API: GMT_IMAGE_READ_ERROR (22)
[Session pygmt-session (198)]: Error returned from GMT API: GMT_IMAGE_READ_ERROR (22) So do we need to add 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 think yes 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. Working in conda-forge/gmt-feedstock#293. Edit: Tried to run 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 test still fails even if The expected
The test added in conda-forge/gmt-feedstock#293 also fails (see https://github.com/conda-forge/gmt-feedstock/pull/293/checks?check_run_id=27696860313, https://dev.azure.com/conda-forge/feedstock-builds/_build/results?buildId=983672&view=logs&jobId=656edd35-690f-5c53-9ba3-09c10d0bea97&j=656edd35-690f-5c53-9ba3-09c10d0bea97&t=986b1512-c876-5f92-0d81-ba851554a0a3). Not interested in digging down the rabbit hole, so I prefer to removing this test. 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. Hmm, or we can keep the test and mark it with xfail? Might have something to do with different GDAL versions or something. I almost think we should include GDAL in 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've added the test back but the macOS + Python 3.12 CI job keeps crashing (https://github.com/GenericMappingTools/pygmt/actions/runs/10024733960/job/27707337991?pr=3338). |
||
image = data_ptr.contents | ||
header = image.header.contents | ||
assert header.n_rows == 180 | ||
assert header.n_columns == 360 | ||
assert header.wesn[:] == [-180.0, 180.0, -90.0, 90.0] | ||
# Explicitly check n_bands. Grid has only one band. | ||
assert header.n_bands == 1 | ||
|
||
|
||
def test_clib_read_data_fails(): | ||
""" | ||
Test that the Session.read_data method raises an exception if there are errors. | ||
|
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.
header.z_min and header.z_max are not checked here, because they have invalid values like 1.79769313486231570000e+308 (i.e.,
DBL_MAX
).