Skip to content

Commit 4186400

Browse files
Meghan Jonesweiji14seisman
authored
Wrap grdhisteq (#1433)
Co-authored-by: Wei Ji <[email protected]> Co-authored-by: Dongdong Tian <[email protected]>
1 parent b5fa22b commit 4186400

File tree

5 files changed

+491
-0
lines changed

5 files changed

+491
-0
lines changed

doc/api/index.rst

+3
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ Operations on raster data
138138
grdfill
139139
grdfilter
140140
grdgradient
141+
grdhisteq
142+
grdhisteq.equalize_grid
143+
grdhisteq.compute_bins
141144
grdlandmask
142145
grdproject
143146
grdsample

pygmt/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
grdfill,
4141
grdfilter,
4242
grdgradient,
43+
grdhisteq,
4344
grdinfo,
4445
grdlandmask,
4546
grdproject,

pygmt/src/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pygmt.src.grdfill import grdfill
1818
from pygmt.src.grdfilter import grdfilter
1919
from pygmt.src.grdgradient import grdgradient
20+
from pygmt.src.grdhisteq import grdhisteq
2021
from pygmt.src.grdimage import grdimage
2122
from pygmt.src.grdinfo import grdinfo
2223
from pygmt.src.grdlandmask import grdlandmask

pygmt/src/grdhisteq.py

+349
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
"""
2+
grdhisteq - Perform histogram equalization for a grid.
3+
"""
4+
import warnings
5+
6+
import numpy as np
7+
import pandas as pd
8+
from pygmt.clib import Session
9+
from pygmt.exceptions import GMTInvalidInput
10+
from pygmt.helpers import (
11+
GMTTempFile,
12+
build_arg_string,
13+
fmt_docstring,
14+
kwargs_to_strings,
15+
use_alias,
16+
)
17+
from pygmt.io import load_dataarray
18+
19+
__doctest_skip__ = ["grdhisteq.*"]
20+
21+
22+
class grdhisteq: # pylint: disable=invalid-name
23+
r"""
24+
Perform histogram equalization for a grid.
25+
26+
Two common use cases of :meth:`pygmt.grdhisteq` are to find data values
27+
that divide a grid into patches of equal area
28+
(:meth:`pygmt.grdhisteq.compute_bins`) or to write a grid with
29+
statistics based on some kind of cumulative distribution function
30+
(:meth:`pygmt.grdhisteq.equalize_grid`).
31+
32+
Histogram equalization provides a way to highlight data that has most
33+
values clustered in a small portion of the dynamic range, such as a
34+
grid of flat topography with a mountain in the middle. Ordinary gray
35+
shading of this grid (using :meth:`pygmt.Figure.grdimage` or
36+
:meth:`pygmt.Figure.grdview`) with a linear mapping from topography to
37+
graytone will result in most of the image being very dark gray, with the
38+
mountain being almost white. :meth:`pygmt.grdhisteq.compute_bins` can
39+
provide a list of data values that divide the data range into divisions
40+
which have an equal area in the image [Default is 16 if ``divisions`` is
41+
not set]. The :class:`pandas.DataFrame` or ASCII file output can be used to
42+
make a colormap with :meth:`pygmt.makecpt` and an image with
43+
:meth:`pygmt.Figure.grdimage` that has all levels of gray occuring
44+
equally.
45+
46+
:meth:`pygmt.grdhisteq.equalize_grid` provides a way to write a grid with
47+
statistics based on a cumulative distribution function. In this
48+
application, the ``outgrid`` has relative highs and lows in the same
49+
(x,y) locations as the ``grid``, but the values are changed to reflect
50+
their place in the cumulative distribution.
51+
"""
52+
53+
@staticmethod
54+
@fmt_docstring
55+
@use_alias(
56+
C="divisions",
57+
D="outfile",
58+
G="outgrid",
59+
R="region",
60+
N="gaussian",
61+
Q="quadratic",
62+
V="verbose",
63+
h="header",
64+
)
65+
@kwargs_to_strings(R="sequence")
66+
def _grdhisteq(grid, output_type, **kwargs):
67+
r"""
68+
Perform histogram equalization for a grid.
69+
70+
Must provide ``outfile`` or ``outgrid``.
71+
72+
Full option list at :gmt-docs:`grdhisteq.html`
73+
74+
{aliases}
75+
76+
Parameters
77+
----------
78+
grid : str or xarray.DataArray
79+
The file name of the input grid or the grid loaded as a DataArray.
80+
outgrid : str or bool or None
81+
The name of the output netCDF file with extension .nc to store the
82+
grid in.
83+
outfile : str or bool or None
84+
The name of the output ASCII file to store the results of the
85+
histogram equalization in.
86+
output_type: str
87+
Determines the output type. Use "file", "xarray", "pandas", or
88+
"numpy".
89+
divisions : int
90+
Set the number of divisions of the data range [Default is 16].
91+
92+
{R}
93+
{V}
94+
{h}
95+
96+
Returns
97+
-------
98+
ret: pandas.DataFrame or xarray.DataArray or None
99+
Return type depends on whether the ``outgrid`` parameter is set:
100+
101+
- xarray.DataArray if ``output_type`` is "xarray""
102+
- numpy.ndarray if ``output_type`` is "numpy"
103+
- pandas.DataFrame if ``output_type`` is "pandas"
104+
- None if ``output_type`` is "file" (output is stored in
105+
``outgrid`` or ``outfile``)
106+
107+
See Also
108+
-------
109+
:meth:`pygmt.grd2cpt`
110+
"""
111+
112+
with Session() as lib:
113+
file_context = lib.virtualfile_from_data(check_kind="raster", data=grid)
114+
with file_context as infile:
115+
arg_str = " ".join([infile, build_arg_string(kwargs)])
116+
lib.call_module("grdhisteq", arg_str)
117+
118+
if output_type == "file":
119+
return None
120+
if output_type == "xarray":
121+
return load_dataarray(kwargs["G"])
122+
123+
result = pd.read_csv(
124+
filepath_or_buffer=kwargs["D"],
125+
sep="\t",
126+
header=None,
127+
names=["start", "stop", "bin_id"],
128+
dtype={
129+
"start": np.float32,
130+
"stop": np.float32,
131+
"bin_id": np.uint32,
132+
},
133+
)
134+
if output_type == "numpy":
135+
return result.to_numpy()
136+
137+
return result.set_index("bin_id")
138+
139+
@staticmethod
140+
@fmt_docstring
141+
def equalize_grid(
142+
grid,
143+
*,
144+
outgrid=True,
145+
divisions=None,
146+
region=None,
147+
gaussian=None,
148+
quadratic=None,
149+
verbose=None,
150+
):
151+
r"""
152+
Perform histogram equalization for a grid.
153+
154+
:meth:`pygmt.grdhisteq.equalize_grid` provides a way to write a grid
155+
with statistics based on a cumulative distribution function. The
156+
``outgrid`` has relative highs and lows in the same (x,y) locations as
157+
the ``grid``, but the values are changed to reflect their place in the
158+
cumulative distribution.
159+
160+
Full option list at :gmt-docs:`grdhisteq.html`
161+
162+
Parameters
163+
----------
164+
grid : str or xarray.DataArray
165+
The file name of the input grid or the grid loaded as a DataArray.
166+
outgrid : str or bool or None
167+
The name of the output netCDF file with extension .nc to store the
168+
grid in.
169+
divisions : int
170+
Set the number of divisions of the data range.
171+
gaussian : bool or int or float
172+
*norm*.
173+
Produce an output grid with standard normal scores using
174+
``gaussian=True`` or force the scores to fall in the ±\ *norm*
175+
range.
176+
quadratic: bool
177+
Perform quadratic equalization [Default is linear].
178+
{R}
179+
{V}
180+
181+
Returns
182+
-------
183+
ret: xarray.DataArray or None
184+
Return type depends on the ``outgrid`` parameter:
185+
186+
- xarray.DataArray if ``outgrid`` is True or None
187+
- None if ``outgrid`` is a str (grid output is stored in
188+
``outgrid``)
189+
190+
Example
191+
-------
192+
>>> import pygmt
193+
>>> # Load a grid of @earth_relief_30m data, with an x-range of 10 to
194+
>>> # 30, and a y-range of 15 to 25
195+
>>> grid = pygmt.datasets.load_earth_relief(
196+
... resolution="30m", region=[10, 30, 15, 25]
197+
... )
198+
>>> # Create a new grid with a Gaussian data distribution
199+
>>> grid = pygmt.grdhisteq.equalize_grid(grid=grid, gaussian=True)
200+
201+
See Also
202+
-------
203+
:meth:`pygmt.grd2cpt`
204+
205+
Notes
206+
-----
207+
This method does a weighted histogram equalization for geographic
208+
grids to account for node area varying with latitude.
209+
"""
210+
# Return an xarray.DataArray if ``outgrid`` is not set
211+
with GMTTempFile(suffix=".nc") as tmpfile:
212+
if isinstance(outgrid, str):
213+
output_type = "file"
214+
else:
215+
output_type = "xarray"
216+
outgrid = tmpfile.name
217+
return grdhisteq._grdhisteq(
218+
grid=grid,
219+
output_type=output_type,
220+
outgrid=outgrid,
221+
divisions=divisions,
222+
region=region,
223+
gaussian=gaussian,
224+
quadratic=quadratic,
225+
verbose=verbose,
226+
)
227+
228+
@staticmethod
229+
@fmt_docstring
230+
def compute_bins(
231+
grid,
232+
*,
233+
output_type="pandas",
234+
outfile=None,
235+
divisions=None,
236+
quadratic=None,
237+
verbose=None,
238+
region=None,
239+
header=None,
240+
):
241+
r"""
242+
Perform histogram equalization for a grid.
243+
244+
Histogram equalization provides a way to highlight data that has most
245+
values clustered in a small portion of the dynamic range, such as a
246+
grid of flat topography with a mountain in the middle. Ordinary gray
247+
shading of this grid (using :meth:`pygmt.Figure.grdimage` or
248+
:meth:`pygmt.Figure.grdview`) with a linear mapping from topography to
249+
graytone will result in most of the image being very dark gray, with
250+
the mountain being almost white. :meth:`pygmt.grdhisteq.compute_bins`
251+
can provide a list of data values that divide the data range into
252+
divisions which have an equal area in the image [Default is 16 if
253+
``divisions`` is not set]. The :class:`pandas.DataFrame` or ASCII file
254+
output can be used to make a colormap with :meth:`pygmt.makecpt` and an
255+
image with :meth:`pygmt.Figure.grdimage` that has all levels of gray
256+
occuring equally.
257+
258+
Full option list at :gmt-docs:`grdhisteq.html`
259+
260+
Parameters
261+
----------
262+
grid : str or xarray.DataArray
263+
The file name of the input grid or the grid loaded as a DataArray.
264+
outfile : str or bool or None
265+
The name of the output ASCII file to store the results of the
266+
histogram equalization in.
267+
output_type : str
268+
Determine the format the xyz data will be returned in [Default is
269+
``pandas``]:
270+
271+
- ``numpy`` - :class:`numpy.ndarray`
272+
- ``pandas``- :class:`pandas.DataFrame`
273+
- ``file`` - ASCII file (requires ``outfile``)
274+
divisions : int
275+
Set the number of divisions of the data range.
276+
quadratic : bool
277+
Perform quadratic equalization [Default is linear].
278+
{R}
279+
{V}
280+
{h}
281+
282+
Returns
283+
-------
284+
ret: pandas.DataFrame or None
285+
Return type depends on the ``outfile`` parameter:
286+
287+
- pandas.DataFrame if ``outfile`` is True or None
288+
- None if ``outfile`` is a str (file output is stored in
289+
``outfile``)
290+
291+
Example
292+
-------
293+
>>> import pygmt
294+
>>> # Load a grid of @earth_relief_30m data, with an x-range of 10 to
295+
>>> # 30, and a y-range of 15 to 25
296+
>>> grid = pygmt.datasets.load_earth_relief(
297+
... resolution="30m", region=[10, 30, 15, 25]
298+
... )
299+
>>> # Find elevation intervals that splits the data range into 5
300+
>>> # divisions, each of which have an equal area in the original grid.
301+
>>> bins = pygmt.grdhisteq.compute_bins(grid=grid, divisions=5)
302+
>>> print(bins)
303+
start stop
304+
bin_id
305+
0 179.0 397.5
306+
1 397.5 475.5
307+
2 475.5 573.5
308+
3 573.5 710.5
309+
4 710.5 2103.0
310+
311+
See Also
312+
-------
313+
:meth:`pygmt.grd2cpt`
314+
315+
Notes
316+
-----
317+
This method does a weighted histogram equalization for geographic
318+
grids to account for node area varying with latitude.
319+
"""
320+
# Return a pandas.DataFrame if ``outfile`` is not set
321+
if output_type not in ["numpy", "pandas", "file"]:
322+
raise GMTInvalidInput(
323+
"Must specify 'output_type' either as 'numpy', 'pandas' or 'file'."
324+
)
325+
326+
if header is not None and output_type != "file":
327+
raise GMTInvalidInput("'header' is only allowed with output_type='file'.")
328+
329+
if isinstance(outfile, str) and output_type != "file":
330+
msg = (
331+
f"Changing 'output_type' from '{output_type}' to 'file' "
332+
"since 'outfile' parameter is set. Please use output_type='file' "
333+
"to silence this warning."
334+
)
335+
warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2)
336+
output_type = "file"
337+
with GMTTempFile(suffix=".txt") as tmpfile:
338+
if output_type != "file":
339+
outfile = tmpfile.name
340+
return grdhisteq._grdhisteq(
341+
grid,
342+
output_type=output_type,
343+
outfile=outfile,
344+
divisions=divisions,
345+
quadratic=quadratic,
346+
verbose=verbose,
347+
region=region,
348+
header=header,
349+
)

0 commit comments

Comments
 (0)