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)