99from pygmt ._typing import PathLike
1010from pygmt .alias import Alias , AliasSystem
1111from pygmt .clib import Session
12- from pygmt .exceptions import GMTParameterError
12+ from pygmt .exceptions import GMTInvalidInput , GMTParameterError
1313from pygmt .helpers import build_arg_list , fmt_docstring , use_alias
1414
1515__doctest_skip__ = ["grdgradient" ]
1616
1717
18+ def _alias_option_N ( # noqa: N802
19+ normalize = False ,
20+ norm_amp = None ,
21+ norm_ambient = None ,
22+ norm_sigma = None ,
23+ norm_offset = None ,
24+ ):
25+ """
26+ Helper function to create the alias list for the -N option.
27+
28+ Examples
29+ --------
30+ >>> def parse(**kwargs):
31+ ... return AliasSystem(N=_alias_option_N(**kwargs)).get("N")
32+ >>> parse(normalize=True)
33+ ''
34+ >>> parse(normalize="laplace")
35+ 'e'
36+ >>> parse(normalize="cauchy")
37+ 't'
38+ >>> parse(
39+ ... normalize="laplace",
40+ ... norm_amp=2,
41+ ... norm_offset=10,
42+ ... norm_sigma=0.5,
43+ ... norm_ambient=0.1,
44+ ... )
45+ 'e2+a0.1+s0.5+o10'
46+ >>> # Check for backward compatibility with old syntax
47+ >>> parse(normalize="e2+a0.2+s0.5+o10")
48+ 'e2+a0.2+s0.5+o10'
49+ """
50+ _normalize_mapping = {"laplace" : "e" , "cauchy" : "t" }
51+ # Check for old syntax for normalize
52+ if isinstance (normalize , str ) and normalize not in _normalize_mapping :
53+ if any (
54+ v is not None and v is not False
55+ for v in [norm_amp , norm_ambient , norm_sigma , norm_offset ]
56+ ):
57+ msg = (
58+ "Parameter 'normalize' using old syntax with raw GMT CLI string "
59+ "conflicts with parameters 'norm_amp', 'norm_ambient', 'norm_sigma', "
60+ "or 'norm_offset'."
61+ )
62+ raise GMTInvalidInput (msg )
63+ _normalize_mapping = None
64+
65+ return [
66+ Alias (normalize , name = "normalize" , mapping = _normalize_mapping ),
67+ Alias (norm_amp , name = "norm_amp" ),
68+ Alias (norm_ambient , name = "norm_ambient" , prefix = "+a" ),
69+ Alias (norm_sigma , name = "norm_sigma" , prefix = "+s" ),
70+ Alias (norm_offset , name = "norm_offset" , prefix = "+o" ),
71+ ]
72+
73+
1874@fmt_docstring
19- @use_alias (
20- D = "direction" ,
21- N = "normalize" ,
22- Q = "tiles" ,
23- S = "slope_file" ,
24- f = "coltypes" ,
25- n = "interpolation" ,
26- )
27- def grdgradient (
75+ @use_alias (D = "direction" , Q = "tiles" , S = "slope_file" , f = "coltypes" , n = "interpolation" )
76+ def grdgradient ( # noqa: PLR0913
2877 grid : PathLike | xr .DataArray ,
2978 outgrid : PathLike | None = None ,
3079 azimuth : float | Sequence [float ] | None = None ,
3180 radiance : Sequence [float ] | str | None = None ,
81+ normalize : Literal ["laplace" , "cauchy" ] | bool = False ,
82+ norm_amp : float | None = None ,
83+ norm_ambient : float | None = None ,
84+ norm_sigma : float | None = None ,
85+ norm_offset : float | None = None ,
3286 region : Sequence [float | str ] | str | None = None ,
3387 verbose : Literal ["quiet" , "error" , "warning" , "timing" , "info" , "compat" , "debug" ]
3488 | bool = False ,
@@ -49,6 +103,12 @@ def grdgradient(
49103 - R = region
50104 - V = verbose
51105
106+ .. hlist::
107+ :columns: 1
108+
109+ - N = normalize, norm_amp, **+a**: norm_ambient, **+s**: norm_sigma,
110+ **+o**: norm_offset
111+
52112 Parameters
53113 ----------
54114 $grid
@@ -101,31 +161,37 @@ def grdgradient(
101161 algorithm; in this case *azim* and *elev* are hardwired to 315
102162 and 45 degrees. This means that even if you provide other values
103163 they will be ignored.).
104- normalize : str or bool
105- [**e**\|\ **t**][*amp*][**+a**\ *ambient*][**+s**\ *sigma*]\
106- [**+o**\ *offset*].
107- The actual gradients :math:`g` are offset and scaled to produce
108- normalized gradients :math:`g_n` with a maximum output magnitude of
109- *amp*. If *amp* is not given, default *amp* = 1. If *offset* is not
110- given, it is set to the average of :math:`g`. The following forms are
111- supported:
112-
113- - **True**: Normalize using math:`g_n = \mbox{amp}\
114- (\frac{g - \mbox{offset}}{max(|g - \mbox{offset}|)})`
115- - **e**: Normalize using a cumulative Laplace distribution yielding:
116- :math:`g_n = \mbox{amp}(1 - \
117- \exp{(\sqrt{2}\frac{g - \mbox{offset}}{\sigma}))}`, where
118- :math:`\sigma` is estimated using the L1 norm of
119- :math:`(g - \mbox{offset})` if it is not given.
120- - **t**: Normalize using a cumulative Cauchy distribution yielding:
121- :math:`g_n = \
122- \frac{2(\mbox{amp})}{\pi}(\tan^{-1}(\frac{g - \
123- \mbox{offset}}{\sigma}))` where :math:`\sigma` is estimated
124- using the L2 norm of :math:`(g - \mbox{offset})` if it is not
125- given.
126-
127- As a final option, you may add **+a**\ *ambient* to add *ambient* to
128- all nodes after gradient calculations are completed.
164+ normalize
165+ Normalize the output gradients. Valid values are:
166+
167+ - ``False``: No normalization is done [Default].
168+ - ``True``: Normalize using max absolute value.
169+ - ``"laplace"``: Normalize using cumulative Laplace distribution.
170+ - ``"cauchy"``: Normalize using cumulative Cauchy distribution.
171+
172+ The normalization process is controlled via the additional parameters
173+ ``norm_amp``, ``norm_ambient``, ``norm_sigma``, and ``norm_offset``.
174+
175+ Let :math:`g` denote the actual gradients, :math:`g_n` the normalized gradients,
176+ :math:`a` the maximum output magnitude (``norm_amp``), :math:`o` the offset
177+ value (``norm_offset``), and :math:`\sigma` the sigma value (``norm_sigma``).
178+ The normalization is computed as follows:
179+
180+ - ``True``: :math:`g_n = a (\frac{g - o}{\max(|g - o|)})`
181+ - ``"laplace"``: :math:`g_n = a(1 - \exp(\sqrt{2}\frac{g - o}{\sigma}))`
182+ - ``"cauchy"``: :math:`g_n = \frac{2a}{\pi}\arctan(\frac{g - o}{\sigma})`
183+ norm_amp
184+ Set the maximum output magnitude [Default is 1].
185+ norm_ambient
186+ The ambient value to add to all nodes after gradient calculations are completed
187+ [Default is 0].
188+ norm_offset
189+ The offset value used in the normalization. If not given, it is set to the
190+ average of :math:`g`.
191+ norm_sigma
192+ The *sigma* value used in the Laplace or Cauchy normalization. If not given,
193+ it is estimated from the L1 norm of :math:`g-o` for Laplace or the L2 norm of
194+ :math:`g-o` for Cauchy.
129195 tiles : str
130196 **c**\|\ **r**\|\ **R**.
131197 Control how normalization via ``normalize`` is carried out. When
@@ -137,10 +203,10 @@ def grdgradient(
137203 grid output is not needed for this run then do not specify
138204 ``outgrid``. For subsequent runs, just use **r** to read these
139205 values. Using **R** will read then delete the statistics file.
140- $region
141206 slope_file : str
142207 Name of output grid file with scalar magnitudes of gradient vectors.
143208 Requires ``direction`` but makes ``outgrid`` optional.
209+ $region
144210 $verbose
145211 $coltypes
146212 $interpolation
@@ -179,6 +245,13 @@ def grdgradient(
179245 aliasdict = AliasSystem (
180246 A = Alias (azimuth , name = "azimuth" , sep = "/" , size = 2 ),
181247 E = Alias (radiance , name = "radiance" , sep = "/" , size = 2 ),
248+ N = _alias_option_N (
249+ normalize = normalize ,
250+ norm_amp = norm_amp ,
251+ norm_ambient = norm_ambient ,
252+ norm_sigma = norm_sigma ,
253+ norm_offset = norm_offset ,
254+ ),
182255 ).add_common (
183256 R = region ,
184257 V = verbose ,
0 commit comments