Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

2D colormap in Python

I have a 2d vector (x,y) and I need to find a 2D colormap that maps these coordinates to a smooth colormap. The color code will depend only on the (x,y) value. For instance,

  • white around the center,
  • more red as we go to the northeast,
  • more blue as we go the southeast,
  • more green as we go to the northwest,
  • and more green as we go to the southwest.

In other words:

green         red
       white
green         blue

I didn't find anything in matplotlib correspond to my need. I thought about converting the coordinates into magnitude and phase but still the problem be the same. I also thought about adding a dummy dimension to the vector (x,y) to make it a 3d dimension and then normalize the resulted 3D vector. Then, feed it to cmap parameter in the matplotlib plots. However, this would produce a non-smooth colors. Any leads?

A sample of the color map:

sample color map

like image 901
rando Avatar asked Jan 30 '26 06:01

rando


2 Answers

The way to do this is to format your colormap as a 3D array with RGB values i.e. an array cmap of shape (256,256,3). You fill this array with color values to make up the colormap. You then adjust your two variables (x,y) to the range 0→255 and convert them to dtype = int, (x_int, y_int).

You then sample your cmap based on (x_int, y_int)

image = cmap[y_int,x_int]

You can then visualize both the colormap and image:

fig, axes = plt.subplots(1,2)
axes[0].imshow(image)
axes[0].imshow(cmap)

if you do not wish to do this manually you can check out the package at https://colorstamps.readthedocs.io/en/latest/ (pip install colorstamps)

import matplotlib.pyplot as plt
import colorstamps
# img = (100,200,2) example data
img = colorstamps.helpers.get_random_data()
# map data to colormap
rgb, stamp = colorstamps.apply_stamp(img[:,:,0], img[:,:,1], 'peak',
                                   vmin_0 = -1.2, vmax_0 = 1.2,
                                   vmin_1 = -1, vmax_1 = 1,
                                 )
fig, axes = plt.subplots(1,2,figsize=(10,3), dpi = 100)
axes[0].imshow(rgb)
# show colormap as overlay
overlaid_ax = stamp.overlay_ax(axes[0],
                lower_left_corner = [0.7,0.85], width = 0.2)
overlaid_ax.set_ylabel(r'$\phi$')
overlaid_ax.set_xlabel(r'$\omega$')
# also show colormap as in separate ax to illustrate functionality
stamp.show_in_ax(axes[1])
axes[1].set_ylabel(r'$\phi$')
axes[1].set_xlabel(r'$\omega$')

enter image description here

This is a package I built because I had the same requirements as the question asker.

like image 169
trygvrad Avatar answered Feb 01 '26 20:02

trygvrad


So to restate the problem to ensure that I understand it correctly: you would like to have two different colormap channels, not a single one?

I don't see a direct way to do this in matplotlib, there are two options:

  • plot the same data multiple times with different color channels/colormaps/transparencies
  • define your own custom 2D-to-RGB color map and pass an array of RGB triplets to the plotting function

For the "hack-y" multiple plotting solution:

import numpy as np
from matplotlib.colors import hsv_to_rgb, rgb_to_hsv
import matplotlib.pyplot as plt

xydata = np.array([(x,y) for x in np.arange(-1.,1.1,0.1) for y in np.arange(-1.,1.1,0.1)], dtype=float)
x_colorfunc = lambda xy: xy.T[0].max() - np.abs(xy.T[0])
y_colorfunc = lambda xy: np.abs(xy.T[1])
y_colormap_coord = y_colorfunc(xydata)
x_colormap_coord = x_colorfunc(xydata)
x_colormap = "plasma"
y_colormap = "Greys"


plt.figure("2d_colormap_hack")
plt.scatter(xydata.T[0], xydata.T[1], c=x_colormap_coord, cmap= x_colormap, alpha=1.0)
plt.scatter(xydata.T[0], xydata.T[1], c=y_colormap_coord, cmap= y_colormap, alpha=0.6)

Which produces

2-channel hack graph

You can do anything you'd like tor the custom 2D-to-color function, but here are two suggestions:

def xy_color_func(xy):
    # using np.divide handles `RuntimeWarning: divide by zero encountered in true_divide`
    xy_ratio = np.divide(xy.T[1], xy.T[0], out=np.ones_like(xy.T[0]), where=(xy.T[0]!=0) )
    xy_angle_frac = (4/np.pi)*np.abs(np.arctan(xy_ratio))
    xy_mag = np.linalg.norm(xy, axis=-1)
    hsl_hue = 1 - 1./6*xy_angle_frac   # hue goes from red to blue 
    hsl_sat = 1 - xy_mag/xy_mag.max()  # 0 is full color saturation, 1 is equal RGB values
    hsl_luminance = 0.75 - 0.25*(xy_mag/xy_mag.max())  # brighter at the "target" point of (0, 0)
    hsv = hsl_to_hsv(hsl_hue, hsl_sat, hsl_luminance)
    rgb = hsv_to_rgb(hsv)
    return rgb

def hsl_to_hsv(hsl_hue, hsl_sat, hsl_luminance):
    hsv_hue = hsl_hue
    hsv_v = hsl_luminance + hsl_sat*np.minimum(hsl_luminance, 1-hsl_luminance)
    hsv_sat = 2*(1-np.divide(hsl_luminance, hsv_v, out=np.ones_like(hsv_v), where=(hsv_v!=0) ))
    hsv = np.vstack((hsv_hue, hsv_sat, hsv_v)).T
    return hsv

xy_colors = xy_color_func(xydata)
plt.figure("2d_colormap_func")
plt.scatter(xydata.T[0], xydata.T[1], c=xy_colors)

Which produces

2D color function graph

It looks like your desired color map needs a few more rules to convert the XY regions to the desired colors,and gradient/blending function to transition from one region to another, similar to the trapezoidal blending shown in 4. In your desired map above,

  • "green" is x<=0,
  • "red" is x >0 & y < 0,
  • "blue" is x > 0 & y >= 0, and
  • white is 1-magnitude(x,y),

One way to achieve this might be to make a grid of points with the desired color in a graphics program (like Gimp or Inkscape), tweak the key coordinates and specified color triplets (in RGB, HSL, or HSV) until you are pleased with the appearance, then use scipy.interpolate.griddata5 to interpolate each of the 3 color channels for your XY data, like the following:

key_xy_points = np.array([[0,0],[1,0],[1,1],[1,-1],[-1,1], [-1,-1]],dtype=float)
key_xy_RGBs = np.array([[1,1,1], [1,1,1], [0,0,1], [1,0,0], [0,1,0], [0,1,0]],dtype=float)

from scipy.interpolate import griddata

reds = griddata(key_xy_points, key_xy_RGBs.T[0], xydata)
greens = griddata(key_xy_points, key_xy_RGBs.T[1], xydata)
blues = griddata(key_xy_points, key_xy_RGBs.T[2], xydata)

xy_colors_griddata = np.vstack((reds, greens, blues)).T
plt.figure("2d_colormap_griddata")
plt.scatter(xydata.T[0], xydata.T[1], c=xy_colors_griddata)

Which produces

2D color function graph from griddata

Note: As long as I was writing my own colorspace conversion function, I could have converted directly from HSL to RGB 3, but perhaps one of the commenters can explain why matplotlib.colors has hsv_to_rgb but not an hsl_to_rgb (running matplotlib v.3.3.2).

like image 33
eswint Avatar answered Feb 01 '26 20:02

eswint



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!