Source code for pointAdvection.tools

#!/usr/bin/env python
u"""
tools.py
Written by Tyler Sutterley (09/2024)
Plotting tools for visualization

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 09/2024: use wrapper to importlib for optional dependencies
    Updated 04/2024: add catch for existing colormaps
    Updated 05/2023: using pathlib to define and expand paths
    Updated 12/2022: use f-strings for ascii and verbose formatting
    Written 06/2022
"""
import re
import pathlib
import colorsys
import numpy as np
import pointAdvection.utilities
# attempt imports
cm = pointAdvection.utilities.import_dependency('matplotlib.cm')
colors = pointAdvection.utilities.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).expanduser().absolute() with filename.open(mode='r', encoding='utf8') as f: file_contents = f.read().splitlines() # extract basename from cpt filename name = re.sub(r'\.cpt', '', filename.name, flags=re.I) # 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.,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(name, 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.,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() # catch exception if colormap already exists try: cm.register_cmap(name=name, 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 """ # 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'Unknown color map {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