Search code examples
pythonmatplotlibcolormapcolor-depth

generate 16-bit color map in matplotlib


I would like to colorize a 16-bit depth image with a RGB colormap in matplotlib. Technically, 3 channels with 8 bit per channel should be enough to have distinct rgb values for all 2^16 possible depth values.

A standard color map 'viridis' does result in <1000 distinct values even though the original depth image had more than twice of that.

I tried creating the colormap with a larger number of samples plt.get_cmap('viridis', 2**16), but it is still not enough.

Some code to describe what I am trying to do:

def depth_to_rgb(path):   
    depth_map = Image.open(path)
    pixel = np.array(depth_map)
    pixel = (pixel - np.min(pixel)) / np.ptp(pixel)
    cm = plt.get_cmap('viridis', 2**16)
    pixel_colored = np.uint8(np.rint(cm(pixel) * 255))[:, :, :3]
    return Image.fromarray(pixel_colored)

I can slightly increase the number of distinct values in the map by creating a custom cm, but it is still not enough:

cm = mlp.colors.LinearSegmentedColormap.from_list("",
["red", "green", "yellow"], N=2**16)

Is there a colormap with enough values or how could I create one? Solutions related to the Pillow Image Library are also appreciated.

EDIT

Apparently (thanks to ImportanceOfBeingErnest) the resulting colormap has indeed 2**16 values, but np.uint8(np.rint(cm(pixel) * 255)) caused some of them to fall on the same colors. I only printed the number of distinct colors in the resulting image. I guess I have to do a somehow different mapping, but the original question is answered.


Solution

  • Viridis has 256 colors. It is a ListedColormap, which means when resampling it, it will still give you a maximum of the initial number of colors. So plt.get_cmap('viridis', 2**16) will still get you the 256 initial colors.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.colors as mcolors
    
    cmap = plt.get_cmap("viridis", 2**16)
    a = cmap(np.linspace(0,1,2**16))
    print(len(a))
    print(len(np.unique(a, axis=0)))
    

    prints

    65536
    256
    

    But LinearSegmentedColormap.from_list with a larger N should work.

    cmap = mcolors.LinearSegmentedColormap.from_list("", ["red", "green", "yellow"], N=2**16)
    a = cmap(np.linspace(0,1,2**16))
    print(len(a))
    print(len(np.unique(a, axis=0)))
    

    prints

    65536
    65536
    

    If you wanted a viridis colormap with 2^16 entries, you can still interpolate in between the existing 256 colors,

    cmap = mcolors.LinearSegmentedColormap.from_list("", plt.cm.viridis.colors, N=2**16)
    

    this would result in 65536 colors as above.