create multi-colored thresholded image from grayscale heatmap in python

Viewed 836

I have a gray-scale images and want to threshold them in 4 range of values (0.035, 0.7, 0.75), displayed by 4 different colors. I need the result saved as images in UINT8 format. The gray-scale image information is as follows:

print(type(grads))
print(grads.shape)
print(grads.min())
print(grads.max())
cv2.imshow('1_grads', grads)
cv2.waitKey()

### OUTPUT
<class 'numpy.ndarray'>
(512, 512)
0.0
1.0

enter image description here

I have tried the following:

thresh_map = Image.new('RGB', grads.shape, color='white')
thresh_map = np.where(grads < 0.035, (0, 0, 0), (0, 0, 0))
thresh_map = np.where(0.035 < grads < 0.7, (0, 0, 255), (0, 0, 0))
thresh_map = np.where(0.7 < grads < 0.75, (0, 255, 0), (0, 0, 0))
thresh_map = np.where(0.75 < grads, (0, 255, 0), (0, 0, 0))

This returns this Error: ValueError: operands could not be broadcast together with shapes (512,512) (3,) (3,)

I have somehow solved the problem by using for loops and pasting the pixel values one by one. But it is not nice and takes forever, considering the fact that I am going to apply this to ~4000 images.

thresh_map = Image.new('RGB', grads.shape, color='white')
blac = Image.new('RGB', (1, 1), color='black')
blue = Image.new('RGB', (1, 1), color='blue')
redd = Image.new('RGB', (1, 1), color='red')
gree = Image.new('RGB', (1, 1), color='green')
for i in range(grads.shape[0]):
    for j in range(grads.shape[1]):
        print(i, j)
        if grads[i, j] < 0.035:
            thresh_map.paste(blac, (i, j))

        elif .035 < grads[i, j] < 0.7:
            thresh_map.paste(redd, (i, j))

        elif 0.7 < grads[i, j] < 0.75:
            thresh_map.paste(gree, (i, j))

        elif 0.75 < grads[i, j]:
            thresh_map.paste(blue, (i, j))

np_thresh_map = np.asarray(thresh_map)
cv2.imshow('1_thresh', np_thresh_map)
cv2.waitKey()

enter image description here

Is there a more sophisticated and efficient way of doing this?

2 Answers

Here's my solution using NumPy's boolean array indexing. The code should be straightforward. If not, please ask. I'll then provide some more explanations.

import cv2
import numpy as np

# Set up some random grayscale image; and sort for better visualization
image = np.sort(np.random.rand(300, 400))

# Given thresholds
thresholds = [0.035, 0.7, 0.75]

# Expand threshold with boundaries
thresholds = np.concatenate(([0], thresholds, [1]))

# Initialize output, and map colors
map = np.zeros((image.shape[0], image.shape[1], 3), np.uint8)
colors = np.array([[255, 0, 0], [128, 128, 0], [0, 0, 255], [0, 255, 0]])

# Iterate ranges; find proper pixel indices; set proper color in map at these indices
for i in range(len(thresholds)-1):
    idx = (thresholds[i] <= image) & (image < thresholds[i+1])
    map[idx, :] = colors[i, :]

# Show image and map
cv2.imshow('image', image)
cv2.imshow('map', map)
cv2.waitKey(0)
cv2.destroyAllWindows()

The image might look like this:

Image

And, the corresponding map looks like this:

Map

Hope that helps!

You can use matplotlib to create a discrete colormap using a BoundaryNorm as normalizer:

import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt

#generate some data
dx, dy = 0.002, 0.002
y, x = np.mgrid[slice(0, 1 + dy, dy),
                slice(0, 1 + dx, dx)]
z = np.sin(x)**1.5 + np.cos(10 + y*x) * np.cos(x)
z = z[:-1, :-1]/z.max()

#define colormap
cmap = matplotlib.colors.ListedColormap(["red", "blue", "green", "black"])
bounds = np.array([0,0.035,0.7,0.75,1])
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

#Plot
fig, axs = plt.subplots(figsize=(10, 10),nrows=2,ncols=1,gridspec_kw={'height_ratios': [20,1.5]})
pcm=axs[0].pcolormesh(x,y,z,cmap=cmap,norm=norm)
#colorbar
cbar=fig.colorbar(pcm,cax=axs[1], extend='both', orientation='horizontal')

this is the result: enter image description here

Edit To save your data you can use matplotlib savefig function. If you don't want to display the colorbar you can remove the last line from the script above. You can also remove the ticks and their labels using:

axs[0].get_xaxis().set_visible(False)
axs[0].get_yaxis().set_visible(False)
Related