22grdclip - Clip the range of grid values.
33"""
44
5+ from collections .abc import Sequence
6+
57import xarray as xr
68from pygmt .clib import Session
9+ from pygmt .exceptions import GMTInvalidInput
710from 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 ,
0 commit comments