Digitizing a color map

Consider the following image: Source: https://fr.mathworks.com/matlabcentral/mlc-downloads/downloads/submissions/34863/version/15/screenshot.jpg

I would like to print it as a grayscale image. I can do the conversion with scikit-image

:

from skimage.io import imread
from matplotlib import pyplot as plt
from skimage.color import rgb2gray


img = imread('image.jpg')

plt.grid(which = 'both')
plt.imshow(rgb2gray(img), cmap=plt.cm.gray)

      

I get:

enter image description here

which is clearly not what I want .

My question is: Is there a way with scikit-image

or with raw numpy

and / or mathplotlib

for digitizing the image to get a 3D array (first dimension: X index, second dimension: Y index, third dimension: value according to color palette). Then I can easily change to colormap to what turns out to have the best results when printing in grayscale?

+3


source to share


2 answers


The example below shows an easy way to reverse colormap mapping value -> RGB

.

def unmap_nearest(img, rgb):
    """ img is an image of shape [n, m, 3], and rgb is a colormap of shape [k, 3]. """
    d = np.sum(np.abs(img[np.newaxis, ...] - rgb[:, np.newaxis, np.newaxis, :]), axis=-1)    
    i = np.argmin(d, axis=0)
    return i / (rgb.shape[0] - 1)

      

This function works by taking the RGB value of each pixel and looking at the index of the best matching color in the color palette. Some trickery with indexing and broadcasting allows for efficient vectorization (at the expense of memory wasted on temporary arrays):

  • img[np.newaxis, ...]

    converts an image from the form [n, m, 3] to [1, n, m, 3]

  • rgb[:, np.newaxis, np.newaxis, :]

    converts the color palette from the form [k, 3] to [k, 1, 1, 3].

  • subtracting the resulting arrays results in an array of the form [k, n, m, 3], which corresponds to the difference between each color scheme index k

    and a pixel n, m

    for each color component.
  • sum(abs(..), axis=-1)

    takes the absolute value of the differences and sums over all color components (last dimension) to get the total difference between all pixels and color map entries (array of the form [k, n, m]).
  • i = np.argmin(d, axis=0)

    finds the index of the smallest element by first dimension. The result is the index of the best consistent color map entry for each pixel [n, m].
  • return i / (rgb.shape[0] - 1)

    finally returns the indices normalized to the size of the color map so that the result is in the range 0-1.

enter image description here



There are such false caveats with this approach:

  • It cannot restore the original range of values.
  • It will treat all pixels as part of the color map (i.e. the outline of the continents will also be displayed).
  • If you use the wrong color map, it will fail.

...

import numpy as np
import matplotlib.pyplot as plt
from skimage.color import rgb2gray


def unmap_nearest(img, rgb):
    """ img is an image of shape [n, m, 3], and rgb is a colormap of shape [k, 3]. """
    d = np.sum(np.abs(img[np.newaxis, ...] - rgb[:, np.newaxis, np.newaxis, :]), axis=-1)    
    i = np.argmin(d, axis=0)
    return i / (rgb.shape[0] - 1)


cmap = plt.cm.jet
rgb = cmap(np.linspace(0, 1, cmap.N))[:, :3]


original = (np.arange(10)[:, None] + np.arange(10)[None, :])

plt.subplot(2, 2, 1)
plt.imshow(original, cmap='gray')
plt.colorbar()
plt.title('original')


plt.subplot(2, 2, 2)
rgb_img = cmap(original / 18)[..., :-1]
plt.imshow(rgb_img)
plt.title('color-mapped')

plt.subplot(2, 2, 3)
wrong = rgb2gray(rgb_img)
plt.imshow(wrong, cmap='gray')
plt.title('rgb2gray')

plt.subplot(2, 2, 4)
reconstructed = unmap_nearest(rgb_img, rgb)
plt.imshow(reconstructed, cmap='gray')
plt.colorbar()
plt.title('reconstructed')

plt.show()

      

+3


source


Building on @ kazemakmakase's answer, if you are digitizing a figure, you are probably dealing with a copy of the original that has been converted, or perhaps even printed and scanned at some point. These things can distort colors from the "true" color map that was originally used.

You can handle this by using a slice through the colorbar shape as a "template" (rgb) for the matching. Specifically, crop the shape to just the color ramp (in landscape orientation in this example), then replace the variable rgb

in @kazemakmakase's example with:



cmapimg = plt.imread('cropped_colorbar.png')
rgb = cmapimg[cmapimg.shape[0]/2,:,:3]

      

0


source







All Articles