Source code for icesat2_toolkit.tools

#!/usr/bin/env python
u"""
tools.py
Written by Tyler Sutterley (05/2024)
Plotting tools and utilities

PYTHON DEPENDENCIES:
    numpy: Scientific Computing Tools For Python
        https://numpy.org
        https://numpy.org/doc/stable/user/numpy-for-matlab-users.html
    matplotlib: Python 2D plotting library
        http://matplotlib.org/
        https://github.com/matplotlib/matplotlib

UPDATE HISTORY:
    Updated 05/2024: use wrapper to importlib for optional dependencies
    Updated 04/2024: add catch for existing colormaps
    Updated 03/2024: use pathlib to define and operate on paths
    Updated 04/2022: updated docstrings to numpy documentation format
    Updated 12/2021: added custom colormap function for some common scales
    Written 09/2021
"""
import re
import copy
import pathlib
import colorsys
import warnings
import numpy as np
from icesat2_toolkit.utilities import import_dependency

# attempt imports
cm = import_dependency('matplotlib.cm')
colors = import_dependency('matplotlib.colors')

[docs]def from_cpt(filename, use_extremes=True, **kwargs): """ Reads GMT color palette table files and registers the colormap to be recognizable by ``plt.cm.get_cmap()`` Can import HSV (hue-saturation-value) or RGB values Parameters ---------- filename: str color palette table file use_extremes: bool, default True use the under, over and bad values from the cpt file **kwargs: dict optional arguments for LinearSegmentedColormap """ # read the cpt file and get contents filename = pathlib.Path(filename) with filename.open(mode='r', encoding='utf-8') as f: file_contents = f.read().splitlines() # compile regular expression operator to find numerical instances rx = re.compile(r'[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?') # create list objects for x, r, g, b x, r, g, b = ([], [], [], []) # assume RGB color model colorModel = "RGB" # back, forward and no data flags flags = dict(B=None, F=None, N=None) for line in file_contents: # find back, forward and no-data flags model = re.search(r'COLOR_MODEL.*(HSV|RGB)', line, re.I) BFN = re.match(r'[BFN]', line, re.I) # parse non-color data lines if model: # find color model colorModel = model.group(1) continue elif BFN: flags[BFN.group(0)] = [float(i) for i in rx.findall(line)] continue elif re.search(r"#", line): # skip over commented header text continue # find numerical instances within line x1, r1, g1, b1, x2, r2, g2, b2 = rx.findall(line) # append colors and locations to lists x.append(float(x1)) r.append(float(r1)) g.append(float(g1)) b.append(float(b1)) # append end colors and locations to lists x.append(float(x2)) r.append(float(r2)) g.append(float(g2)) b.append(float(b2)) # convert input colormap to output xNorm = [None]*len(x) if (colorModel == "HSV"): # convert HSV (hue-saturation-value) to RGB # calculate normalized locations (0:1) for i,xi in enumerate(x): rr,gg,bb = colorsys.hsv_to_rgb(r[i]/360.0, g[i], b[i]) r[i] = rr g[i] = gg b[i] = bb xNorm[i] = (xi - x[0])/(x[-1] - x[0]) elif (colorModel == "RGB"): # normalize hexadecimal RGB triple from (0:255) to (0:1) # calculate normalized locations (0:1) for i,xi in enumerate(x): r[i] /= 255.0 g[i] /= 255.0 b[i] /= 255.0 xNorm[i] = (xi - x[0])/(x[-1] - x[0]) # output RGB lists containing normalized location and colors cdict = dict(red=[None]*len(x),green=[None]*len(x),blue=[None]*len(x)) for i,xi in enumerate(x): cdict['red'][i] = [xNorm[i], r[i], r[i]] cdict['green'][i] = [xNorm[i], g[i], g[i]] cdict['blue'][i] = [xNorm[i], b[i], b[i]] # create colormap for use in matplotlib cmap = colors.LinearSegmentedColormap(filename.stem, cdict, **kwargs) # set flags for under, over and bad values extremes = dict(under=None,over=None,bad=None) for key, attr in zip(['B', 'F', 'N'], ['under', 'over', 'bad']): if flags[key] is not None: r,g,b = flags[key] if (colorModel == "HSV"): # convert HSV (hue-saturation-value) to RGB r, g, b = colorsys.hsv_to_rgb(r/360.0, g, b) elif (colorModel == 'RGB'): # normalize hexadecimal RGB triple from (0:255) to (0:1) r, g, b = (r/255.0, g/255.0, b/255.0) # set attribute for under, over and bad values extremes[attr] = (r, g, b) # create copy of colormap with extremes if use_extremes: cmap = cmap.with_extremes(**extremes) # register colormap to be recognizable by cm.get_cmap() try: cm.register_cmap(name=filename.stem, cmap=cmap) except: pass # return the colormap return cmap
[docs]def custom_colormap(N, map_name, **kwargs): """ Calculates a custom colormap and registers it to be recognizable by ``plt.cm.get_cmap()`` Parameters ---------- N: int number of slices in initial HSV color map map_name: str name of color map - ``'Joughin'``: [Joughin2018]_ standard velocity colormap - ``'Rignot'``: [Rignot2011]_ standard velocity colormap - ``'Seroussi'``: [Seroussi2011]_ velocity divergence colormap **kwargs: dict optional arguments for LinearSegmentedColormap References ---------- .. [Joughin2018] I. Joughin, B. E. Smith, and I. Howat, "Greenland Ice Mapping Project: ice flow velocity variation at sub-monthly to decadal timescales", *The Cryosphere*, 12, 2211--2227, (2018). `doi: 10.5194/tc-12-2211-2018 <https://doi.org/10.5194/tc-12-2211-2018>`_ .. [Rignot2011] E. Rignot J. Mouginot, and B. Scheuchl, "Ice Flow of the Antarctic Ice Sheet", *Science*, 333(6048), 1427--1430, (2011). `doi: 10.1126/science.1208336 <https://doi.org/10.1126/science.1208336>`_ .. [Seroussi2011] H. Seroussi, M. Morlighem, E. Rignot, E. Larour, D. Aubry, H. Ben Dhia, and S. S. Kristensen, "Ice flux divergence anomalies on 79north Glacier, Greenland", *Geophysical Research Letters*, 38(L09501), (2011). `doi: 10.1029/2011GL047338 <https://doi.org/10.1029/2011GL047338>`_ """ # make sure map_name is properly formatted map_name = map_name.capitalize() if (map_name == 'Joughin'): # calculate initial HSV for Ian Joughin's color map h = np.linspace(0.1,1,N) s = np.ones((N)) v = np.ones((N)) # calculate RGB color map from HSV color_map = np.zeros((N,3)) for i in range(N): color_map[i,:] = colorsys.hsv_to_rgb(h[i],s[i],v[i]) elif (map_name == 'Seroussi'): # calculate initial HSV for Helene Seroussi's color map h = np.linspace(0,1,N) s = np.ones((N)) v = np.ones((N)) # calculate RGB color map from HSV RGB = np.zeros((N,3)) for i in range(N): RGB[i,:] = colorsys.hsv_to_rgb(h[i],s[i],v[i]) # reverse color order and trim to range RGB = RGB[::-1,:] RGB = RGB[1:np.floor(0.7*N).astype('i'),:] # calculate HSV color map from RGB HSV = np.zeros_like(RGB) for i,val in enumerate(RGB): HSV[i,:] = colorsys.rgb_to_hsv(val[0],val[1],val[2]) # calculate saturation as a function of hue HSV[:,1] = np.clip(0.1 + HSV[:,0], 0, 1) # calculate RGB color map from HSV color_map = np.zeros_like(HSV) for i,val in enumerate(HSV): color_map[i,:] = colorsys.hsv_to_rgb(val[0],val[1],val[2]) elif (map_name == 'Rignot'): # calculate initial HSV for Eric Rignot's color map h = np.linspace(0,1,N) s = np.clip(0.1 + h, 0, 1) v = np.ones((N)) # calculate RGB color map from HSV color_map = np.zeros((N,3)) for i in range(N): color_map[i,:] = colorsys.hsv_to_rgb(h[i],s[i],v[i]) else: raise ValueError(f'Incorrect color map specified ({map_name})') # output RGB lists containing normalized location and colors Xnorm = len(color_map) - 1.0 cdict = dict(red=[None]*len(color_map), green=[None]*len(color_map), blue=[None]*len(color_map)) for i,rgb in enumerate(color_map): cdict['red'][i] = [float(i)/Xnorm,rgb[0],rgb[0]] cdict['green'][i] = [float(i)/Xnorm,rgb[1],rgb[1]] cdict['blue'][i] = [float(i)/Xnorm,rgb[2],rgb[2]] # create colormap for use in matplotlib cmap = colors.LinearSegmentedColormap(map_name, cdict, **kwargs) # register colormap to be recognizable by cm.get_cmap() try: cm.register_cmap(name=map_name, cmap=cmap) except: pass # return the colormap return cmap