Color Map Utilities

Matplotlib provides numerous built-in colormaps and an excellent tutorial on Choosing Colormaps. Various colormaps and styles third party packages are available that extend Matplotlib.

Familiarity with these built-in color maps is suggested. The sequential built-in maps are particularly useful for 3D surface visualizations.

Choosing colormaps for surfaces in 3D has the additional requirement of enhancing the geometric visualization along with defining functional values. S3Dlib provides multiple easy-to-use methods for creating custom colormaps which are particularly applicable for displaying 3D surfaces. These methods are particularly useful when transparent and mirrored colormaps are needed for clear visualizations of 3D surfaces, or simply for a more graphic display. All colormaps using these functions are derived from matplotlib.colors.Colormap. These functions are available in the module:

import s3dlib.cmap_utilities as cmu

In the following discussion, example colormaps are provided to illustrate the usage of the S3Dlib methods.

RGB Linear Gradient

Simple color maps, linear in RGB space, are created using the function

cmu.rgb_cmap_gradient(lowColor,highColor,name=None,mirrored=False)

which returns an instance of a registered matplotlib.colors.Colormap with name. When name is None, the name is assigned an eight-character string of random upper-case letters. The color arguments are in Matplotlib Colors format. The colormap monotonically increases from the lowColor to the highColor in RGB color space. The mirrored argument is a boolean and if set True, the color map is mirrored with the highcolor at the center. Mirrored colormaps are particularly useful for non-orientable surfaces. See Non-Orientable Surface.

Several examples using this function are:

cmaps['RGB Gradient'] = [
            'rgbDefault', 'mpDefault',
            'RdToGn', 'YWToBu', 'cardboard', 'cardboardMrrd',
            'bnShade', 'WtToBk', 'BkToWtMrrd', 'WtToBkMrrd' ]

cmu.rgb_cmap_gradient( name='rgbDefault')
cmu.rgb_cmap_gradient( 'black', [0.122, 0.467, 0.706], 'mpDefault' )

cmu.rgb_cmap_gradient( 'red', 'green', 'RdToGn' )
cmu.rgb_cmap_gradient( 'yellow', 'blue', 'YWToBu' )
cmu.rgb_cmap_gradient( [0.25,0.15,0], [1,.9,.75], 'cardboard' )
cmu.rgb_cmap_gradient( [0.25,0.15,0], [1,.9,.75], 'cardboardMrrd', mirrored=True )

cmu.rgb_cmap_gradient( [0.25,0.15,0], 'white', 'bnShade' )
cmu.rgb_cmap_gradient( 'white', 'black', 'WtToBk' )
cmu.rgb_cmap_gradient( 'black', 'white', 'BkToWtMrrd', mirrored=True )
cmu.rgb_cmap_gradient( 'white', 'black', 'WtToBkMrrd', mirrored=True )

Note that the colormap with assigned name ‘rgbDefault’ is the default colormap if no color arguments are provided.

../../_images/cmap_rgb_grad.png

HSV Linear Gradient

Simple color maps, linear in HSV space, are created using the function

cmu.hsv_cmap_gradient(lowHSV,hiHSV,name=None,mirrored=False)

which returns an instance of a registered matplotlib.colors.Colormap with name. When name is None, the name is assigned an eight-character string of random upper-case letters. The lowHSV and hiHSV are HSV or HSVA (hue, saturation, value, alpha) arrays of float values. The float values for SVA range in [0,1].

The float values for hue range in [0,2], which is not the standard range for hue. This was defined in this manner to allow for direction in the cyclic definition of hue. This hue mapping from 0 to 2 is shown below.

../../_images/hue_colorbar.png

For example, consider a color map going from green (low) to blue (high). Specifying green and blue as 0.33 and 0.67, the map will pass through cyan. However, specifying green as 1.33, the map will pass through yellow, red and magenta. The colormap still monotonically increases from the lowColor to the highColor in HSV space. The mirrored argument is applied in the same manner as previously described for the RGB gradient color maps.

Hue

The following are examples of varying the cyclic hue value of the color arguments in both the forward and backward directions. In these examples, the saturation and value for the low and high color arguments were held constant at 1.

cmaps['Hue Gradient'] = [
            'hsvDefault',
            'hsvRdToGn', 'hsvRdFromGn', 'hsvYwToBu','hsvYwFromBu',
            'RdToRd', 'RdFromRd', 'RdToRdMrrd', 'RdFromRdMrrd',
            'BlToBl', 'BlFromBl', 'BlToBlMrrd', 'BlFromBlMrrd' ]

cmu.hsv_cmap_gradient( name='hsvDefault')

cmu.hsv_cmap_gradient( [0,1,1], [0.333,1,1], 'hsvRdToGn' )
cmu.hsv_cmap_gradient( [1,1,1], [0.333,1,1], 'hsvRdFromGn' )
cmu.hsv_cmap_gradient( [0.166,1,1], [0.666,1,1], 'hsvYwToBu' )
cmu.hsv_cmap_gradient( [1.166,1,1], [0.666,1,1], 'hsvYwFromBu' )

cmu.hsv_cmap_gradient( [0,1,1], [1,1,1], 'RdToRd' )
cmu.hsv_cmap_gradient( [1,1,1], [0,1,1], 'RdFromRd' )
cmu.hsv_cmap_gradient( [0,1,1], [1,1,1],'RdToRdMrrd', mirrored=True )
cmu.hsv_cmap_gradient( [1,1,1], [0,1,1],'RdFromRdMrrd', mirrored=True )

cmu.hsv_cmap_gradient( [0.666,1,1], [1.666,1,1], 'BlToBl' )
cmu.hsv_cmap_gradient( [1.666,1,1], [0.666,1,1], 'BlFromBl' )
cmu.hsv_cmap_gradient( [0.666,1,1], [1.666,1,1], 'BlToBlMrrd', mirrored=True )
cmu.hsv_cmap_gradient( [1.666,1,1], [0.666,1,1], 'BlFromBlMrrd', mirrored=True )

Note that the colormap with the assigned name ‘hsvDefault’ is the default colormap if no color arguments are provided. This colormap is equivalent to the Matplotlib built-in colormap named ‘hsv.’

../../_images/cmap_hue_grad.png

The effect of reversing the order of the low and high color arguments is most notable by comparing the maps ‘RdToRd’ versus ‘RdFromRd’. Both start and end with red. Also, the difference between using RGB and HSV is seen by comparing the yellow to blue colormaps in these two-color spaces, ‘YwToBu’ versus ‘hsvYwToBu’.

Saturation and Value

In these examples, the hue was constant at 0 (red). The first four colormaps vary value [0,1] while holding saturation to 1. The second set varies saturation [0,1] and hold value constant at 1.

cmaps['Saturation and Value Gradient'] = [
            'RdToBk', 'BkFromRd', 'RdToBkMrrd', 'BkFromRdMrrd',
            'RdToWt', 'WtFromRd', 'RdToWtMrrd', 'WtFromRdMrrd' ]

cmu.hsv_cmap_gradient( [0,1,1], [0,1,0], 'RdToBk' )
cmu.hsv_cmap_gradient( [0,1,0], [0,1,1], 'BkFromRd' )
cmu.hsv_cmap_gradient( [0,1,1], [0,1,0], 'RdToBkMrrd', mirrored=True )
cmu.hsv_cmap_gradient( [0,1,0], [0,1,1], 'BkFromRdMrrd', mirrored=True )

cmu.hsv_cmap_gradient( [0,1,1], [0,0,1], 'RdToWt' )
cmu.hsv_cmap_gradient( [0,0,1], [0,1,1], 'WtFromRd' )
cmu.hsv_cmap_gradient( [0,1,1], [0,0,1], 'RdToWtMrrd', mirrored=True )
cmu.hsv_cmap_gradient( [0,0,1], [0,1,1], 'WtFromRdMrrd', mirrored=True )
../../_images/cmap_satval_grad.png

Cyclic and Named Colors

Since the hue is cyclic, any same hue can be represented by a number plus an integer value. For example, blue hue is 0.67, 1.67, 2.66, 3.67, etc. This results in the ability to produce repeated color maps by representing the low and high arguments with values greater than 2.

Also, the arguments can be strings of named Matplotlib colors. See for example named colors. To shift the named color hues above 1, the color string is prefixed with a ‘+’ character. This provides the method of reversing the direction of the hue while still using named color values.

Examples using cyclic and named color for HSV color maps are:

cmaps['Cyclic and Named Color'] = [
            'red3', 'blue2R', 'goldteal', 'plgrnOrchR',
            'pgrdkcy', 'hpnktrq', 'pgrdkcy_RGB', 'hpnktrq_RGB' ]

cmu.hsv_cmap_gradient( [0,1,1], [3,1,1], 'red3' )
cmu.hsv_cmap_gradient( [2.666,1,1], [0.666,1,1], 'blue2R' )
cmu.hsv_cmap_gradient( 'gold','teal', 'goldteal' )
cmu.hsv_cmap_gradient( '+palegreen','orchid', 'plgrnOrchR' )

cmu.hsv_cmap_gradient( 'palegoldenrod','darkcyan', 'pgrdkcy' )
cmu.hsv_cmap_gradient( 'hotpink','turquoise', 'hpnktrq' )

cmu.rgb_cmap_gradient( 'palegoldenrod','darkcyan', 'pgrdkcy_RGB' )
cmu.rgb_cmap_gradient( 'hotpink','turquoise', 'hpnktrq_RGB' )

This produces the following colormaps.

../../_images/cmap_cyclic.png

In the above, the last two colormaps use the RGB gradient method as a comparison to the map produced by the HSV gradient method which precedes them. This exemplifies the effect of a reduction in saturation and value while linearly moving through RGB space.

Hue HSV Modifications

While varying hue in HSV color space, there is an apparent ‘sharpness’ or rapid change in apparent color at yellow, cyan, and magenta. This is seen in ‘hsv’ colormap shown below.

../../_images/hsv_colormap.png

This perceptual change is also reflected in the rapid change in CIELAB color space represented by lightness, L*. (A more detailed discussion of this is in Choosing Colors in Matplotlib ).

To smooth-out this sharpness in the HSV colormap, the following method is available:

cmu.hue_cmap(lowHue=0, hiHue=None, smooth=1.6, name=None)

Which returns a colormap of constant value and saturation of 1. The smooth parameter is the degree of smoothing at yellow, cyan and magenta, with a value of 1 for no smoothing. For values greater than one, hues around yellow, cyan and magenta are expanded, reducing the extent of red, green and blue hues. For values less than one, the opposite occurs. The smooth value range is [0.1,10]. The default value is 1.6. The parameters lowHue and highHue are similary defined for the hue as given in the previous sections. When color names are used for this parameter, only the hue value of the color is used.

Example colormaps using this method are given below.

cmaps['Hue HSV'] = [
            'hue_05', 'hsvDefault', 'HUEdefault','hue_80',
            'hueYwToYw8', 'hueYwToBu8', 'hueYwFromBu8', 'hueYwFromBu5' ]

cmu.hue_cmap(    name='HUEdefault' )
cmu.hue_cmap(smooth=8.0,name='hue_80')
cmu.hue_cmap(smooth=0.5,name='hue_05' )
cmu.hue_cmap( 0.166,smooth=8.0, name='hueYwToYw8')
cmu.hue_cmap('y', 'b',8.0,    'hueYwToBu8')
cmu.hue_cmap('+y','b',8.0,    'hueYwFromBu8')
cmu.hue_cmap('+y','b',0.5,    'hueYwFromBu5')

../../_images/cmap_hueHSV.png

The effect of lightness, L*, on smoothing is shown in the grayscale maps below, for the first four colormaps.

../../_images/cmap_grayscale.png

A qualitative description of smoothing by examining the L* numerical values is shown in the plot below. The effect of smoothing is illustrated in this plot showing the peaks, valleys and plateaus of the lightness curves at CMY (0.167, 0.5, 0.833) and RGB (0.0, 0.33, 0.67).

../../_images/cmap_Lplot.png

The script for producing the above plot is given below.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colormaps
from matplotlib.colors import ListedColormap
from colorspacious import cspace_converter
import s3dlib.cmap_utilities as cmu

def Lstar(cmap) :
    ''' x,y arrays for ploting L* of a colormap.'''
    if isinstance(cmap,str) : cmap = colormaps[cmap]
    N = 256
    if isinstance(cmap,ListedColormap) : N = len(cmap.colors)
    rgb = cmap(range(N))[:,0:3]
    lab = cspace_converter("sRGB1", "CAM02-UCS")(rgb)        
    L = np.transpose(lab)[0]
    x = np.linspace(0, 1, N)
    return x,L
# ...................................................
vhsv, Lhsv   = Lstar('hsv')
vdflt, Ldflt = Lstar(cmu.hue_cmap())
v_80, L_80   = Lstar(cmu.hue_cmap(smooth=8))
v_05, L_05   = Lstar(cmu.hue_cmap(smooth=0.5))

fig = plt.figure(figsize=(6,2.5))
ax = fig.add_subplot(1,1,1)
ax.set(xlim=(0,1), ylim=(0,100))
ax.set_ylabel('L* value')

ax.plot(v_05,L_05,    label='0.5 ',         color='g', linestyle='-.')
ax.plot(vhsv, Lhsv,   label='1.0, HSV',     color='k')
ax.plot(vdflt, Ldflt, label='1.6, default', color='r')
ax.plot(v_80,L_80,    label='8.0  ',        color='y', linestyle=':')
ax.legend()

plt.show()

The effect of smoothing out an HSV colormap when applied to a surface is shown in the simple surface plot below.

../../_images/hue_torus.png

Smooth HSV

The ‘smooth’ parameter is also available when using the hsv_cmap_gradient method. This effect is most apparent when creating colormaps between the RGB colors. The regular and smooth colormaps are shown as a comparison.

cmaps['Smoothed HSV'] = [
            'RG', 'RG_s', 'GB','GB_s',
            'BR', 'BR_s']

cmu.hsv_cmap_gradient( [0,1,1],      [0.333,1,.65], 'RG' )
cmu.hsv_cmap_gradient( [0,1,1],      [0.333,1,.65], 'RG_s', smooth=1.6 )
cmu.hsv_cmap_gradient( [.333,1,.34], [0.666,1,1],   'GB' )
cmu.hsv_cmap_gradient( [.333,1,.34], [0.666,1,1],   'GB_s', smooth=1.4 )
cmu.hsv_cmap_gradient( [0.666,1,1],  [0.999,1,.52],     'BR' )
cmu.hsv_cmap_gradient( [0.666,1,1],  [0.999,1,.52],     'BR_s', smooth=1.6 )
../../_images/cmap_smooth.png

In these examples, the red and green values were selected with reduced values to create maps with similar starting and ending L* values, as seen in the following plot.

../../_images/cmap_Lplot2.png

Lab Linear Gradient

A colormap with a linearly monotonic increasing L* values may be preferable for increased perception when used for illustrating surface geometries. This type of map is also beneficial when surface geometry visualization is needed when printed on black and white printers. Numerous sequential colormaps to choose from are by Matplotlib in the section Sequential.

The cmap_xtra file provides the Lab_cmap_gradient method to construct colormaps which are linear in Lab space. Consequently, these maps will be linear in L*. Linear Lab colormaps are created using a function from a file and creating the cmap as:

import s3dlib.cmap_xtra as cmx
cmap = cmx.Lab_cmap_gradient(lowColor='k', highColor='w', name=None, mirrored=False)

All calling parameters are identical to those used in the rgb_cmap_gradient method.

Examples of colormaps using Lab_cmap_gradient are given below.

cmaps['Lab Gradient'] = [
            'magma', 'viridis', 'lab_indg','lab_marn',
            'lab_dgrn', 'lab_RdWt', 'RdToWt' ]

cmx.Lab_cmap_gradient('indigo',   'aqua',        'lab_indg')
cmx.Lab_cmap_gradient('maroon',   'yellow',      'lab_marn')
cmx.Lab_cmap_gradient('darkgreen','lemonchiffon','lab_dgrn')
cmx.Lab_cmap_gradient('red',      'white',       'lab_RdWt')

The resulting colormaps are below. The linear Matplotlib colormaps of ‘magma’ and ‘viridis’ are shown as a comparison to those generated using this function.

../../_images/cmap_lab.png

Grayscale maps for these colormaps illustrate the perceived lightness values.

../../_images/cmap_grayscale_lab.png

The plot below quantitatively demonstrates the linear nature of the colormaps and the relative differences between the maps.

../../_images/cmap_Lplot3.png

Lab Linear, Hue Constant Gradient

Colormap having a linear lightness gradient from a low, lowL, to a high, hiL, with a constant value of hue can be produced using the method:

import s3dlib.cmap_xtra as cmx
cmap = cmx.Hue_Lab_gradient(color,lowL=0.0, hiL=1.0, name=None)

where color is a Matplotlib named color, a three-element list or a single numerical value which will be used to set the H value of the gradient. The range for the lowL and hiL arguments is [0,1]. For example, using this method as:

cmaps['Hue Lab Gradient'] = [
    'gray','red_L', 'yellow_L', 'green_L', 'cyan_L', 'blue_L','magenta_L' ]

cmx.Hue_Lab_gradient('red')
cmx.Hue_Lab_gradient('yellow')
cmx.Hue_Lab_gradient('green')
cmx.Hue_Lab_gradient('cyan')
cmx.Hue_Lab_gradient('blue')
cmx.Hue_Lab_gradient('magenta')

results in colormaps as:

../../_images/cmap_hue_lab.png

with corresponding L* plots:

../../_images/cmap_hue_lab_Lplot.png

Colormaps with a linear gradient in L* may be a substitute for the shading using the surface method map_cmap_from_normal. The following is a comparison among the above colormaps when surface mapping to a sphere.

../../_images/hue_lab.png

In the above figure, the Matplotlib named colormap ‘gray’ and shading a white sphere is also shown as a reference.

Lab Linear of a Colormap

Colormaps having a linear lightness gradient from a low, lowL, to a high, hiL, with a colormap hue of maximum Saturation can be produced using the method:

import s3dlib.cmap_xtra as cmx
cmap = cmx.Hue_Lab_gradient(colormap, lowL=None, hiL=None, name=None)

where colormap is a colormap, which will be to set the H values of the gradient.

If assigned, the range for the lowL and hiL arguments is [0,1]. If not defined, the domain is the range taken from the colormap. For example, using this method as:

cmNm = ['viridis','plasma','Reds','YlGn','spring','autumn','copper']
cPairs = zip( cmNm , [ n+'_L' for n in cmNm] )
clst = [item for sub_list in cPairs for item in sub_list]

cmaps['Cmap Lab Gradient'] = clst

for cmVal in clst : cmx.Cmap_Lab_gradient(cmVal)

results in colormaps as:

../../_images/cmap_cmap_lab.png

with corresponding L* plots:

../../_images/cmap_cmap_lab_Lplot.png

This method is most suitable using sequential colormaps since the resulting colormap is linear from start to finish. The effect of this method is to, in general, increase the color saturation of the colormap when the second-derivative of L* is negative. This is seen in the Matplotlib colormaps of viridis, plasma, Reds, YlGn, and copper. The opposite effect of reducing saturation occurs for colormaps with positive derivativess, evident for spring and autumn.

Stitch Cmaps

Colormaps may be created from concatenated colormaps using

cmu.stitch_cmap( *maps, bndry=None, name=None)

which returns an instance of a registered matplotlib.colors.Colormap with name. When name is None, the name is assigned an eight-character string of random upper-case letters. The bndry argument is a float or a list of float values designating the boundaries between the input maps. Values range from greater than 0 to less than 1. If bndry is None (default), the input maps are evenly spaced.

cmaps['Stitch'] = [
            'fWt_2', 'inferno_3', 'RdCy_4',
            'Diverging*', 'Cyclic*', 'stchG' ]

cmA = cmu.hsv_cmap_gradient('firebrick','wheat',smooth=1.5)
cmB = cmu.hsv_cmap_gradient('wheat','teal',smooth=1.5)
cmu.stitch_cmap(cmA,cmB,name='fWt_2')

cmu.mirrored_cmap('inferno',rev=True)
testMap3 = cmu.stitch_cmap('inferno','inferno_mr','inferno_r',bndry=[0.2,0.6],name='inferno_3' )

cmu.reversed_cmap('RdToBk')
cmu.hsv_cmap_gradient( [0.5,1,1], [0.5,1,0], 'CyToBk' )
cmu.reversed_cmap('CyToBk')
cmu.stitch_cmap('RdToBk_r', 'RdToBk','CyToBk_r', 'CyToBk',bndry=[0.3,0.5,0.8], name='RdCy_4' )

cmC = cmx.Lab_cmap_gradient('olive','paleturquoise')
cmD = cmx.Lab_cmap_gradient('paleturquoise','mediumslateblue')
cmu.stitch_cmap(cmC,cmD,name='Diverging*')

cmE,cmF = cmx.Lab_cmap_gradient('black','mediumvioletred'), cmx.Lab_cmap_gradient('mediumvioletred','white'),
cmG,cmH = cmx.Lab_cmap_gradient('white','green'), cmx.Lab_cmap_gradient('green','black')
cmu.stitch_cmap(cmE,cmF,cmG,cmH,name='Cyclic*')

cmu.hsv_cmap_gradient( [0,1,.8],        [0,0,1],       'redG')
cmu.hsv_cmap_gradient( 'saddlebrown',   'yellow',      'yelG')
cmu.hsv_cmap_gradient( 'darkgreen',     'lime',        'grnG')
cmu.hsv_cmap_gradient( 'darkslategray', 'cyan',        'cynG')
cmu.hsv_cmap_gradient( 'midnightblue',  'deepskyblue', 'bluG')
cmu.hsv_cmap_gradient( [0.833,1,0.1],   'magenta',     'mgnG')
cmu.stitch_cmap( 'mgnG','bluG','cynG', 'grnG','yelG', 'redG', name='stchG' )

This method can be used to construct simple linear tricolor or multi-color colormaps, for example fWt_2. Specific colormap values can be emphasized using this method as seen in the inferno_3 and RdCy_4 examples, (0.2, 0.6) and (0.3,0.8) respectively. Using Lab colormaps, Diverging and Cyclic colormaps can be constructed.

../../_images/cmap_stitch.png

Stitch Colors

Simple multi-color maps are created using the function:

cmu.stitch_color(*colorVals, bndry=None, name=None )

which returns an instance of a registered matplotlib.colors.Colormap with name. When name is None, the name is assigned an eight-character string of random upper-case letters. The bndry argument is a float or a list of float values designating the boundaries between the input colors. Values range from greater than 0 to less than 1. If bndry is None (default), the input colors are evenly spaced.

The following is equivalent for two-color colormaps:

cmu.binary_cmap(negColor='b',posColor='r',name=None,bndry=None)

but in this case, the division between the colors are set by the single float bndry argument, which ranges from 0.03 to 0.97, with a default value of 0.5. Binary colormaps are particularly useful for having different colors for front and back surfaces. For example, see Inner/Outer Surface Colormap and Inner and Outer Surface Coloring.

cmaps['Multi-color'] = [
            'binaryDefault', 'binaryRdBu', 'rygcb', 'binaryBuGnLw' ]

cmu.binary_cmap( name='binaryDefault' )
cmu.binary_cmap( [1,0,0], [0,0,1], 'binaryRdBu'  )
cmu.stitch_color( 'red', 'yellow', 'green', 'cyan','blue', bndry=[0.1,.4,.7,.8,],name='rygcb'  )
cmu.binary_cmap( [0,0,.5], [0,.5,0], 'binaryBuGnLw',0.33  )
../../_images/cmap_binary.png

Mirrored and Reversed

Any registered colormap can have the order reversed using

cmu.reversed_cmap(cmap,name=None)

which returns a new instance of a registered colormap. The returned colormap name is the cmap name with the suffix ‘_r’.

Any registered colormap can be used to create a mirrored color map using

cmu.mirrored_cmap(cmap,name=None,rev=False)

The returned colormap name is the cmap name with the suffix ‘_m’. If the rev argument is True, the returned colormap is also reversed and the name has the suffix ‘_mr’.

cmaps['Mirrored and Reversed'] = [
            'viridis', 'plasma', 'magma', 'cividis',
            'viridis_r', 'plasma_r', 'magma_r', 'cividis_r',
            'viridis_m', 'plasma_m', 'magma_m', 'cividis_m',
            'viridis_mr', 'plasma_mr', 'magma_mr', 'cividis_mr' ]
            
cmu.reversed_cmap('viridis')
cmu.reversed_cmap('plasma')
cmu.reversed_cmap('magma')
cmu.reversed_cmap('cividis')

cmu.mirrored_cmap('viridis')
cmu.mirrored_cmap('plasma')
cmu.mirrored_cmap('magma')
cmu.mirrored_cmap('cividis')

cmu.mirrored_cmap('viridis', rev=True)
cmu.mirrored_cmap('plasma', rev=True)
cmu.mirrored_cmap('magma', rev=True)
cmu.mirrored_cmap('cividis', rev=True)
../../_images/cmap_mirror_rev.png

Creating Cmaps Using Functions

The previous functions provide methods for creating colormaps which are linear in RGB, HSV or Lab color spaces. For a more general approach, colormaps may be created using a function returning the color component values using

cmu.op_cmap(op,rgb=True,name=None)

where op is a function that takes one argument, a N Numpy array of float numbers in the domain of 0 to 1. The function returns a 3XN or 4XN array of color values. By default, RGB or RGBA color values are returned by the operation function. If the rgb argument is set False, the operation returns HSV color values.

This returns a registered colormap with the name given by the name argument. When name is None, the colormap is named using the function name. For a lambda function, the name is assigned an eight-character string of random upper-case letters.

def demoRGB(t):      #... function for RGB
    r = (0.5*( 1+ np.cos(2*np.pi*t) ))**2
    g = (0.5*( 1+ np.sin(2*np.pi*t) ))**2
    b = t*1   
    return r,g,b

def demoHSV(t) :     #... function for HSV (smoothed 'jet' like)
    rgb = cmu.hsv_cmap_gradient( [0.67,1,1],[0,1,1],smooth=1.6)(t)[:,0:3]
    h,s,v = cm.colors.rgb_to_hsv(rgb).T
    S = 1 - .5*(np.sin(np.pi*t))**3
    V = 0.5 + 0.5*(np.sin(np.pi*t)**0.5)
    return h,S,V

def cmapSect(t,cmap,s,e) : return colormaps[cmap](s + (e-s)*t).T  # rev. mpl v_3.8
def midTerrain(t) : return cmapSect(t,'terrain',.25,.75)  # function using Matplotlib 'terrain'

def RGBAtrans(t) :   #... function for RGBA using Matplotlib 'hsv'
    r,g,b,a = colormaps['hsv'](t).T  # rev. mpl v_3.8
    a = (0.5*( 1+ np.cos(6*np.pi*t) ))**6
    return r,g,b,a

def CMYblack(t):     #... function for HSV
    s = np.full(len(t),1)
    v = (0.5*( 1- np.cos(6*np.pi*t) ))**6
    return t,s,v

cmu.op_cmap(midTerrain)
cmu.op_cmap(demoHSV,False)
cmu.op_cmap(demoRGB)
cmu.op_cmap(RGBAtrans)
cmu.op_cmap(CMYblack,False)

cmaps = [ 'demoRGB', 'jet', 'demoHSV', 'terrain', 'midTerrain', 'hsv', 'RGBAtrans', 'CMYblack' ]
../../_images/cmap_op.png

Note that in the above script and plot, the function names are assigned to the colormap names since the name argument was not defined when op_cmap was called.

The colormap utilities also include the section_cmap function which creates a colormap from a predefined colormap, similar to the midTerrain example shown above.

Transparency

Any registered colormap can have the transparency set using

cmu.alpha_cmap(cmap,alpha,constant=False,name=None)

where cmap argument is a cmap or a registered colormap name. The argument alpha ranges from 0 to 1 for fully transparent to opaque, respectively. This function will create and return a cmap with a name with suffix ‘_a’. The argument constant default is False, in which case, all alpha values of the returned colormap are the input values multiplied by alpha. When constant is set to True, all colors in the map will have the same alpha value.

When only the alpha channel is to be modified from a registered colormap, use the method:

cmu.op_alpha_cmap( cmap,operation,fit=True,name=None )

where cmap is a registered colormap and operation is a function returning a numerical value in the domain 0 to 1. When fit is True, the function range is adjusted from 0 to 1. Otherwise, the return value of the operation must be in the range of 0 to 1. There are two advantages of using this method, compared to the op_cmap method. First, the same function may be applied for different colormaps. Second, using the default for fit, the function range is conveniently set from 0 to 1.

def gapJet(t) :
    r,g,b,a = colormaps['jet'](t).T  # rev. mpl v_3.8
    gap = np.logical_and( t>0.4, t<0.6 )  
    a = np.where(gap,np.zeros(a.shape),np.ones(a.shape))
    return r,g,b,a   
cmu.op_cmap(gapJet)

def cosfunc(t) : return np.cos(2*np.pi*t)
# .....................................................................

cmu.alpha_cmap('jet',0.5)
cmu.alpha_cmap('gapJet',0.5)
cmu.alpha_cmap('gapJet',0.5,True,'gapJet_a_True')
cmu.op_alpha_cmap('jet',cosfunc,name='jet_op')
cmu.op_alpha_cmap('cool',cosfunc,name='cool_op')

cmaps = [ 'jet', 'jet_a', 'gapJet', 'gapJet_a', 'gapJet_a_True','jet_op','cool','cool_op']
../../_images/cmap_alpha1.png

In the above example, the gapJet colormap was created to demonstrate the effect of True or False values for the constant argument using an initial colormap with transparency values.

DualCmap

Colormaps map one independent variable, u, to a color, i.e., RGBA = f(u). A DualCmap class object provides a means of mapping two independent variables, u and v, to a color, RGBA = g(u,v). Instead of using an object.map_cmap_from_op method, a two-dimensional colormap may be used with the object.map_color_from_op method which is passed a function as:

def op_function(xyz) :
    u,v = f(xyz)
    return dcmap( u,v )

where f(xyz) is a function of xyz and dcmap is an instance of the DualCmap class. DualCmap objects are constructed from two colormaps, xcmap and ycmap, as:

dcmap = cmu.DualCmap( xcmap, ycmap, kind='sum', norm=True )

where kind indicates the method of combining colormap colors. Values are either predefined types of combinations (‘sum’,’ave’,’ave2’,’srt’) or a float in the range 0.2 to 4.0. The argument norm indicates if the method arguments are to be normalized. Otherwise, the calling arguments to the instance, u and v, should be in the range 0 to 1.

Using the following colormaps:

../../_images/cmap_dualcmap4.png

the resulting DualCmaps can be constructed:

../../_images/dualcmap4.png

The following examples demonstrate using a DualCmap object:

Miscellaneous

Color Space Cmap Comparisons

The colormap functions provide the creation of colormaps which are linear in RGB, HSV or Lab colorspace. A comparison between the differences is shown below using the same initial and final colors for the colormaps. The Matplotlib ‘Blues’ colormap is used as the reference colormap.

../../_images/cmap_rgbhsvlab.png

The lightness, L*, for these colormaps is compared below.

../../_images/cmap_rgbhsvlab_Lplot.png

Since the Lab, HSV_Lab and Blues_L colormaps are linear in Lab space, these line-plots overlap. A visualization of several linear colormaps in both Lab and RGB colorspace are included in the Matplotlib Colormaps example.

The above colormaps were generated using the following script fragment:

from colorspacious import cspace_converter

blues = colormaps['Blues']
strRGB = cm.colors.to_rgb(blues(0.0))
endRGB = cm.colors.to_rgb(blues(1.0))
strHSV = cm.colors.rgb_to_hsv(strRGB)
endHSV = cm.colors.rgb_to_hsv(endRGB)
strLab = cspace_converter("sRGB1", "CAM02-UCS"  )(strRGB)
endLab = cspace_converter("sRGB1", "CAM02-UCS"  )(endRGB)
sLab = strLab[0]/100
eLab = endLab[0]/100

cm_rgb = cmu.rgb_cmap_gradient(strRGB,endRGB,name='RGB')
cm_hsv = cmu.hsv_cmap_gradient(strHSV,endHSV,name='HSV')
cm_lab = cmx.Lab_cmap_gradient(strRGB,endRGB,name='Lab')
cm_Hlb = cmx.Hue_Lab_gradient(endRGB,sLab,eLab,name='Hue_Lab')
cm_Blu = cmx.Cmap_Lab_gradient('Blues')

cmaps = [cm_rgb,cm_hsv,cm_lab,cm_Hlb,cm_Blu,'Blues']

Colormap Figures

Any colormaps can be readily analyzed and compared using the cmap_xtra function:

show_cmaps(plt,cmaps,onlyColormaps=True,show=True)

where plt is matplotlib.pyplot and cmaps is a list of colormaps. The colormap list can also include strings, the names of registered colormaps.

Three figures are generated: colormap images, grayscale colormap images and L* plot. If only the colormap figure is to be generated, the boolean onlyColormaps is set True (the default). By default, the matplotlib.pyplot method show() will be called unless the show argument is set to False.

The following demonstrates three figures for a set of example colormaps.

Visual color scale.

../../_images/cmap_script_1.png

Visual L* gray scale.

../../_images/cmap_script_2.png

x versus L* line plot.

../../_images/cmap_script_3.png

Note that the x,L* plot does not reflect the alpha channel of the colormap.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colormaps
import s3dlib.cmap_utilities as cmu
import s3dlib.cmap_xtra as cmx

#.. Demo use of show_cmaps: colormap figures

# 1. Define colormaps to examime .........................................

def RGBAtrans(t) :   #... function for RGBA 
    r,g,b,a = colormaps['hsv'](t).T  # rev. mpl v_3.8
    a = (0.5*( 1+ np.cos(6*np.pi*t) ))**6
    return r,g,b,a

def demoHSV(t) :      #... function for HSV (smoothed 'jet' like)
    rgb = cmu.hsv_cmap_gradient( [0.67,1,1],[0,1,1],smooth=1.6)(t)[:,0:3]
    h,s,v = cm.colors.rgb_to_hsv(rgb).T
    S = 1 - .5*(np.sin(np.pi*t))**3
    V = 0.5 + 0.5*(np.sin(np.pi*t)**0.5)
    return h,S,V

cmu.hue_cmap(smooth=8.0,name='hue_80')
cmu.hsv_cmap_gradient( [0,1,1],      [0.333,1,.65], 'RG_s', smooth=1.6 )
cmx.Lab_cmap_gradient('darkgreen','lemonchiffon','lab_dgrn')
cmu.binary_cmap( 'blue', 'yellow', 'binaryBuYw'  )
cmu.op_cmap(RGBAtrans)
cmu.alpha_cmap('jet',0.5)
cmu.op_cmap(demoHSV,False)

# 2. Construct figure and plot ...........................................

cmaps = [ 'hue_80', 'RG_s', 'lab_dgrn', 'binaryBuYw', 'RGBAtrans', 'jet_a', 'demoHSV' ]
cmx.show_cmaps(plt,cmaps,False)

Named Colors

There are numerous available colors in the List of named colors in Matplotlib, sorted by hsv color value. When constructing custom colormaps, comparing colors based on the Lab color values is often convenient. The following table lists the named CSS colors sorted by Lab color.

../../_images/colors_by_lab.png

Summary Table

../../_images/cmap_summary.png

The above colormaps were generated using the following script fragment:

def RYGCBM(t):  #... function for op_cmap
    v = (0.5*( 1+np.cos(12*np.pi*t) ))**6
    return t, np.ones(len(t)), v  #.. HSV

cmu.rgb_cmap_gradient('red','blue', name='1.')
cmu.hue_cmap         ('+red','blue', 1.6, name='2.')
cmu.hsv_cmap_gradient('+tomato','lightblue', name='3.')
cmu.stitch_cmap      ('plasma','viridis','inferno',bndry=[0.2,0.6], name='4.')
cmu.stitch_color     ('red','yellow','green','blue',bndry=[0.3,0.4,0.8], name='5.')
cmu.binary_cmap      ('red','blue', name='6.')
cmu.reversed_cmap    ('viridis', name='7.')
cmu.mirrored_cmap    ('viridis', name='8.')
cmu.alpha_cmap       ('viridis',0.75, name='9.')
cmu.op_alpha_cmap    ('viridis',lambda t:np.cos(2*np.pi*t), name='10.' )
cmu.op_cmap          (RYGCBM,False, name='11.')
cmu.section_cmap     ('viridis',0.2,0.8, name='12.')
cmx.Lab_cmap_gradient('red','blue', name='13.')
cmx.Hue_Lab_gradient ('red', name='14.')
cmx.Cmap_Lab_gradient('gist_rainbow_r', hiL=0.7, name='15.')

cmaps = [ str(i)+'.' for i in range(1,16)]