Overview of unmap#

This library aims to do 2 things:

  1. Guess the colourmap from a pseudocolour image, for those occasions when the colourmap is not known and not published along with the image.

  2. Recover (‘unmap’) the data from a pseudocolour image, essentially performing the reverse of the pseudocolour process.

These 2 distinct tasks are illustrated below.

from gio import generate_random_surface
import matplotlib.pyplot as plt
import matplotlib.cm as cm

# Make a random surface and normalize it to the range [0, 1].
data = generate_random_surface(size=(32, 32), random_seed=42).values
data = (data - data.min()) / (data.max() - data.min())

# Take a look at it.
plt.imshow(data, cmap='Greys_r')
<matplotlib.image.AxesImage at 0x7f7f6e8640d0>
../_images/e479271a8e40e1867f8f3df84e43803f5a1fd78a4ac9a8788e5ecded1e581b6c.png

Note: this is a greyscale image; it’s the closest thing we can make to a ‘pure’ visualization of the data. The values in the data array correspond to a grey level from 0 (black) to 1 (white).

If we were to look closely at the top-left corner, it’s just a grid of numbers…

fig, ax = plt.subplots()
for i in range(5):
    for j in range(5):
        ax.text(i, j, f'{data[i, j]:0.2f}', ha='center')
ax.imshow(data[:5, :5], vmin=0, vmax=1)
ax.axis('off')
plt.show()
../_images/a4cad92ec869e45c432b4388f259a8acc86a68d326c99b96d29abc9c971ad5c9.png

Now we’ll make an RGB image out of this 2D array. This involves using a look-up table of colours, called a colourmap, to transform each data value into an RGB colour, which is a 3-tuple of floats like (0.25, 0.33, 0.9). In doing so, we lose the data values themselves; they are now encoded as colours.

# Make a pseudocolored RGB image array.
imarray = cm.turbo(data)

# Display the RGB image.
plt.imshow(imarray)
<matplotlib.image.AxesImage at 0x7f7f6e6e9ba0>
../_images/2d57545893707f8b8a852052196e31f4b6ad25a0c1a2ab13b41df83cd1a43573.png

If we know the colourmap and the original range of the data, then recovering data from this image is not too hard: we can do a reverse look-up of each colour in the image and replace it with the corresponding data value. This library, unmap, can handle this.

On the other hand, without knowledge of the colourmap, or the range of values in the original array, it is hard to recover the original dataset from this image. But in some cases, unmap can help with this too.

Guess the colourmap#

We know that we used the jet colormap to create this image, but that information is not contained in the image. It’s just a 3D NumPy array.

Let’s see if unmap.guess_cmap_from_array() can guess the colourmap we used:

import unmap

cmap = unmap.guess_cmap_from_array(imarray)
cmap
recovered
recovered colormap
under
bad
over

Phew! It looks good. Now we can give this to the unmapping function.

Recover the data#

Use unmap.unmap() to try recovering the data from an image array using the colourmap we guessed:

rec = unmap.unmap(imarray, cmap)
rec
array([[0.65882353, 0.62352941, 0.57647059, ..., 0.85490196, 0.81960784,
        0.74117647],
       [0.63137255, 0.59607843, 0.5254902 , ..., 0.88627451, 0.86666667,
        0.80784314],
       [0.62352941, 0.59215686, 0.51372549, ..., 0.90196078, 0.89019608,
        0.83921569],
       ...,
       [0.69411765, 0.60392157, 0.54117647, ..., 0.56470588, 0.59215686,
        0.64313725],
       [0.69019608, 0.60392157, 0.55294118, ..., 0.5254902 , 0.57254902,
        0.61568627],
       [0.6745098 , 0.59607843, 0.5372549 , ..., 0.49411765, 0.55294118,
        0.59607843]])

Let’s have a look at this data:

plt.imshow(rec)
<matplotlib.image.AxesImage at 0x7f7f6e58a2c0>
../_images/0e1b5bb52b8bcdb27e8463460e46cc18a0165b14c99c09ff7d7be22ac2a10423.png

Or we can plot it with the colourmap we recovered:

plt.imshow(rec, cmap=cmap)
<matplotlib.image.AxesImage at 0x7f7f6e45c6a0>
../_images/375d3995e066a728ee7ec84c552b5ec47b549104621c56a4a49f7a1ebed66fd8.png

Or, in this case, we can use the colourmap we know was applied originally to create the image:

plt.imshow(rec, cmap='turbo')
<matplotlib.image.AxesImage at 0x7f7f6e4bea70>
../_images/4892c86e1e05ed26d19ffbaaac55684bdf8f40699ed1acaf1c5b17b39792c769.png

These results look very close.

We can compute the difference:

plt.imshow(data - rec, cmap='RdBu', vmin=-0.1, vmax=0.1)
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7f7f6e363850>
../_images/273427d4ded3c773f89724920d42083ea6e0681f8ba5241e604806be382446a2.png

The absolute error seems to go up to a little above 8% in some areas. Each colourmap has its own error profile, but this order of error magnitude is typical.

All in one#

Sometimes we can actually skip the colormap guessing step and let unmap.unmap try to do it. Let’s try it:

rec = unmap.unmap(imarray)

plt.imshow(rec)
<matplotlib.image.AxesImage at 0x7f7f6e21f700>
../_images/4dc0bc01fcbf095ed09b2955dc2b854b21b5ccceb0a2cd880a442c3fe8ffcd04.png

Nice. If it works.