I ran a algorithm that gave me rgb images, representing the estimated depth of the pixels of the imgs. Since it's representing a value, i wanted to convert it to greyscale. However, the rgb palette isnt linear with the level of grey.
I tried to use the image.convert
line from the PIL library with the palette
argument,as follows, but it didn't work.
At first i tried
import os
from PIL import Image
directory = "folder/path"
for filename in os.listdir(directory):
if filename.endswith(".jpg") or filename.endswith(".png"):
file_path = os.path.join(directory, filename)
image = Image.open(file_path)
image_gray = image.convert('L')
image_gray.save(file_path)
but that gave me a regular rgb to grayscale conversion, so i tried to use the custom scale like this :
import os
from PIL import Image
directory = "folder/path"
custom_scale_path = "custom-scale/path"
custom_scale = Image.open(custom_scale_path).convert('L')
for filename in os.listdir(directory):
if filename.endswith(".jpg") or filename.endswith(".png"):
file_path = os.path.join(directory, filename)
image = Image.open(file_path)
image_gray = image.convert('P', palette=custom_scale)
image_gray.save(file_path)
Any suggestion ?
You can solve this by building a lookup table from the given color scale and then finding the closest point for every pixel to it. Assumption would be that the image does not contain any RGB that has no representation in the given scale.
Here is how I did it:
First, read the image and the scale and transform it to arrays.
from pylab import * # I am sorry for this...
from PIL import Image
# read files
filename = "DVKmP.png" # file
filename_sc = "cdHiq.png" # file with scale
im = Image.open(filename)
im_sc = Image.open(filename_sc)
# convert image to pixels -> I am sure there are other ways...
pixels = list(im.getdata())
width, height = im.size
pixels = np.array([pixels[i * width:(i + 1) * width] for i in range(height)])
pixels_sc = list(im_sc.getdata())
width_sc, height_sc = im_sc.size
pixels_sc = np.array([pixels_sc[i * width_sc:(i + 1) * width_sc] for i in range(height_sc)])
Next, construct the lookup table (lut) from your scale.
# create a lookup table for the RGB to height values
lut_c = mean(pixels_sc[:,:-1], axis=0) # colors -> the last column of your color scale was zero, so I skipped it
lut_h = linspace(0, 1, len(lut_c)) # heights -> insert your actual values here
# test plot
figure()
plot(lut_h, lut_c[:,0], color='r')
plot(lut_h, lut_c[:,1], color='g')
plot(lut_h, lut_c[:,2], color='b')
xlabel('x - insert your scale here')
ylabel('RGB values')
title('Your lookup table for conversion')
At this point, you need to put in actual numbers for the height (or depth) map, that is connected to your scale. I simply used 0 and 1 here. What we can see is that above 0.6, you will not be able to discriminate values anymore with the given color scale, since all RGB values are constant.
Now comes the conversion function. It calculates the minimum distance of any given RGB tuple to the values in the lookup table and returns the corresponding value.
# function to map RGB to grayscale: it uses the closest point to your color scale
mapRGBgrayscale = vectorize(
lambda r,g,b: lut_h[argmin((r - lut_c[:,0])**2
+ (g - lut_c[:,1])**2
+ (b - lut_c[:,2])**2)])
Now apply it to your data file. This takes a while in python for larger pictures, so you may have to put in some additional work to make it faster.
# this is your conversion
h_map = mapRGBgrayscale(pixels[:,:,0], pixels[:,:,1], pixels[:,:,2])
And plot or use elsewhere...
# ... and the result
figure(dpi=150)
imshow(h_map)
colorbar(label='height - put in your scale here')
(I did not use the grayscale color map for the plot, since there is this one area, far away, which would visualy dominate the image.)