Skip to content

Commit 2304053

Browse files
willschlitzerMeghan Jonesseismanweiji14
authored
Wrap dimfilter (#1492)
Co-authored-by: Meghan Jones <[email protected]> Co-authored-by: Dongdong Tian <[email protected]> Co-authored-by: Wei Ji <[email protected]>
1 parent f619079 commit 2304053

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

doc/api/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Operations on raster data
135135
.. autosummary::
136136
:toctree: generated
137137

138+
dimfilter
138139
grd2xyz
139140
grdclip
140141
grdcut

pygmt/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
blockmedian,
3434
blockmode,
3535
config,
36+
dimfilter,
3637
grd2cpt,
3738
grd2xyz,
3839
grdclip,

pygmt/src/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pygmt.src.colorbar import colorbar
1010
from pygmt.src.config import config
1111
from pygmt.src.contour import contour
12+
from pygmt.src.dimfilter import dimfilter
1213
from pygmt.src.grd2cpt import grd2cpt
1314
from pygmt.src.grd2xyz import grd2xyz
1415
from pygmt.src.grdclip import grdclip

pygmt/src/dimfilter.py

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""
2+
dimfilter - Directional filtering of grids in the space domain.
3+
"""
4+
5+
from pygmt.clib import Session
6+
from pygmt.exceptions import GMTInvalidInput
7+
from pygmt.helpers import (
8+
GMTTempFile,
9+
build_arg_string,
10+
fmt_docstring,
11+
kwargs_to_strings,
12+
use_alias,
13+
)
14+
from pygmt.io import load_dataarray
15+
16+
17+
@fmt_docstring
18+
@use_alias(
19+
D="distance",
20+
F="filter",
21+
G="outgrid",
22+
I="spacing",
23+
N="sectors",
24+
R="region",
25+
V="verbose",
26+
)
27+
@kwargs_to_strings(I="sequence", R="sequence")
28+
def dimfilter(grid, **kwargs):
29+
r"""
30+
Filter a grid by dividing the filter circle.
31+
32+
Filter a grid in the space (or time) domain by
33+
dividing the given filter circle into the given number of sectors,
34+
applying one of the selected primary convolution or non-convolution
35+
filters to each sector, and choosing the final outcome according to the
36+
selected secondary filter. It computes distances using Cartesian or
37+
Spherical geometries. The output grid can optionally be generated as a
38+
subregion of the input and/or with a new increment using ``spacing``,
39+
which may add an "extra space" in the input data to prevent edge
40+
effects for the output grid. If the filter is low-pass, then the output
41+
may be less frequently sampled than the input. **dimfilter** will not
42+
produce a smooth output as other spatial filters
43+
do because it returns a minimum median out of *N* medians of *N*
44+
sectors. The output can be rough unless the input data is noise-free.
45+
Thus, an additional filtering (e.g., Gaussian via :func:`pygmt.grdfilter`)
46+
of the DiM-filtered data is generally recommended.
47+
48+
Full option list at :gmt-docs:`dimfilter.html`
49+
50+
{aliases}
51+
52+
Parameters
53+
----------
54+
grid : str or xarray.DataArray
55+
The file name of the input grid or the grid loaded as a DataArray.
56+
outgrid : str or None
57+
The name of the output netCDF file with extension .nc to store the grid
58+
in.
59+
distance : int or str
60+
Distance flag tells how grid (x,y) relates to filter width, as follows:
61+
62+
- **0**\ : grid (x,y) in same units as *width*, Cartesian distances.
63+
- **1**\ : grid (x,y) in degrees, *width* in kilometers, Cartesian
64+
distances.
65+
- **2**\ : grid (x,y) in degrees, *width* in km, dx scaled by
66+
cos(middle y), Cartesian distances.
67+
68+
The above options are fastest because they allow weight matrix to be
69+
computed only once. The next two options are slower because they
70+
recompute weights for each latitude.
71+
72+
- **3**\ : grid (x,y) in degrees, *width* in km, dx scaled by
73+
cosine(y), Cartesian distance calculation.
74+
- **4**\ : grid (x,y) in degrees, *width* in km, Spherical distance
75+
calculation.
76+
filter : str
77+
**x**\ *width*\ [**+l**\|\ **u**].
78+
Sets the primary filter type. Choose among convolution and
79+
non-convolution filters. Use the filter code **x** followed by
80+
the full diameter *width*. Available convolution filters are:
81+
82+
- (**b**) Boxcar: All weights are equal.
83+
- (**c**) Cosine Arch: Weights follow a cosine arch curve.
84+
- (**g**) Gaussian: Weights are given by the Gaussian function.
85+
86+
Non-convolution filters are:
87+
88+
- (**m**) Median: Returns median value.
89+
- (**p**) Maximum likelihood probability (a mode estimator): Return
90+
modal value. If more than one mode is found we return their average
91+
value. Append **+l** or **+h** to the filter width if you want
92+
to return the smallest or largest of each sector's modal values.
93+
sectors : str
94+
**x**\ *sectors*\ [**+l**\|\ **u**]
95+
Sets the secondary filter type **x** and the number of bow-tie sectors.
96+
*sectors* must be integer and larger than 0. When *sectors* is
97+
set to 1, the secondary filter is not effective. Available secondary
98+
filters **x** are:
99+
100+
- (**l**) Lower: Return the minimum of all filtered values.
101+
- (**u**) Upper: Return the maximum of all filtered values.
102+
- (**a**) Average: Return the mean of all filtered values.
103+
- (**m**) Median: Return the median of all filtered values.
104+
- (**p**) Mode: Return the mode of all filtered values:
105+
If more than one mode is found we return their average
106+
value. Append **+l** or **+h** to the sectors if you rather want to
107+
return the smallest or largest of the modal values.
108+
spacing : str or list
109+
*x_inc* [and optionally *y_inc*] is the output Increment. Append
110+
**m** to indicate minutes, or **c** to indicate seconds. If the new
111+
*x_inc*, *y_inc* are NOT integer multiples of the old ones (in the
112+
input data), filtering will be considerably slower. [Default: Same
113+
as input.]
114+
region : str or list
115+
[*xmin*, *xmax*, *ymin*, *ymax*].
116+
Defines the region of the output points. [Default: Same as input.]
117+
{V}
118+
119+
Returns
120+
-------
121+
ret: xarray.DataArray or None
122+
Return type depends on whether the ``outgrid`` parameter is set:
123+
124+
- :class:`xarray.DataArray` if ``outgrid`` is not set
125+
- None if ``outgrid`` is set (grid output will be stored in file set by
126+
``outgrid``)
127+
"""
128+
if not all(arg in kwargs for arg in ["D", "F", "N"]) and "Q" not in kwargs:
129+
raise GMTInvalidInput(
130+
"""At least one of the following parameters must be specified:
131+
distance, filters, or sectors."""
132+
)
133+
134+
with GMTTempFile(suffix=".nc") as tmpfile:
135+
with Session() as lib:
136+
file_context = lib.virtualfile_from_data(check_kind=None, data=grid)
137+
with file_context as infile:
138+
if "G" not in kwargs: # if outgrid is unset, output to tempfile
139+
kwargs.update({"G": tmpfile.name})
140+
outgrid = kwargs["G"]
141+
arg_str = " ".join([infile, build_arg_string(kwargs)])
142+
lib.call_module("dimfilter", arg_str)
143+
144+
return load_dataarray(outgrid) if outgrid == tmpfile.name else None

pygmt/tests/test_dimfilter.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Tests for dimfilter.
3+
"""
4+
import os
5+
6+
import pytest
7+
import xarray as xr
8+
from pygmt import dimfilter, load_dataarray
9+
from pygmt.exceptions import GMTInvalidInput
10+
from pygmt.helpers import GMTTempFile
11+
from pygmt.helpers.testing import load_static_earth_relief
12+
13+
14+
@pytest.fixture(scope="module", name="grid")
15+
def fixture_grid():
16+
"""
17+
Load the grid data from the static_earth_relief file.
18+
"""
19+
return load_static_earth_relief()
20+
21+
22+
@pytest.fixture(scope="module", name="expected_grid")
23+
def fixture_grid_result():
24+
"""
25+
Load the expected dimfilter grid result.
26+
"""
27+
return xr.DataArray(
28+
data=[
29+
[346.0, 344.5, 349.0, 349.0],
30+
[344.5, 318.5, 344.5, 394.0],
31+
[344.5, 356.5, 345.5, 352.5],
32+
[367.5, 349.0, 385.5, 349.0],
33+
[435.0, 385.5, 413.5, 481.5],
34+
],
35+
coords=dict(
36+
lon=[-54.5, -53.5, -52.5, -51.5],
37+
lat=[-23.5, -22.5, -21.5, -20.5, -19.5],
38+
),
39+
dims=["lat", "lon"],
40+
)
41+
42+
43+
def test_dimfilter_outgrid(grid, expected_grid):
44+
"""
45+
Test the required parameters for dimfilter with a set outgrid.
46+
"""
47+
with GMTTempFile(suffix=".nc") as tmpfile:
48+
result = dimfilter(
49+
grid=grid,
50+
outgrid=tmpfile.name,
51+
filter="m600",
52+
distance=4,
53+
sectors="l6",
54+
region=[-55, -51, -24, -19],
55+
)
56+
assert result is None # return value is None
57+
assert os.path.exists(path=tmpfile.name) # check that outgrid exists
58+
temp_grid = load_dataarray(tmpfile.name)
59+
xr.testing.assert_allclose(a=temp_grid, b=expected_grid)
60+
61+
62+
def test_dimfilter_no_outgrid(grid, expected_grid):
63+
"""
64+
Test the required parameters for dimfilter with no set outgrid.
65+
"""
66+
result = dimfilter(
67+
grid=grid, filter="m600", distance=4, sectors="l6", region=[-55, -51, -24, -19]
68+
)
69+
assert result.dims == ("lat", "lon")
70+
assert result.gmt.gtype == 1 # Geographic grid
71+
assert result.gmt.registration == 1 # Pixel registration
72+
xr.testing.assert_allclose(a=result, b=expected_grid)
73+
74+
75+
def test_dimfilter_fails(grid):
76+
"""
77+
Check that dimfilter fails correctly when not all of sectors, filters, and
78+
distance are specified.
79+
"""
80+
with pytest.raises(GMTInvalidInput):
81+
dimfilter(grid=grid, sectors="l6", distance=4)

0 commit comments

Comments
 (0)