Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify colormap creations #2308

Closed
gerritholl opened this issue Dec 5, 2022 · 0 comments · Fixed by #2313
Closed

Unify colormap creations #2308

gerritholl opened this issue Dec 5, 2022 · 0 comments · Fixed by #2313
Assignees
Labels

Comments

@gerritholl
Copy link
Collaborator

This issue could be either here or in trollimage, as it affects both projects. I'm putting it here because the problem is more apparently in Satpy, however, the solution will probably be primarily in trollimage.

Feature Request

Is your feature request related to a problem? Please describe.

There are at least three different places in Pytroll where colormaps are created:

  • in the staticmethod satpy.composites.ColormapCompositor.build_colormap

@staticmethod
def build_colormap(palette, dtype, info):
"""Create the colormap from the `raw_palette` and the valid_range.
Colormaps come in different forms, but they are all supposed to have
color values between 0 and 255. The following cases are considered:
- Palettes comprised of only a list of colors. If *dtype* is uint8,
the values of the colormap are the enumeration of the colors.
Otherwise, the colormap values will be spread evenly from the min
to the max of the valid_range provided in `info`.
- Palettes that have a palette_meanings attribute. The palette meanings
will be used as values of the colormap.
"""
from trollimage.colormap import Colormap
squeezed_palette = np.asanyarray(palette).squeeze() / 255.0
set_range = True
if hasattr(palette, 'attrs') and 'palette_meanings' in palette.attrs:
set_range = False
meanings = palette.attrs['palette_meanings']
iterator = zip(meanings, squeezed_palette)
else:
iterator = enumerate(squeezed_palette[:-1])
if dtype == np.dtype('uint8'):
tups = [(val, tuple(tup))
for (val, tup) in iterator]
colormap = Colormap(*tups)
elif 'valid_range' in info:
tups = [(val, tuple(tup))
for (val, tup) in iterator]
colormap = Colormap(*tups)
if set_range:
sf = info.get('scale_factor', np.array(1))
colormap.set_range(
*(np.array(info['valid_range']) * sf
+ info.get('add_offset', 0)))
else:
raise AttributeError("Data needs to have either a valid_range or be of type uint8" +
" in order to be displayable with an attached color-palette!")
return colormap, squeezed_palette

  • in the function satpy.enhancements.create_colormap:

def create_colormap(palette):
"""Create colormap of the given numpy file, color vector, or colormap.
Args:
palette (dict): Information describing how to create a colormap
object. See below for more details.
**From a file**
Colormaps can be loaded from ``.npy``, ``.npz``, or comma-separated text
files. Numpy (npy/npz) files should be 2D arrays with rows for each color.
Comma-separated files should have a row for each color with each column
representing a single value/channel. The filename to load can be provided
with the ``filename`` key in the provided palette information. A filename
ending with ``.npy`` or ``.npz`` is read as a numpy file with
:func:`numpy.load`. All other extensions are
read as a comma-separated file. For ``.npz`` files the data must be stored
as a positional list where the first element represents the colormap to
use. See :func:`numpy.savez` for more information. The path to the
colormap can be relative if it is stored in a directory specified by
:ref:`config_path_setting`. Otherwise it should be an absolute path.
The colormap is interpreted as 1 of 4 different "colormap modes":
``RGB``, ``RGBA``, ``VRGB``, or ``VRGBA``. The
colormap mode can be forced with the ``colormap_mode`` key in the provided
palette information. If it is not provided then a default will be chosen
based on the number of columns in the array (3: RGB, 4: VRGB, 5: VRGBA).
The "V" in the possible colormap modes represents the control value of
where that color should be applied. If "V" is not provided in the colormap
data it defaults to the row index in the colormap array (0, 1, 2, ...)
divided by the total number of colors to produce a number between 0 and 1.
See the "Set Range" section below for more information.
The remaining elements in the colormap array represent the Red (R),
Green (G), and Blue (B) color to be mapped to.
See the "Color Scale" section below for more information on the value
range of provided numbers.
**From a list**
Colormaps can be loaded from lists of colors provided by the ``colors``
key in the provided dictionary. Each element in the list represents a
single color to be mapped to and can be 3 (RGB) or 4 (RGBA) elements long.
By default the value or control point for a color is determined by the
index in the list (0, 1, 2, ...) divided by the total number of colors
to produce a number between 0 and 1. This can be overridden by providing a
``values`` key in the provided dictionary. See the "Set Range" section
below for more information.
See the "Color Scale" section below for more information on the value
range of provided numbers.
**From a builtin colormap**
Colormaps can be loaded by name from the builtin colormaps in the
``trollimage``` package. Specify the name with the ``colors``
key in the provided dictionary (ex. ``{'colors': 'blues'}``).
See :doc:`trollimage:colormap` for the full list of available colormaps.
**Color Scale**
By default colors are expected to be in a 0-255 range. This
can be overridden by specifying ``color_scale`` in the provided colormap
information. A common alternative to 255 is ``1`` to specify floating
point numbers between 0 and 1. The resulting Colormap uses the normalized
color values (0-1).
**Set Range**
By default the control points or values of the Colormap are between 0 and
1. This means that data values being mapped to a color must also be
between 0 and 1. When this is not the case, the expected input range of
the data can be used to configure the Colormap and change the control point
values. To do this specify the input data range with ``min_value`` and
``max_value``. See :meth:`trollimage.colormap.Colormap.set_range` for more
information.
"""
fname = palette.get('filename', None)
colors = palette.get('colors', None)
# are colors between 0-255 or 0-1
color_scale = palette.get('color_scale', 255)
if fname:
cmap = _create_colormap_from_file(fname, palette, color_scale)
elif isinstance(colors, (tuple, list)):
cmap = _create_colormap_from_sequence(colors, palette, color_scale)
elif isinstance(colors, str):
import copy
from trollimage import colormap
cmap = copy.copy(getattr(colormap, colors))
else:
raise ValueError("Unknown colormap format: {}".format(palette))
if palette.get("reverse", False):
cmap.reverse()
if 'min_value' in palette and 'max_value' in palette:
cmap.set_range(palette["min_value"], palette["max_value"])
elif 'min_value' in palette or 'max_value' in palette:
raise ValueError("Both 'min_value' and 'max_value' must be specified")
return cmap

  • in the classmethod trollimage.colormap.Colormap.from_file:

https://github.com/pytroll/trollimage/blob/75e2e8d7967f75a146fe56b48fde9999502b2b67/trollimage/colormap.py#L399-L460

The latter even duplicates some lines from Satpy verbatim.

This split makes the functionality hard to find and hard to maintain.

Describe the solution you'd like

I would like one centralised place for colormap creation, probably as classmethods under trollimage.colormap.Colormap. All other interfaces will be deprecated and will just call the trollimage one, or be thin layers that extract colormaps (for example, from an auxiliary variable) and pass them on to trollimage.

Describe any changes to existing user workflow

We would deprecate some of the existing satpy interfaces as far as they're not handling satpy-specific API, but maintain backward compatibility for the time being.

Additional context

I ran into this while working on #1844, for which I want to create a colormap from an auxiliary data variable read from a file. This is currently implemented in satpy.composites, but not convenient to use in an enhancement. Rather than duplicating the code in satpy.enhancements, we should have a more flexible colormap creation in trollimage and then only a thin shell in Satpy (the part that deals with extracting the colormap data from the auxiliary variable).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant