diff --git a/doc/api/index.rst b/doc/api/index.rst
index d410ef035c3..e6b45603910 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -44,6 +44,7 @@ Color palette table generation:
.. autosummary::
:toctree: generated
+ grd2cpt
makecpt
Saving and displaying the figure:
diff --git a/pygmt/__init__.py b/pygmt/__init__.py
index f22df6dff7d..b7a6aa6007f 100644
--- a/pygmt/__init__.py
+++ b/pygmt/__init__.py
@@ -19,6 +19,7 @@
from pygmt.session_management import end as _end
from pygmt.src import (
blockmedian,
+ grd2cpt,
grdcut,
grdfilter,
grdinfo,
diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py
index 132cb80cece..016e610d6de 100644
--- a/pygmt/src/__init__.py
+++ b/pygmt/src/__init__.py
@@ -7,6 +7,7 @@
from pygmt.src.coast import coast
from pygmt.src.colorbar import colorbar
from pygmt.src.contour import contour
+from pygmt.src.grd2cpt import grd2cpt
from pygmt.src.grdcontour import grdcontour
from pygmt.src.grdcut import grdcut
from pygmt.src.grdfilter import grdfilter
diff --git a/pygmt/src/grd2cpt.py b/pygmt/src/grd2cpt.py
new file mode 100644
index 00000000000..569a3232060
--- /dev/null
+++ b/pygmt/src/grd2cpt.py
@@ -0,0 +1,190 @@
+"""
+grd2cpt - Create a CPT from a grid file.
+"""
+
+from pygmt.clib import Session
+from pygmt.exceptions import GMTInvalidInput
+from pygmt.helpers import (
+ build_arg_string,
+ data_kind,
+ dummy_context,
+ fmt_docstring,
+ kwargs_to_strings,
+ use_alias,
+)
+
+
+@fmt_docstring
+@use_alias(
+ A="transparency",
+ C="cmap",
+ D="background",
+ F="color_model",
+ E="nlevels",
+ G="truncate",
+ H="output",
+ I="reverse",
+ L="limit",
+ M="overrule_bg",
+ N="no_bg",
+ Q="log",
+ R="region",
+ T="series",
+ V="verbose",
+ W="categorical",
+ Ww="cyclic",
+ Z="continuous",
+)
+@kwargs_to_strings(G="sequence", L="sequence", R="sequence", T="sequence")
+def grd2cpt(grid, **kwargs):
+ r"""
+ Make GMT color palette tables from a grid file.
+
+ This is a module that will help you make static color palette tables
+ (CPTs). By default, the CPT will simply be saved to the current session,
+ but you can use ``output`` to save it to a file. The CPT is based on an
+ existing dynamic master CPT of your choice, and the mapping from data value
+ to colors is through the data's cumulative distribution function (CDF), so
+ that the colors are histogram equalized. Thus if the grid(s) and the
+ resulting CPT are used in :meth:`pygmt.Figure.grdimage` with a linear
+ projection, the colors will be uniformly distributed in area on the plot.
+ Let z be the data values in the grid. Define CDF(Z) = (# of z < Z) / (# of
+ z in grid). (NaNs are ignored). These z-values are then normalized to the
+ master CPT and colors are sampled at the desired intervals.
+
+ The CPT includes three additional colors beyond the range of z-values.
+ These are the background color (B) assigned to values lower than the lowest
+ *z*-value, the foreground color (F) assigned to values higher than the
+ highest *z*-value, and the NaN color (N) painted wherever values are
+ undefined. For color tables beyond the standard GMT offerings, visit
+ `cpt-city `_ and
+ `Scientific Colour-Maps `_.
+
+ If the master CPT includes B, F, and N entries, these will be copied into
+ the new master file. If not, the parameters :gmt-term:`COLOR_BACKGROUND`,
+ :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` from the
+ :gmt-docs:`gmt.conf ` file or the command line will be used. This
+ default behavior can be overruled using the options ``background``,
+ ``overrule_bg`` or ``no_bg``.
+
+ The color model (RGB, HSV or CMYK) of the palette created by
+ :meth:`pygmt.grd2cpt` will be the same as specified in the header of the
+ master CPT. When there is no :gmt-term:`COLOR_MODEL` entry in the master
+ CPT, the :gmt-term:`COLOR_MODEL` specified in the
+ :gmt-docs:`gmt.conf ` file or the ``color_model`` option will be
+ used.
+
+ Full option list at :gmt-docs:`grd2cpt.html`
+
+ {aliases}
+
+ Parameters
+ ----------
+ grid : str or xarray.DataArray
+ The file name of the input grid or the grid loaded as a DataArray.
+ transparency : int or float or str
+ Sets a constant level of transparency (0-100) for all color slices.
+ Append **+a** to also affect the fore-, back-, and nan-colors
+ [Default is no transparency, i.e., 0 (opaque)].
+ cmap : str
+ Selects the master color palette table (CPT) to use in the
+ interpolation. Full list of built-in color palette tables can be found
+ at :gmt-docs:`cookbook/cpts.html#built-in-color-palette-tables-cpt`.
+ background : bool or str
+ Select the back- and foreground colors to match the colors for lowest
+ and highest *z*-values in the output CPT [Default (``background=True``
+ or ``background='o'``) uses the colors specified in the master file, or
+ those defined by the parameters :gmt-term:`COLOR_BACKGROUND`,
+ :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN`]. Use
+ ``background='i'`` to match the colors for the lowest and highest
+ values in the input (instead of the output) CPT.
+ color_model :
+ [**R**\|\ **r**\|\ **h**\|\ **c**][**+c**\ [*label*]].
+ Force output CPT to be written with r/g/b codes, gray-scale values or
+ color name (**R**, default) or r/g/b codes only (**r**), or h-s-v codes
+ (**h**), or c/m/y/k codes (**c**). Optionally or alternatively, append
+ **+c** to write discrete palettes in categorical format. If *label* is
+ appended then we create labels for each category to be used when the
+ CPT is plotted. The *label* may be a comma-separated list of category
+ names (you can skip a category by not giving a name), or give
+ *start*\[-], where we automatically build monotonically increasing
+ labels from *start* (a single letter or an integer). Append ``-`` to
+ build ranges *start*-*start+1* instead.
+ nlevels : bool or int or str
+ Set to ``True`` to create a linear color table by using the grid
+ z-range as the new limits in the CPT. Alternatively, set *nlevels*
+ to resample the color table into *nlevels* equidistant slices.
+ series : list or str
+ [*min/max/inc*\ [**+b**\|\ **l**\|\ **n**\]|\ *file*\|\ *list*\].
+ Defines the range of the new CPT by giving the lowest and highest
+ z-value (and optionally an interval). If this is not given, the
+ existing range in the master CPT will be used intact. The values
+ produced defines the color slice boundaries. If **+n** is used it
+ refers to the number of such boundaries and not the number of slices.
+ For details on array creation, see
+ :gmt-docs:`makecpt.html#generate-1d-array`.
+ truncate : list or str
+ *zlo/zhi*.
+ Truncate the incoming CPT so that the lowest and highest z-levels are
+ to *zlo* and *zhi*. If one of these equal NaN then we leave that end of
+ the CPT alone. The truncation takes place before any resampling. See
+ also :gmt-docs:`cookbook/features.html#manipulating-cpts`.
+ output : str
+ Optional argument to set the file name with extension .cpt to store
+ the generated CPT file. If not given or False (default), saves the CPT
+ as the session current CPT.
+ reverse : str
+ Set this to True or c [Default] to reverse the sense of color
+ progression in the master CPT. Set this to z to reverse the sign of
+ z-values in the color table. Note that this change of z-direction
+ happens before *truncate* and *series* values are used so the latter
+ must be compatible with the changed *z*-range. See also
+ :gmt-docs:`cookbook/features.html#manipulating-cpts`.
+ overrule_bg : str
+ Overrule background, foreground, and NaN colors specified in the master
+ CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`,
+ :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in
+ the :gmt-docs:`gmt.conf ` file or on the command line. When
+ combined with ``background``, only :gmt-term:`COLOR_NAN` is considered.
+ no_bg : bool
+ Do not write out the background, foreground, and NaN-color fields
+ [Default will write them, i.e. ``no_bg=False``].
+ log : bool
+ For logarithmic interpolation scheme with input given as logarithms.
+ Expects input z-values provided via ``series`` to be log10(*z*),
+ assigns colors, and writes out *z*.
+ continuous : bool
+ Force a continuous CPT when building from a list of colors and a list
+ of z-values [Default is None, i.e. discrete values].
+ categorical : bool
+ Do not interpolate the input color table but pick the output colors
+ starting at the beginning of the color table, until colors for all
+ intervals are assigned. This is particularly useful in combination with
+ a categorical color table, like ``cmap='categorical'``.
+ cyclic : bool
+ Produce a wrapped (cyclic) color table that endlessly repeats its
+ range. Note that ``cyclic=True`` cannot be set together with
+ ``categorical=True``.
+ {V}
+ """
+ if "W" in kwargs and "Ww" in kwargs:
+ raise GMTInvalidInput("Set only categorical or cyclic to True, not both.")
+ kind = data_kind(grid)
+ with Session() as lib:
+ if kind == "file":
+ file_context = dummy_context(grid)
+ elif kind == "grid":
+ file_context = lib.virtualfile_from_grid(grid)
+ else:
+ raise GMTInvalidInput(f"Unrecognized data type: {type(grid)}")
+ with file_context as infile:
+ if "H" not in kwargs.keys(): # if no output is set
+ arg_str = " ".join([infile, build_arg_string(kwargs)])
+ elif "H" in kwargs.keys(): # if output is set
+ outfile = kwargs.pop("H")
+ if not outfile or not isinstance(outfile, str):
+ raise GMTInvalidInput("'output' should be a proper file name.")
+ arg_str = " ".join(
+ [infile, build_arg_string(kwargs), f"-H > {outfile}"]
+ )
+ lib.call_module("grd2cpt", arg_str)
diff --git a/pygmt/src/makecpt.py b/pygmt/src/makecpt.py
index e4c9f7190a0..bdad3f07952 100644
--- a/pygmt/src/makecpt.py
+++ b/pygmt/src/makecpt.py
@@ -118,7 +118,7 @@ def makecpt(**kwargs):
happens before *truncate* and *series* values are used so the latter
must be compatible with the changed *z*-range. See also
:gmt-docs:`cookbook/features.html#manipulating-cpts`.
- overrule_bg :
+ overrule_bg : str
Overrule background, foreground, and NaN colors specified in the master
CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`,
:gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in
diff --git a/pygmt/tests/test_grd2cpt.py b/pygmt/tests/test_grd2cpt.py
new file mode 100644
index 00000000000..26a83cced36
--- /dev/null
+++ b/pygmt/tests/test_grd2cpt.py
@@ -0,0 +1,79 @@
+"""
+Tests for grd2cpt.
+"""
+import os
+
+import pytest
+from pygmt import Figure
+from pygmt.datasets import load_earth_relief
+from pygmt.exceptions import GMTInvalidInput
+from pygmt.helpers import GMTTempFile
+from pygmt.helpers.testing import check_figures_equal
+from pygmt.src.grd2cpt import grd2cpt
+
+
+@pytest.fixture(scope="module", name="grid")
+def fixture_grid():
+ """
+ Load the grid data from the sample earth_relief file.
+ """
+ return load_earth_relief()
+
+
+@check_figures_equal()
+def test_grd2cpt(grid):
+ """
+ Test creating a CPT with grd2cpt to create a CPT based off a grid input and
+ plot it with a color bar.
+ """
+ fig_ref, fig_test = Figure(), Figure()
+ # Use single-character arguments for the reference image
+ fig_ref.basemap(B="a", J="W0/15c", R="d")
+ grd2cpt(grid="@earth_relief_01d")
+ fig_ref.colorbar(B="a2000")
+ fig_test.basemap(frame="a", projection="W0/15c", region="d")
+ grd2cpt(grid=grid)
+ fig_test.colorbar(frame="a2000")
+ return fig_ref, fig_test
+
+
+def test_grd2cpt_blank_output(grid):
+ """
+ Use incorrect setting by passing in blank file name to output parameter.
+ """
+ with pytest.raises(GMTInvalidInput):
+ grd2cpt(grid=grid, output="")
+
+
+def test_grd2cpt_invalid_output(grid):
+ """
+ Use incorrect setting by passing in invalid type to output parameter.
+ """
+ with pytest.raises(GMTInvalidInput):
+ grd2cpt(grid=grid, output=["some.cpt"])
+
+
+def test_grd2cpt_output_to_cpt_file(grid):
+ """
+ Save the generated static color palette table to a .cpt file.
+ """
+ with GMTTempFile(suffix=".cpt") as cptfile:
+ grd2cpt(grid=grid, output=cptfile.name)
+ assert os.path.getsize(cptfile.name) > 0
+
+
+def test_grd2cpt_unrecognized_data_type():
+ """
+ Test that an error will be raised if an invalid data type is passed to
+ grid.
+ """
+ with pytest.raises(GMTInvalidInput):
+ grd2cpt(grid=0)
+
+
+def test_grd2cpt_categorical_and_cyclic(grid):
+ """
+ Use incorrect setting by setting both categorical and cyclic to True.
+ """
+ with pytest.raises(GMTInvalidInput):
+ grd2cpt(grid=grid, cmap="batlow", categorical=True, cyclic=True)