2

I'm looking for the most efficient way to perform the following task.

I have a numpy array with integer values and I have a color map which is a dictionary mapping integers to rgb colors.

What I need is to create for each width by heigth numpy array a width by height by 3 numpy array that can be interpreted as a color image.

For example

import numpy as np

colorMap = {1: [22,244,23], 2: [220,244,23], 3: [22,244,230]}

x = np.array([[1,2,2],[2,2,3],[3,1,2] ])

#I need a very efficient function to create a color image from these two components
image = f(x, colorMap)

My current approach is as follows

import numpy as np

colorMap = {1: [22,244,23], 2: [220,244,23], 3: [22,244,230]}

x = np.array([[1,2,2],[2,2,3],[3,1,2] ])


def f(x):
    return colorMap[x]

x = x.flatten()

image = np.reshape(np.array(list(map(f, x))) , (3,3,3))

But when I time this it is rather slow when compared to numpy inbuilt functions. I'm wondering if anyone knows a way to do this using numpy built in functions that would speed up the procedure.

The above is a dummy example, in reality I need to map large rasters to a visualization in real time. The problem is that the colorMap can be rather long (length between 1 and 100) so that looping over the color map is not a really good option. (If I could loop over the colorMap I would see how to do this with numpy built in functions)

2 Answers 2

2

Since the keys of the dictionary are small positive integers, you can convert the colorMap from the dictionary to a numpy array, and then directly use the index:

>>> color_map = [[0] * 3]
>>> color_map += colorMap.values()
>>> color_map = np.array(color_map)
>>> color_map
array([[  0,   0,   0],
       [ 22, 244,  23],
       [220, 244,  23],
       [ 22, 244, 230]])
>>> color_map[x]
array([[[ 22, 244,  23],
        [220, 244,  23],
        [220, 244,  23]],

       [[220, 244,  23],
        [220, 244,  23],
        [ 22, 244, 230]],

       [[ 22, 244, 230],
        [ 22, 244,  23],
        [220, 244,  23]]])
Sign up to request clarification or add additional context in comments.

4 Comments

I can indeed assume that the colorMap maps integers between 0 and 100 to a color. So your constructed array we be at most 3 times 100 values, which is not that much.
@DanielvanderMaas You seem to have misunderstood the role of the color_map I constructed. It is an intermediate result, not the final result.
I think I understood. Just pointing out that the intermediate result can indeed be easily calculated. (If there would be values of say 10.000 that needed to get mapped to some color the colorMap array would spin out of control a bit). I by the way tested your idea, it is indeed 50 times faster than the approach in the question for raster of 256 by 256.
@DanielvanderMaas Well, it seems that I misunderstood your words. Out of control is predictable, so I wrote the preconditions in the first sentence of the answer :-)
0

You can apply the mapping with a simple for loop:

def int_to_rgb_for(arr, mapping):
    res = arr.copy()[..., None].repeat(3, axis=-1)
    for old_value, new_value in mapping.items():
        res[arr == old_value] = new_value
    return res

The other answer is faster, but it adds the constraint that all integers values between 0 and the maximum value of the array are mapped, even if not used.

We can adapt the other answer to lift that constraint as follows (but needs a default value):

def int_to_rgb_idx(arr, mapping, default=(0, 0, 0)):
    mapping = {k: mapping.get(k, default) for k in range(int(arr.max()) + 1)}
    colormap = np.array([*mapping.values()])
    return colormap[arr]

So let's see the usage with an array having only 1% of the integers values in the range [0, 1_000]:

orig_values = np.random.choice(np.arange(1_000), size=10)
mapping = {orig: np.random.choice(np.arange(256), size=3) for orig in orig_values}

arr = np.random.choice(orig_values, size=(512, 512))

res_for = int_to_rgb_for(arr, mapping)
res_idx = int_to_rgb_idx(arr, mapping)

assert np.all(res_for == res_idx)

Both methods return the same, which one is faster?:

%timeit int_to_rgb_for(arr, mapping)
%timeit int_to_rgb_idx(arr, mapping)
# 13 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# 3.18 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Solution by Mechanic Pig is 4x faster.


import matplotlib.pyplot as plt

arr = np.random.choice(orig_values, size=(32, 32))

res_for = int_to_rgb_for(arr, mapping)
res_idx = int_to_rgb_idx(arr, mapping)

fig, (ax_orig, ax_for, ax_idx) = plt.subplots(ncols=3)

ax_orig.set_title("Original array")
ax_orig.imshow(arr, cmap="gray")

ax_for.set_title("RGB mapped (for)")
ax_for.imshow(res_for)

ax_idx.set_title("RGB mapped (idx)")
ax_idx.imshow(res_idx)

plt.show()

RGB mapping result with imshow

3 Comments

I think with a large mapping. (say 100 items) this procedure is quite slow compared to the solution of Mechanic Pig
Yes Mechanic Pig's solution is clever and much faster, however it adds the constraint that all integers values between 0 and the maximum value of the array are mapped, even if not used. So let's say you only have 10 possible values in the range [0, 10_000], you still have to define a mapping for all 10_000 values.
I edited my answer to lift the constraint of the other answer, which remains faster.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.