Skip to content

Commit fe41846

Browse files
committed
Merge branch 'main' into pass-arg-list-to-module
2 parents 13334d0 + 62eb5d6 commit fe41846

File tree

8 files changed

+96
-45
lines changed

8 files changed

+96
-45
lines changed

examples/gallery/embellishments/colorbar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
The colormap is set via the ``cmap`` parameter. A full list of available
77
color palette tables can be found at :gmt-docs:`reference/cpts.html`.
88
Use the ``frame`` parameter to add labels to the **x** and **y** axes
9-
of the colorbar by appending **+l** followed by the desired text. To Add
9+
of the colorbar by appending **+l** followed by the desired text. To add
1010
and adjust the annotations (**a**) and ticks (**f**) append the letter
1111
followed by the desired interval. The placement of the colorbar is set
1212
via the ``position`` parameter. There are the following options:

pygmt/clib/conversion.py

+64-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
Functions to convert data types into ctypes friendly formats.
33
"""
44

5+
import ctypes as ctp
56
import warnings
7+
from collections.abc import Sequence
68

79
import numpy as np
810
from pygmt.exceptions import GMTInvalidInput
@@ -243,41 +245,81 @@ def as_c_contiguous(array):
243245
return array
244246

245247

246-
def kwargs_to_ctypes_array(argument, kwargs, dtype):
248+
def sequence_to_ctypes_array(sequence: Sequence, ctype, size: int) -> ctp.Array | None:
247249
"""
248-
Convert an iterable argument from kwargs into a ctypes array variable.
250+
Convert a sequence of numbers into a ctypes array variable.
249251
250-
If the argument is not present in kwargs, returns ``None``.
252+
If the sequence is ``None``, returns ``None``. Otherwise, returns a ctypes array.
253+
The function only works for sequences of numbers. For converting a sequence of
254+
strings, use ``strings_to_ctypes_array`` instead.
251255
252256
Parameters
253257
----------
254-
argument : str
255-
The name of the argument.
256-
kwargs : dict
257-
Dictionary of keyword arguments.
258-
dtype : ctypes type
259-
The ctypes array type (e.g., ``ctypes.c_double*4``)
258+
sequence
259+
The sequence to convert. If ``None``, returns ``None``. Otherwise, it must be a
260+
sequence (e.g., list, tuple, numpy array).
261+
ctype
262+
The ctypes type of the array (e.g., ``ctypes.c_int``).
263+
size
264+
The size of the array. If the sequence is smaller than the size, the remaining
265+
elements will be filled with zeros. If the sequence is larger than the size, an
266+
exception will be raised.
260267
261268
Returns
262269
-------
263-
ctypes_value : ctypes array or None
270+
ctypes_array
271+
The ctypes array variable.
264272
265273
Examples
266274
--------
267-
268-
>>> import ctypes as ct
269-
>>> value = kwargs_to_ctypes_array("bla", {"bla": [10, 10]}, ct.c_long * 2)
270-
>>> type(value)
271-
<class 'pygmt.clib.conversion.c_long_Array_2'>
272-
>>> should_be_none = kwargs_to_ctypes_array(
273-
... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2
274-
... )
275-
>>> print(should_be_none)
275+
>>> import ctypes as ctp
276+
>>> ctypes_array = sequence_to_ctypes_array([1, 2, 3], ctp.c_long, 3)
277+
>>> type(ctypes_array)
278+
<class 'pygmt.clib.conversion.c_long_Array_3'>
279+
>>> ctypes_array[:]
280+
[1, 2, 3]
281+
>>> ctypes_array = sequence_to_ctypes_array([1, 2], ctp.c_long, 5)
282+
>>> type(ctypes_array)
283+
<class 'pygmt.clib.conversion.c_long_Array_5'>
284+
>>> ctypes_array[:]
285+
[1, 2, 0, 0, 0]
286+
>>> ctypes_array = sequence_to_ctypes_array(None, ctp.c_long, 5)
287+
>>> print(ctypes_array)
276288
None
289+
>>> ctypes_array = sequence_to_ctypes_array([1, 2, 3, 4, 5, 6], ctp.c_long, 5)
290+
Traceback (most recent call last):
291+
...
292+
IndexError: invalid index
293+
"""
294+
if sequence is None:
295+
return None
296+
return (ctype * size)(*sequence)
297+
298+
299+
def strings_to_ctypes_array(strings: Sequence[str]) -> ctp.Array:
300+
"""
301+
Convert a sequence (e.g., a list) of strings into a ctypes array.
302+
303+
Parameters
304+
----------
305+
strings
306+
A sequence of strings.
307+
308+
Returns
309+
-------
310+
ctypes_array
311+
A ctypes array of strings.
312+
313+
Examples
314+
--------
315+
>>> strings = ["first", "second", "third"]
316+
>>> ctypes_array = strings_to_ctypes_array(strings)
317+
>>> type(ctypes_array)
318+
<class 'pygmt.clib.conversion.c_char_p_Array_3'>
319+
>>> [s.decode() for s in ctypes_array]
320+
['first', 'second', 'third']
277321
"""
278-
if argument in kwargs:
279-
return dtype(*kwargs[argument])
280-
return None
322+
return (ctp.c_char_p * len(strings))(*[s.encode() for s in strings])
281323

282324

283325
def array_to_datetime(array):

pygmt/clib/session.py

+24-21
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
array_to_datetime,
2020
as_c_contiguous,
2121
dataarray_to_matrix,
22-
kwargs_to_ctypes_array,
22+
sequence_to_ctypes_array,
23+
strings_to_ctypes_array,
2324
vectors_to_arrays,
2425
)
2526
from pygmt.clib.loading import load_libgmt
@@ -656,7 +657,17 @@ def call_module(self, module: str, args: list[str]):
656657
f"Module '{module}' failed with status code {status}:\n{self._error_message}"
657658
)
658659

659-
def create_data(self, family, geometry, mode, **kwargs):
660+
def create_data(
661+
self,
662+
family,
663+
geometry,
664+
mode,
665+
dim=None,
666+
ranges=None,
667+
inc=None,
668+
registration="GMT_GRID_NODE_REG",
669+
pad=None,
670+
):
660671
"""
661672
Create an empty GMT data container.
662673
@@ -720,15 +731,13 @@ def create_data(self, family, geometry, mode, **kwargs):
720731
valid_modifiers=["GMT_GRID_IS_CARTESIAN", "GMT_GRID_IS_GEO"],
721732
)
722733
geometry_int = self._parse_constant(geometry, valid=GEOMETRIES)
723-
registration_int = self._parse_constant(
724-
kwargs.get("registration", "GMT_GRID_NODE_REG"), valid=REGISTRATIONS
725-
)
734+
registration_int = self._parse_constant(registration, valid=REGISTRATIONS)
726735

727736
# Convert dim, ranges, and inc to ctypes arrays if given (will be None
728737
# if not given to represent NULL pointers)
729-
dim = kwargs_to_ctypes_array("dim", kwargs, ctp.c_uint64 * 4)
730-
ranges = kwargs_to_ctypes_array("ranges", kwargs, ctp.c_double * 4)
731-
inc = kwargs_to_ctypes_array("inc", kwargs, ctp.c_double * 2)
738+
dim = sequence_to_ctypes_array(dim, ctp.c_uint64, 4)
739+
ranges = sequence_to_ctypes_array(ranges, ctp.c_double, 4)
740+
inc = sequence_to_ctypes_array(inc, ctp.c_double, 2)
732741

733742
# Use a NULL pointer (None) for existing data to indicate that the
734743
# container should be created empty. Fill it in later using put_vector
@@ -742,7 +751,7 @@ def create_data(self, family, geometry, mode, **kwargs):
742751
ranges,
743752
inc,
744753
registration_int,
745-
self._parse_pad(family, kwargs),
754+
self._parse_pad(family, pad),
746755
None,
747756
)
748757

@@ -751,15 +760,14 @@ def create_data(self, family, geometry, mode, **kwargs):
751760

752761
return data_ptr
753762

754-
def _parse_pad(self, family, kwargs):
763+
def _parse_pad(self, family, pad):
755764
"""
756765
Parse and return an appropriate value for pad if none is given.
757766
758767
Pad is a bit tricky because, for matrix types, pad control the matrix ordering
759768
(row or column major). Using the default pad will set it to column major and
760769
mess things up with the numpy arrays.
761770
"""
762-
pad = kwargs.get("pad", None)
763771
if pad is None:
764772
pad = 0 if "MATRIX" in family else self["GMT_PAD_DEFAULT"]
765773
return pad
@@ -918,13 +926,9 @@ def put_vector(self, dataset, column, vector):
918926

919927
gmt_type = self._check_dtype_and_dim(vector, ndim=1)
920928
if gmt_type in (self["GMT_TEXT"], self["GMT_DATETIME"]):
921-
vector_pointer = (ctp.c_char_p * len(vector))()
922929
if gmt_type == self["GMT_DATETIME"]:
923-
vector_pointer[:] = np.char.encode(
924-
np.datetime_as_string(array_to_datetime(vector))
925-
)
926-
else:
927-
vector_pointer[:] = np.char.encode(vector)
930+
vector = np.datetime_as_string(array_to_datetime(vector))
931+
vector_pointer = strings_to_ctypes_array(vector)
928932
else:
929933
vector_pointer = vector.ctypes.data_as(ctp.c_void_p)
930934
status = c_put_vector(
@@ -981,13 +985,12 @@ def put_strings(self, dataset, family, strings):
981985
restype=ctp.c_int,
982986
)
983987

984-
strings_pointer = (ctp.c_char_p * len(strings))()
985-
strings_pointer[:] = np.char.encode(strings)
986-
987988
family_int = self._parse_constant(
988989
family, valid=FAMILIES, valid_modifiers=METHODS
989990
)
990991

992+
strings_pointer = strings_to_ctypes_array(strings)
993+
991994
status = c_put_strings(
992995
self.session_pointer, family_int, dataset, strings_pointer
993996
)
@@ -1108,7 +1111,7 @@ def write_data(self, family, geometry, mode, wesn, output, data):
11081111
self["GMT_IS_FILE"],
11091112
geometry_int,
11101113
self[mode],
1111-
(ctp.c_double * 6)(*wesn),
1114+
sequence_to_ctypes_array(wesn, ctp.c_double, 6),
11121115
output.encode(),
11131116
data,
11141117
)

pygmt/tests/test_blockm.py

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def test_blockmean_input_filename():
8282
data="@tut_ship.xyz",
8383
spacing="5m",
8484
region=[245, 255, 20, 30],
85+
output_type="file",
8586
outfile=tmpfile.name,
8687
)
8788
assert output is None # check that output is None since outfile is set

pygmt/tests/test_blockmedian.py

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def test_blockmedian_input_filename():
7979
data="@tut_ship.xyz",
8080
spacing="5m",
8181
region=[245, 255, 20, 30],
82+
output_type="file",
8283
outfile=tmpfile.name,
8384
)
8485
assert output is None # check that output is None since outfile is set

pygmt/tests/test_grdtrack.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ def test_grdtrack_input_csvfile_and_dataarray(dataarray, expected_array):
7070
Run grdtrack by passing in a csvfile and xarray.DataArray as inputs.
7171
"""
7272
with GMTTempFile() as tmpfile:
73-
output = grdtrack(points=POINTS_DATA, grid=dataarray, outfile=tmpfile.name)
73+
output = grdtrack(
74+
points=POINTS_DATA, grid=dataarray, output_type="file", outfile=tmpfile.name
75+
)
7476
assert output is None # check that output is None since outfile is set
7577
assert Path(tmpfile.name).stat().st_size > 0 # check that outfile exists
7678
output = np.loadtxt(tmpfile.name)

pygmt/tests/test_project.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def test_project_output_filename(dataframe):
6262
center=[0, -1],
6363
azimuth=45,
6464
flat_earth=True,
65+
output_type="file",
6566
outfile=tmpfile.name,
6667
)
6768
assert output is None # check that output is None since outfile is set

pygmt/tests/test_select.py

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def test_select_input_filename():
5656
data="@tut_ship.xyz",
5757
region=[250, 251, 26, 27],
5858
z_subregion=["-/-630", "-120/0+a"],
59+
output_type="file",
5960
outfile=tmpfile.name,
6061
)
6162
assert output is None # check that output is None since outfile is set

0 commit comments

Comments
 (0)