22grdclip - Clip the range of grid values.
33"""
44
5+ from collections .abc import Sequence
6+
57import xarray as xr
68from pygmt ._typing import PathLike
79from pygmt .clib import Session
10+ from pygmt .exceptions import GMTInvalidInput
811from pygmt .helpers import (
912 build_arg_list ,
1013 deprecate_parameter ,
1114 fmt_docstring ,
15+ is_nonstr_iter ,
1216 kwargs_to_strings ,
1317 use_alias ,
1418)
1519
1620__doctest_skip__ = ["grdclip" ]
1721
1822
23+ def _parse_sequence (name , value , separator = "/" , size = 2 , ndim = 1 ):
24+ """
25+ Parse a 1-D or 2-D sequence of values and join them by a separator.
26+
27+ Parameters
28+ ----------
29+ name
30+ The parameter name.
31+ value
32+ The 1-D or 2-D sequence of values to parse.
33+ separator
34+ The separator to join the values.
35+ size
36+ The number of values in the sequence.
37+ ndim
38+ The expected maximum number of dimensions of the sequence.
39+
40+ Returns
41+ -------
42+ str
43+ The parsed sequence.
44+
45+ Examples
46+ --------
47+ >>> _parse_sequence("above_or_below", [1000, 0], size=2, ndim=1)
48+ '1000/0'
49+ >>> _parse_sequence("between", [1000, 1500, 10000], size=3, ndim=2)
50+ '1000/1500/10000'
51+ >>> _parse_sequence("between", [[1000, 1500, 10000]], size=3, ndim=2)
52+ ['1000/1500/10000']
53+ >>> _parse_sequence(
54+ ... "between", [[1000, 1500, 10000], [1500, 2000, 20000]], size=3, ndim=2
55+ ... )
56+ ['1000/1500/10000', '1500/2000/20000']
57+ >>> _parse_sequence("replace", [1000, 0], size=2, ndim=2)
58+ '1000/0'
59+ >>> _parse_sequence("replace", [[1000, 0]], size=2, ndim=2)
60+ ['1000/0']
61+ >>> _parse_sequence("replace", [[1000, 0], [1500, 10000]], size=2, ndim=2)
62+ ['1000/0', '1500/10000']
63+ >>> _parse_sequence("any", "1000/100")
64+ '1000/100'
65+ >>> _parse_sequence("any", None)
66+ >>> _parse_sequence("any", [])
67+ []
68+ >>> _parse_sequence("above_or_below", [[100, 1000], [1500, 2000]], size=2, ndim=1)
69+ Traceback (most recent call last):
70+ ...
71+ pygmt.exceptions.GMTInvalidInput: Parameter ... must be a 1-D sequence...
72+ >>> _parse_sequence("above_or_below", [100, 200, 300], size=2, ndim=1)
73+ Traceback (most recent call last):
74+ ...
75+ pygmt.exceptions.GMTInvalidInput: Parameter ... must be a 1-D sequence ...
76+ >>> _parse_sequence("between", [[100, 200, 300], [500, 600]], size=3, ndim=2)
77+ Traceback (most recent call last):
78+ ...
79+ pygmt.exceptions.GMTInvalidInput: Parameter ... must be a 2-D sequence with ...
80+ """
81+ # Return the value as is if not a sequence (e.g., str or None) or empty.
82+ if not is_nonstr_iter (value ) or len (value ) == 0 :
83+ return value
84+
85+ # 1-D sequence
86+ if not is_nonstr_iter (value [0 ]):
87+ if len (value ) != size :
88+ msg = (
89+ f"Parameter '{ name } ' must be a 1-D sequence of { size } values, "
90+ f"but got { len (value )} values."
91+ )
92+ raise GMTInvalidInput (msg )
93+ return separator .join (str (i ) for i in value )
94+
95+ # 2-D sequence
96+ if ndim == 1 :
97+ msg = f"Parameter '{ name } ' must be a 1-D sequence, not a 2-D sequence."
98+ raise GMTInvalidInput (msg )
99+
100+ if any (len (i ) != size for i in value ):
101+ msg = (
102+ f"Parameter '{ name } ' must be a 2-D sequence with each sub-sequence "
103+ f"having { size } values."
104+ )
105+ raise GMTInvalidInput (msg )
106+ return [separator .join (str (j ) for j in value [i ]) for i in range (len (value ))]
107+
108+
19109# TODO(PyGMT>=0.19.0): Remove the deprecated "new" parameter.
20110@fmt_docstring
21111@deprecate_parameter ("new" , "replace" , "v0.15.0" , remove_version = "v0.19.0" )
22- @use_alias (
23- R = "region" ,
24- Sa = "above" ,
25- Sb = "below" ,
26- Si = "between" ,
27- Sr = "replace" ,
28- V = "verbose" ,
29- )
30- @kwargs_to_strings (
31- R = "sequence" ,
32- Sa = "sequence" ,
33- Sb = "sequence" ,
34- Si = "sequence" ,
35- Sr = "sequence" ,
36- )
112+ @use_alias (R = "region" , V = "verbose" )
113+ @kwargs_to_strings (R = "sequence" )
37114def grdclip (
38115 grid : PathLike | xr .DataArray ,
39116 outgrid : PathLike | None = None ,
117+ above : Sequence [float ] | None = None ,
118+ below : Sequence [float ] | None = None ,
119+ between : Sequence [float ] | Sequence [Sequence [float ]] | None = None ,
120+ replace : Sequence [float ] | Sequence [Sequence [float ]] | None = None ,
40121 ** kwargs ,
41122) -> xr .DataArray | None :
42- r """
123+ """
43124 Clip the range of grid values.
44125
45- Produce a clipped ``outgrid`` or :class:`xarray.DataArray` version of the
46- input ``grid`` file.
126+ This function operates on the values of a grid. It can:
127+
128+ - Set values smaller than a threshold to a new value
129+ - Set values larger than a threshold to a new value
130+ - Set values within a range to a new value
131+ - Replace individual values with a new value
47132
48- The parameters ``above`` and ``below`` allow for a given value to be set
49- for values above or below a set amount, respectively. This allows for
50- extreme values in a grid, such as points below a certain depth when
51- plotting Earth relief, to all be set to the same value .
133+ Such operations are useful when you want all of a continent or an ocean to fall into
134+ one color or gray shade in image processing, when clipping the range of data
135+ values is required, or for reclassification of data values. The values can be any
136+ number or NaN (Not a Number) .
52137
53138 Full option list at :gmt-docs:`grdclip.html`
54139
@@ -59,19 +144,23 @@ def grdclip(
59144 {grid}
60145 {outgrid}
61146 {region}
62- above : str or list
63- [*high*, *above*].
64- Set all data[i] > *high* to *above*.
65- below : str or list
66- [*low*, *below*].
67- Set all data[i] < *low* to *below*.
68- between : str or list
69- [*low*, *high*, *between*].
70- Set all data[i] >= *low* and <= *high* to *between*.
71- replace : str or list
72- [*old*, *new*].
73- Set all data[i] == *old* to *new*. This is mostly useful when
74- your data are known to be integer values.
147+ above
148+ Pass a sequence of two values in the form of (*high*, *above*), to set all node
149+ values greater than *high* to *above*.
150+ below
151+ Pass a sequence of two values in the form of (*low*, *below*) to set all node
152+ values less than *low* to *below*.
153+ between
154+ Pass a sequence of three values in the form of (*low*, *high*, *between*) to set
155+ all node values between *low* and *high* to *between*. It can also accept a
156+ sequence of sequences (e.g., list of lists or 2-D numpy array) to set different
157+ values for different ranges.
158+ replace
159+ Pass a sequence of two values in the form of (*old*, *new*) to replace all node
160+ values equal to *old* with *new*. It can also accept a sequence of sequences
161+ (e.g., list of lists or 2-D numpy array) to replace different old values with
162+ different new values. This is mostly useful when your data are known to be
163+ integer values.
75164 {verbose}
76165
77166 Returns
@@ -101,6 +190,19 @@ def grdclip(
101190 >>> [new_grid.data.min(), new_grid.data.max()]
102191 [0.0, 10000.0]
103192 """
193+ if all (v is None for v in (above , below , between , replace )):
194+ msg = (
195+ "Must specify at least one of the following parameters: " ,
196+ "'above', 'below', 'between', or 'replace'." ,
197+ )
198+ raise GMTInvalidInput (msg )
199+
200+ # Parse the -S option.
201+ kwargs ["Sa" ] = _parse_sequence ("above" , above , size = 2 , ndim = 1 )
202+ kwargs ["Sb" ] = _parse_sequence ("below" , below , size = 2 , ndim = 1 )
203+ kwargs ["Si" ] = _parse_sequence ("between" , between , size = 3 , ndim = 2 )
204+ kwargs ["Sr" ] = _parse_sequence ("replace" , replace , size = 2 , ndim = 2 )
205+
104206 with Session () as lib :
105207 with (
106208 lib .virtualfile_in (check_kind = "raster" , data = grid ) as vingrd ,
0 commit comments