Skip to content

Commit 502bf63

Browse files
committed
pygmt.grdclip: Parameters 'between' and 'replace' now support 2-D sequences
1 parent 8e4cc96 commit 502bf63

File tree

2 files changed

+149
-36
lines changed

2 files changed

+149
-36
lines changed

Diff for: pygmt/src/grdclip.py

+134-36
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,129 @@
22
grdclip - Clip the range of grid values.
33
"""
44

5+
from collections.abc import Sequence
6+
57
import xarray as xr
68
from pygmt.clib import Session
9+
from pygmt.exceptions import GMTInvalidInput
710
from pygmt.helpers import (
811
build_arg_list,
912
deprecate_parameter,
1013
fmt_docstring,
14+
is_nonstr_iter,
1115
kwargs_to_strings,
1216
use_alias,
1317
)
1418

1519
__doctest_skip__ = ["grdclip"]
1620

1721

22+
def _parse_sequence(name, value, separator="/", size=2, ndim=1):
23+
"""
24+
Parse a 1-D or 2-D sequence of values and join them by a separator.
25+
26+
Parameters
27+
----------
28+
name
29+
The parameter name.
30+
value
31+
The 1-D or 2-D sequence of values to parse.
32+
separator
33+
The separator to join the values.
34+
size
35+
The number of values in the sequence.
36+
ndim
37+
The expected maximum number of dimensions of the sequence.
38+
39+
Returns
40+
-------
41+
str
42+
The parsed sequence.
43+
44+
Examples
45+
--------
46+
>>> _parse_sequence("above_or_below", [1000, 0], size=2, ndim=1)
47+
'1000/0'
48+
>>> _parse_sequence("between", [1000, 1500, 10000], size=3, ndim=2)
49+
'1000/1500/10000'
50+
>>> _parse_sequence("between", [[1000, 1500, 10000]], size=3, ndim=2)
51+
['1000/1500/10000']
52+
>>> _parse_sequence(
53+
... "between", [[1000, 1500, 10000], [1500, 2000, 20000]], size=3, ndim=2
54+
... )
55+
['1000/1500/10000', '1500/2000/20000']
56+
>>> _parse_sequence("replace", [1000, 0], size=2, ndim=2)
57+
'1000/0'
58+
>>> _parse_sequence("replace", [[1000, 0]], size=2, ndim=2)
59+
['1000/0']
60+
>>> _parse_sequence("replace", [[1000, 0], [1500, 10000]], size=2, ndim=2)
61+
['1000/0', '1500/10000']
62+
>>> _parse_sequence("any", "1000/100")
63+
'1000/100'
64+
>>> _parse_sequence("any", None)
65+
>>> _parse_sequence("any", [])
66+
[]
67+
>>> _parse_sequence("above_or_below", [[100, 1000], [1500, 2000]], size=2, ndim=1)
68+
Traceback (most recent call last):
69+
...
70+
pygmt.exceptions.GMTInvalidInput: Parameter ... must be a 1-D sequence...
71+
"""
72+
# Return the value as is if not a sequence (e.g., str or None) or empty.
73+
if not is_nonstr_iter(value) or len(value) == 0:
74+
return value
75+
76+
# 1-D sequence
77+
if not is_nonstr_iter(value[0]):
78+
if len(value) != size:
79+
msg = (
80+
f"Parameter '{name}' must be a 1-D sequence of {size} values, "
81+
f"but got {len(value)} values."
82+
)
83+
raise GMTInvalidInput(msg)
84+
return separator.join(str(i) for i in value)
85+
86+
# 2-D sequence
87+
if ndim == 1:
88+
msg = f"Parameter '{name}' must be a 1-D sequence, not a 2-D sequence."
89+
raise GMTInvalidInput(msg)
90+
91+
if any(len(i) != size for i in value):
92+
msg = (
93+
f"Parameter '{name}' must be a 2-D sequence with each sub-sequence "
94+
f"having {size} values."
95+
)
96+
raise GMTInvalidInput(msg)
97+
return [separator.join(str(j) for j in value[i]) for i in range(len(value))]
98+
99+
18100
# TODO(PyGMT>=0.19.0): Remove the deprecated "new" parameter.
19101
@fmt_docstring
20102
@deprecate_parameter("new", "replace", "v0.15.0", remove_version="v0.19.0")
21-
@use_alias(
22-
R="region",
23-
Sa="above",
24-
Sb="below",
25-
Si="between",
26-
Sr="replace",
27-
V="verbose",
28-
)
29-
@kwargs_to_strings(
30-
R="sequence",
31-
Sa="sequence",
32-
Sb="sequence",
33-
Si="sequence",
34-
Sr="sequence",
35-
)
36-
def grdclip(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | None:
37-
r"""
103+
@use_alias(R="region", V="verbose")
104+
@kwargs_to_strings(R="sequence")
105+
def grdclip(
106+
grid,
107+
outgrid: str | None = None,
108+
above: Sequence[float] | None = None,
109+
below: Sequence[float] | None = None,
110+
between: Sequence[float] | Sequence[Sequence[float]] | None = None,
111+
replace: Sequence[float] | Sequence[Sequence[float]] | None = None,
112+
**kwargs,
113+
) -> xr.DataArray | None:
114+
"""
38115
Clip the range of grid values.
39116
40-
Produce a clipped ``outgrid`` or :class:`xarray.DataArray` version of the
41-
input ``grid`` file.
117+
This function operates on the values of a grid. It can:
118+
119+
- Set values smaller than a threshold to a new value
120+
- Set values larger than a threshold to a new value
121+
- Set values within a range to a new value
122+
- Replace individual values with a new value
42123
43-
The parameters ``above`` and ``below`` allow for a given value to be set
44-
for values above or below a set amount, respectively. This allows for
45-
extreme values in a grid, such as points below a certain depth when
46-
plotting Earth relief, to all be set to the same value.
124+
Such operations are useful when you want all of a continent or an ocean to fall into
125+
one color or gray shade in image processing, when clipping of the range of data
126+
values is required, or for reclassification of data values. The values can be any
127+
number or even NaN (Not a Number).
47128
48129
Full option list at :gmt-docs:`grdclip.html`
49130
@@ -54,19 +135,23 @@ def grdclip(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | None:
54135
{grid}
55136
{outgrid}
56137
{region}
57-
above : str or list
58-
[*high*, *above*].
59-
Set all data[i] > *high* to *above*.
60-
below : str or list
61-
[*low*, *below*].
62-
Set all data[i] < *low* to *below*.
63-
between : str or list
64-
[*low*, *high*, *between*].
65-
Set all data[i] >= *low* and <= *high* to *between*.
66-
replace : str or list
67-
[*old*, *new*].
68-
Set all data[i] == *old* to *new*. This is mostly useful when
69-
your data are known to be integer values.
138+
above
139+
Pass a sequence of two values in the form of (*high*, *above*), to set all node
140+
values greater than *high* to *above*.
141+
below
142+
Pass a sequence of two values in the form of (*low*, *below*) to set all node
143+
values less than *low* to *below*.
144+
between
145+
Pass a sequence of three values in the form of (*low*, *high*, *between*) to set
146+
all node values between *low* and *high* to *between*. It can also accept a
147+
sequence of sequences (e.g., list of lists or 2-D numpy array) to set different
148+
values for different ranges.
149+
replace
150+
Pass a sequence of two values in the form of (*old*, *new*) to replace all node
151+
values equal to *old* with *new*. It can also accept a sequence of sequences
152+
(e.g., list of lists or 2-D numpy array) to replace different old values with
153+
different new values. This is mostly useful when your data are known to be
154+
integer values.
70155
{verbose}
71156
72157
Returns
@@ -96,6 +181,19 @@ def grdclip(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | None:
96181
>>> [new_grid.data.min(), new_grid.data.max()]
97182
[0.0, 10000.0]
98183
"""
184+
if all(v is None for v in (above, below, between, replace)):
185+
msg = (
186+
"Must specify at least one of the following parameters: ",
187+
"'above', 'below', 'between', or 'replace'.",
188+
)
189+
raise GMTInvalidInput(msg)
190+
191+
# Parse the -S option.
192+
kwargs["Sa"] = _parse_sequence("above", above, size=2, ndim=1)
193+
kwargs["Sb"] = _parse_sequence("below", below, size=2, ndim=1)
194+
kwargs["Si"] = _parse_sequence("between", between, size=3, ndim=2)
195+
kwargs["Sr"] = _parse_sequence("replace", replace, size=2, ndim=2)
196+
99197
with Session() as lib:
100198
with (
101199
lib.virtualfile_in(check_kind="raster", data=grid) as vingrd,

Diff for: pygmt/tests/test_grdclip.py

+15
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,18 @@ def test_grdclip_replace():
8888
with pytest.warns(FutureWarning):
8989
grid = grdclip(grid=grid, new=[1, 3]) # Replace 1 with 3
9090
npt.assert_array_equal(np.unique(grid), [2, 3])
91+
92+
93+
def test_grdclip_between_repeated():
94+
"""
95+
Test passing a 2D sequence to the between parameter for grdclip.
96+
"""
97+
grid = load_static_earth_relief()
98+
# Replace values in the range 0-250 with 0, 250-500 with 1, 500-750 with 2, and
99+
# 750-1000 with 3
100+
result = grdclip(
101+
grid,
102+
between=[[0, 250, 0], [250, 500, 1], [500, 750, 2], [750, 1000, 3]],
103+
)
104+
# result should have 4 unique values.
105+
npt.assert_array_equal(np.unique(result.data), [0, 1, 2, 3])

0 commit comments

Comments
 (0)