From 3318486b00549c6a9313ddafd32fdeed843ebc30 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 20 Dec 2025 18:17:53 +0800 Subject: [PATCH 1/2] pygmt.grdfilter: Add parameters filter_type/filter_width and more to set the filter --- pygmt/src/grdfilter.py | 183 +++++++++++++++++++++++++++++++--- pygmt/tests/test_grdfilter.py | 10 +- 2 files changed, 175 insertions(+), 18 deletions(-) diff --git a/pygmt/src/grdfilter.py b/pygmt/src/grdfilter.py index 053f29cb040..88373f01ef7 100644 --- a/pygmt/src/grdfilter.py +++ b/pygmt/src/grdfilter.py @@ -9,16 +9,106 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["grdfilter"] +def _alias_option_F( # noqa: N802 + filter=None, # noqa: A002 + fliter_type=None, + filter_width=None, + hist_bin_width=None, + highpass=None, + median_quantile=None, + hist_center_bins=None, + mode_extreme=None, +): + """ + Helper function to create the alias list for the -F option. + """ + if filter is not None and any( + v is not None and v is not False + for v in { + fliter_type, + filter_width, + hist_bin_width, + highpass, + median_quantile, + hist_center_bins, + mode_extreme, + } + ): + msg = ( + "Parameter 'filter' is given with a raw GMT command string, and conflicts " + "with parameters 'filter_type', 'filter_width', 'hist_bin_width', " + "'highpass', 'median_quantile', 'hist_center_bins', or 'mode_extreme'." + ) + raise GMTInvalidInput(msg) + + if filter is not None: + return Alias(filter, name="filter") # Deprecated raw GMT string. + + return [ + Alias( + fliter_type, + name="filter_type", + mapping={ + "boxcar": "b", + "cosine_arch": "c", + "gaussian": "g", + "custom": "f", + "operator": "o", + "median": "m", + "mlprob": "p", + "histogram": "h", + "minall": "l", + "minpos": "L", + "maxall": "u", + "maxneg": "U", + }, + ), + Alias(filter_width, name="filter_width", sep="/"), + Alias(hist_bin_width, name="hist_bin_width", prefix="/"), + Alias(highpass, name="highpass", prefix="+h"), + Alias(median_quantile, name="median_quantile", prefix="+q"), + Alias(hist_center_bins, name="hist_center_bins", prefix="+c"), + Alias( + mode_extreme, + name="mode_extreme", + mapping={"min": "+l", "max": "+u"}, + ), + ] + + @fmt_docstring -@use_alias(D="distance", F="filter", f="coltypes") -def grdfilter( +@use_alias(D="distance", f="coltypes") +def grdfilter( # noqa: PLR0913 grid: PathLike | xr.DataArray, outgrid: PathLike | None = None, + filter_type: Literal[ + "boxcar", + "cosine_arch", + "gaussian", + "custom", + "operator", + "median", + "mlprob", + "histogram", + "minall", + "minpos", + "maxall", + "maxneg", + ] + | None = None, + filter_width: Sequence[float] | None = None, + highpass: bool = False, + median_quantile: float | None = None, + hist_bin_width: float | None = None, + hist_center_bins: bool = False, + mode_extreme: Literal["min", "max"] | None = None, + filter: str | None = None, # noqa: A002 spacing: Sequence[float | str] | None = None, nans: Literal["ignore", "replace", "preserve"] | None = None, toggle: bool = False, @@ -57,19 +147,67 @@ def grdfilter( ---------- $grid $outgrid - filter : str - **b**\|\ **c**\|\ **g**\|\ **o**\|\ **m**\|\ **p**\|\ **h**\ *width*\ - [/*width2*\][*modifiers*]. - Name of the filter type you wish to apply, followed by the *width*: - - - **b**: Box Car - - **c**: Cosine Arch - - **g**: Gaussian - - **o**: Operator - - **m**: Median - - **p**: Maximum Likelihood probability - - **h**: Histogram + filter_type + The filter type. Choose among convolution and non-convolution filters. + Convolution filters are: + + - ``"boxcar"``: All weights are equal. + - ``"cosine_arch"``: Weights follow a cosine arch curve. + - ``"gaussian"``: Weights are given by the Gaussian function, where filter width + is 6 times the conventional Gaussian sigma. + - ``"custom"``: Weights are given by the precomputed values in the filter weight + grid file *weight*, which must have odd dimensions; also requires ``distance=0`` + and output spacing must match input spacing or be integer multiples. + - ``"operator"``: Weights are given by the precomputed values in the filter weight + grid file *weight*, which must have odd dimensions; also requires ``distance=0`` + and output spacing must match input spacing or be integer multiples. Weights + are assumed to sum to zero so no accumulation of weight sums and normalization + will be done. + + Non-convolution filters are: + + - ``"median"``: Returns median value. To select another quantile, use the + parameter ``median_quantile`` in the 0-1 range [Default is 0.5, i.e., median]. + - ``"mlprob"``: Maximum likelihood probability (a mode estimator). Return modal + value. If more than one mode is found we return their average value. Set + ``mode_extreme`` to ``"min"`` or ``"max"`` to return the lowermost or uppermost + of the modal values. + - ``"histogram"``: Histogram mode (another mode estimator). Return the modal value + as the center of the dominant peak in a histogram. Use parameter + ``histogram_center_bins`` to center the bins on multiples of bin width [Default + has bin edges that are multiples of bin width]. Use parameter + ``histogram_bin_width`` to set the bin width. If more than one mode is found we + return their average value. Set ``mode_extreme`` to ``"min"`` or ``"max"`` to + return the lowermost or uppermost of the modal values. + + - ``"minall"``: Return minimum of all values. + - ``"minpos"``: Return minimum of all positive values only. + - ``"maxall"``: Return maximum of all values. + - ``"maxneg"``: Return maximum of all negative values only. + median_quantile + Quantile to use when ``filter_type="median"``. Must be a float in the range 0-1. + [Default is 0.5 (median)]. + hist_bin_width + Bin width to use when ``filter_type="histogram"``. + hist_center_bins + Center the histogram bins on multiples of *histogram_bin_width* when + ``filter_type="histogram"``. By default, the bins are aligned such that + their edges are on multiples of *hist_bin_width*. + mode_extreme + Choose which extreme to return when ``filter_type="mlprob"`` and multiple modes + are found. Options are: ``"min"`` to return the lowermost mode, or ``"max"`` to + return the uppermost mode. By default, the average of all modes is returned. + filter + Set the filter type. + + .. deprecated:: v0.19.0 + + This parameter is deprecated. Use the parameters ``filter_type``, + ``filter_width``, ``hist_bin_width``, ``highpass``, ``median_quantile``, + ``hist_center_bins``, and ``mode_extreme`` instead. This parameter still + accepts raw GMT CLI strings for the ``-F`` option of the ``grdfilter`` + module for backward compatibility. distance : str State how the grid (x,y) relates to the filter *width*: @@ -129,7 +267,8 @@ def grdfilter( >>> # and return a filtered grid (saved as netCDF file). >>> pygmt.grdfilter( ... grid="@earth_relief_30m_g", - ... filter="m600", + ... filter_type="median", + ... filter_width=600, ... distance="4", ... region=[150, 250, 10, 40], ... spacing=0.5, @@ -139,9 +278,21 @@ def grdfilter( >>> # Apply a Gaussian smoothing filter of 600 km to the input DataArray and return >>> # a filtered DataArray with the smoothed grid. >>> grid = pygmt.datasets.load_earth_relief() - >>> smooth_field = pygmt.grdfilter(grid=grid, filter="g600", distance="4") + >>> smooth_field = pygmt.grdfilter( + ... grid=grid, filter_type="gaussian", filter_width=600, distance="4" + ... ) """ aliasdict = AliasSystem( + F=_alias_option_F( + filter=filter, + fliter_type=filter_type, + filter_width=filter_width, + hist_bin_width=hist_bin_width, + highpass=highpass, + median_quantile=median_quantile, + hist_center_bins=hist_center_bins, + mode_extreme=mode_extreme, + ), I=Alias(spacing, name="spacing", sep="/", size=2), N=Alias( nans, name="nans", mapping={"ignore": "i", "replace": "r", "preserve": "p"} diff --git a/pygmt/tests/test_grdfilter.py b/pygmt/tests/test_grdfilter.py index ab2863a29e8..73a644d041a 100644 --- a/pygmt/tests/test_grdfilter.py +++ b/pygmt/tests/test_grdfilter.py @@ -47,7 +47,12 @@ def test_grdfilter_dataarray_in_dataarray_out(grid, expected_grid): Test grdfilter with an input DataArray, and output as DataArray. """ result = grdfilter( - grid=grid, filter="g600", distance="4", region=[-53, -49, -20, -17], cores=2 + grid=grid, + filter_type="gaussian", + filter_width=600, + distance="4", + region=[-53, -49, -20, -17], + cores=2, ) # check information of the output grid assert isinstance(result, xr.DataArray) @@ -65,7 +70,8 @@ def test_grdfilter_dataarray_in_file_out(grid, expected_grid): result = grdfilter( grid, outgrid=tmpfile.name, - filter="g600", + filter_type="gaussian", + filter_width=600, distance="4", region=[-53, -49, -20, -17], ) From a513e2c7108e47a190c1b9b359eb614bdcbc5c8e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 3 Feb 2026 11:35:03 +0800 Subject: [PATCH 2/2] Improve the checking --- pygmt/src/grdfilter.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/pygmt/src/grdfilter.py b/pygmt/src/grdfilter.py index 88373f01ef7..893fdb11dce 100644 --- a/pygmt/src/grdfilter.py +++ b/pygmt/src/grdfilter.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput +from pygmt.exceptions import GMTInvalidInput, GMTValueError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias __doctest_skip__ = ["grdfilter"] @@ -17,7 +17,7 @@ def _alias_option_F( # noqa: N802 filter=None, # noqa: A002 - fliter_type=None, + filter_type=None, filter_width=None, hist_bin_width=None, highpass=None, @@ -31,7 +31,7 @@ def _alias_option_F( # noqa: N802 if filter is not None and any( v is not None and v is not False for v in { - fliter_type, + filter_type, filter_width, hist_bin_width, highpass, @@ -50,9 +50,34 @@ def _alias_option_F( # noqa: N802 if filter is not None: return Alias(filter, name="filter") # Deprecated raw GMT string. + if median_quantile is not None and filter_type != "median": + raise GMTValueError( + filter_type, + description="filter type", + reason="'filter_type' must be 'median' when 'median_quantile' is set.", + ) + if hist_bin_width is not None and filter_type != "histogram": + raise GMTValueError( + filter_type, + description="filter type", + reason="'filter_type' must be 'histogram' when 'hist_bin_width' is set.", + ) + if hist_center_bins is not None and filter_type != "histogram": + raise GMTValueError( + filter_type, + description="filter type", + reason="'filter_type' must be 'histogram' when 'hist_center_bins' is set.", + ) + if mode_extreme is not None and filter_type not in {"mlprob", "histogram"}: + raise GMTValueError( + filter_type, + description="filter type", + reason="'filter_type' must be 'mlprob' or 'histogram' when 'mode_extreme' is set.", + ) + return [ Alias( - fliter_type, + filter_type, name="filter_type", mapping={ "boxcar": "b", @@ -71,9 +96,9 @@ def _alias_option_F( # noqa: N802 ), Alias(filter_width, name="filter_width", sep="/"), Alias(hist_bin_width, name="hist_bin_width", prefix="/"), + Alias(hist_center_bins, name="hist_center_bins", prefix="+c"), Alias(highpass, name="highpass", prefix="+h"), Alias(median_quantile, name="median_quantile", prefix="+q"), - Alias(hist_center_bins, name="hist_center_bins", prefix="+c"), Alias( mode_extreme, name="mode_extreme", @@ -195,9 +220,10 @@ def grdfilter( # noqa: PLR0913 ``filter_type="histogram"``. By default, the bins are aligned such that their edges are on multiples of *hist_bin_width*. mode_extreme - Choose which extreme to return when ``filter_type="mlprob"`` and multiple modes - are found. Options are: ``"min"`` to return the lowermost mode, or ``"max"`` to - return the uppermost mode. By default, the average of all modes is returned. + Choose which extreme to return when ``filter_type="mlprob"`` or + ``filter_type="histogram"`` and multiple modes are found. Options are: ``"min"`` + to return the lowermost mode, or ``"max"`` to return the uppermost mode. By + default, the average of all modes is returned. filter Set the filter type.