Tensorflow Deeplab image colormap removal confusion - python

In this following code, I only see, an image is read and written again. But how do the image pixel values get changed so drastically? Apparently, converting the PIL image object to numpy array causes this but don't know why. I have read the doc for PIL images but didn't see any reasonable explanation for this to happen.
import numpy as np
from PIL import Image
def _remove_colormap(filename):
return np.array(Image.open(filename))
def _save_annotation(annotation, filename):
pil_image = Image.fromarray(annotation.astype(dtype=np.uint8))
pil_image.save(filename)
def main():
raw_annotation = _remove_colormap('2007_000032.png')
_save_annotation(raw_annotation, '2007_000032_output.png')
if __name__ == '__main__':
main()
Input image is,
Here is the output,
Note: The value at the red area in the input image is [128,0,0] and in the output image it's [1,1,1].
The actual source of the code is here.
Edit:
As #taras made it clear in his comment,
Basically, palette is a list of 3 * 256 values in form 256 red values,
256 green values and 256 blue values. Your pil_image is an array of
greyscale pixels each taking a single value in 0..255 range. When
using 'P' mode the pixel value k is mapped to a color (pallette[k],
palette[256 + k], palette[2*256 + k]). When using 'L' mode the color
is simply k or (k, k, k) in RGB
The segmentation image annotations use a unique color for each object type. So we don't need the actual color palette for the visualization, we get rid of the unnecessary color palette.

A quick check of the opened image mode with
Image.open(filename).mode
shows the input file is opened with 'P' mode
which stands for
8-bit pixels, mapped to any other mode using a color palette
So, when you generate image with Image.fromarray the palette is simply lost
and you are left with a greyscale image in 'L' mode.
You simply need to provide the palette info when creating the output array.
The palette can be extracted with Image.getpalette():
def _remove_colormap(filename):
img = Image.open(filename)
palette = img.getpalette()
return np.array(img), palette
Once you created your pil_image you can set the palette back with Image.putpalette(palette)
def _save_annotation(annotation, palette, filename):
pil_image = Image.fromarray(annotation.astype(dtype=np.uint8))
pil_image.putpalette(palette)
pil_image.save(filename)
And your main changed accordingly:
def main():
raw_annotation, palette = _remove_colormap('SqSbn.png')
_save_annotation(raw_annotation, palette, '2007_000032_output.png')
Edit:
palette is a list of 3 * 256 values in the following form:
256 red values, 256 green values and 256 blue values.
pil_image is an array of greyscale pixels each taking a single value in 0..255 range. When using 'P' mode the pixel value k is mapped to an RGB color (pallette[k], palette[256 + k], palette[2*256 + k]). When using 'L' mode the color is simply k or (k, k, k) in RGB.

Mode conversion is missing in _remove_colormap(filename). As it's defined in the question (and the answer from #taras), remove_colormap converts a PIL image into a numpy array. _save_annotation() further converts the numpy array into a PIL image. RGB image is saved as such. convert('L') should be used for converting to grayscale. Modified function definition is as under:
def _remove_colormap(filename):
img = Image.open(filename).convert('L')
palette = img.getpalette()
print("palette: ", type(palette))
return np.array(img), palette

Related

Grayscale Heatmap to Color Gradient Heatmap

I am trying to take a set of 256x256px 8-bit grayscale .pngs (with transparency) and convert the grayscale .png to a color .png of the same size, still retaining transparency. The palette I want to use is Zissou1 from the R wesanderson package, which I have gotten into a Python dictionary where each key corresponds to a greyscale value and each value a HEX color.
import os
from PIL import Image, ImageColor
### dic = the dictionary containing the palette in format {grayscale pixel value: "HEX COLOR"},
### created earlier in the script
with Image.open("3.png") as im:
fin = Image.new("RGBA", im.size)
px = im.load()
px1 = fin.load()
for x in range(0,256):
for y in range(0,256):
px1.putpixel(x,y,ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
fin.show()
I am getting the error:
px1.putpixel(x,y,ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
AttributeError: 'PixelAccess' object has no attribute 'putpixel'
To extend on Jason's answer:
The lookup as given by PIL
With Image.point(lookup_table, mode = 'L') you can lookup and transpose the colors of your image.
lookup_table = ...
with Image.open("3.png") as orig:
image = orig.point(lookup_table, mode = 'L')
image.show()
To see an example for using the Image.point method with the lookup_table:
Using the Image.point() method in PIL to manipulate pixel data
Your own implementation (fixed with improved naming)
or implement the lookup against your_dic yourself:
your_dic = ...
with Image.open("3.png") as orig:
image = colored_from_map(orig, your_dic)
image.show()
with this alternative function (you almost did):
def colored_from_map(orig, map_to_color):
image_in = orig.load()
image = Image.new("RGBA", im.size)
image_out = image.load()
for x in range(0,256):
for y in range(0,256):
coords = (x,y)
greyscale = image_in.getpixel(x,y)[1]
color_name = map_to_color[greyscale]
image_out.putpixel(coords, ImageColor.getcolor(color_name,"RGBA"))
return image
Preserving the alpha-channel (transparency)
See the source-code of ImageColor.getColor()
at the begin and end of its method body:
color, alpha = getrgb(color), 255 # default to no-transparency
if len(color) == 4: # if your mapped color has 4th part as alpha-channel
color, alpha = color[0:3], color[3] # then use it
# omitted lines
else:
if mode[-1] == "A": # if the last char of `RGBA` is `A`
return color + (alpha,) # then return with added alpha-channel
return color
(comments mine)
So you could simply set the fourth element of the returned color-tuple to the previous value of the original gray-scale image:
greyscale = image_in.getpixel(x,y)[1] # containing original alpha-channel
color_name = map_to_color[greyscale] # name or hex
mapped_color = ImageColor.getcolor(color_name,"RGB") # removed the A
transposed_color = mapped_color[:2] + (greyscale[3],) # first 3 for RGB + original 4th for alpha-channel (transparency)
image_out.putpixel(coords, transposed_color)
Note: because the A(lpha-channel) is provided from original image, I removed the A from the getColor invocation's last argument. Technically, you can also remove the slicing from mapped_color[:2] to result in mapped_color + (greyscale[3],).
The first parameter to PIL's PixelAccess.putpixel method expects the pixel's coordinates to be passed as a (x,y) tuple:
px1.putpixel((x,y),ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
Alternatively, consider using the Image.point method which takes a look up table similar to the one you already created to map an image based on pixel values. See the answer at Using the Image.point() method in PIL to manipulate pixel data for more details

Get RGB colors from color palette image and apply to binary image

I have a color palette image like this one and a binarized image in a numpy array, for example a square such as this:
img = np.zeros((100,100), dtype=np.bool)
img[25:75,25:75] = 1
(The real images are more complicated of course.)
I would like to do the following:
Extract all RGB colors from the color palette image.
For each color, save a copy of img in that color with a transparent background.
My code so far (see below) can save the img as a black object with transparent background. What I am struggling with is a good way of extracting the RGB colors so I can apply them to the image.
# Create an MxNx4 array (RGBA)
img_rgba = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.bool)
# Fill R, G and B with inverted copies of the image
# Note: This creates a black object; instead of this, I need the colors from the palette.
for c in range(3):
img_rgba[:,:,c] = ~img
# For alpha just use the image again (makes background transparent)
img_rgba[:,:,3] = img
# Save image
imsave('img.png', img_rgba)
You can use a combination of a reshape and np.unique to extract the unique RGB values from your color palette image:
# Load the color palette
from skimage import io
palette = io.imread(os.path.join(os.getcwd(), 'color_palette.png'))
# Use `np.unique` following a reshape to get the RGB values
palette = palette.reshape(palette.shape[0]*palette.shape[1], palette.shape[2])
palette_colors = np.unique(palette, axis=0)
(Note that the axis argument for np.unique was added in numpy version 1.13.0, so you may need to upgrade numpy for this to work.)
Once you have palette_colors, you can pretty much use the code you already have to save the image, except you now add the different RGB values instead of copies of ~img to your img_rgba array.
for p in range(palette_colors.shape[0]):
# Create an MxNx4 array (RGBA)
img_rgba = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8)
# Fill R, G and B with appropriate colors
for c in range(3):
img_rgba[:,:,c] = img.astype(np.uint8) * palette_colors[p,c]
# For alpha just use the image again (makes background transparent)
img_rgba[:,:,3] = img.astype(np.uint8) * 255
# Save image
imsave('img_col'+str(p)+'.png', img_rgba)
(Note that you need to use np.uint8 as datatype for your image, since binary images obviously cannot represent different colors.)

how to get all RGB values from a pixel in Python? [duplicate]

Is it possible to get the RGB color of a pixel using PIL?
I'm using this code:
im = Image.open("image.gif")
pix = im.load()
print(pix[1,1])
However, it only outputs a number (e.g. 0 or 1) and not three numbers (e.g. 60,60,60 for R,G,B). I guess I'm not understanding something about the function. I'd love some explanation.
Thanks a lot.
Yes, this way:
im = Image.open('image.gif')
rgb_im = im.convert('RGB')
r, g, b = rgb_im.getpixel((1, 1))
print(r, g, b)
(65, 100, 137)
The reason you were getting a single value before with pix[1, 1] is because GIF pixels refer to one of the 256 values in the GIF color palette.
See also this SO post: Python and PIL pixel values different for GIF and JPEG and this PIL Reference page contains more information on the convert() function.
By the way, your code would work just fine for .jpg images.
With numpy :
im = Image.open('image.gif')
im_matrix = np.array(im)
print(im_matrix[0][0])
Give RGB vector of the pixel in position (0,0)
GIFs store colors as one of x number of possible colors in a palette. Read about the gif limited color palette. So PIL is giving you the palette index, rather than the color information of that palette color.
Edit: Removed link to a blog post solution that had a typo. Other answers do the same thing without the typo.
An alternative to converting the image is to create an RGB index from the palette.
from PIL import Image
def chunk(seq, size, groupByList=True):
"""Returns list of lists/tuples broken up by size input"""
func = tuple
if groupByList:
func = list
return [func(seq[i:i + size]) for i in range(0, len(seq), size)]
def getPaletteInRgb(img):
"""
Returns list of RGB tuples found in the image palette
:type img: Image.Image
:rtype: list[tuple]
"""
assert img.mode == 'P', "image should be palette mode"
pal = img.getpalette()
colors = chunk(pal, 3, False)
return colors
# Usage
im = Image.open("image.gif")
pal = getPalletteInRgb(im)
Not PIL, but imageio.imread might still be interesting:
import imageio
im = scipy.misc.imread('um_000000.png', flatten=False, mode='RGB')
im = imageio.imread('Figure_1.png', pilmode='RGB')
print(im.shape)
gives
(480, 640, 3)
so it is (height, width, channels). So the pixel at position (x, y) is
color = tuple(im[y][x])
r, g, b = color
Outdated
scipy.misc.imread is deprecated in SciPy 1.0.0 (thanks for the reminder, fbahr!)

PIL import png pixels as single value instead of 3 values vector

I have a bunch of map files I've downloaded from Google maps in png formats that I want to convert to a single larger image. When I import one and I look at the pixels, I see that the pixels are a single number in the range of 0..256 instead of three values list. What's going on here?
I'm using
from PIL import Image
print open('b1.png').load()[0,0]
and I get 153 instead of [r,g,b]
my image file is
The reason of such result (value 153 in [0,0]) is that image mode is set to P (8-bit pixels, mapped to any other mode using a colour palette). If You want to set different mode (e.g. RGB) You can do it before invoking method load().
Here is an example of how to do this:
from PIL import Image
file_data = Image.open('b1.png')
file_data = file_data.convert('RGB') # conversion to RGB
data = file_data.load()
print data[0,0]
and the result of print is
(240, 237, 229)
For more information about Image Modes please visit the documentation.
Your image is in mode=P. It has it's colors defined in a color palette.
>>> Image.open('b1.png')
<PIL.PngImagePlugin.PngImageFile image mode=P size=640x640 at 0x101856B48>
You want a RGB value. First convert to RGB:
>>> im = Image.open('b1.png')
>>> im = im.convert('RGB')
>>> im.getpixel((1,1))
(240, 237, 229)
From the docs: http://pillow.readthedocs.org/en/latest/handbook/concepts.html?highlight=mode
P (8-bit pixels, mapped to any other mode using a color palette)
...
RGB (3x8-bit pixels, true color)

Get pixel's RGB using PIL

Is it possible to get the RGB color of a pixel using PIL?
I'm using this code:
im = Image.open("image.gif")
pix = im.load()
print(pix[1,1])
However, it only outputs a number (e.g. 0 or 1) and not three numbers (e.g. 60,60,60 for R,G,B). I guess I'm not understanding something about the function. I'd love some explanation.
Thanks a lot.
Yes, this way:
im = Image.open('image.gif')
rgb_im = im.convert('RGB')
r, g, b = rgb_im.getpixel((1, 1))
print(r, g, b)
(65, 100, 137)
The reason you were getting a single value before with pix[1, 1] is because GIF pixels refer to one of the 256 values in the GIF color palette.
See also this SO post: Python and PIL pixel values different for GIF and JPEG and this PIL Reference page contains more information on the convert() function.
By the way, your code would work just fine for .jpg images.
With numpy :
im = Image.open('image.gif')
im_matrix = np.array(im)
print(im_matrix[0][0])
Give RGB vector of the pixel in position (0,0)
GIFs store colors as one of x number of possible colors in a palette. Read about the gif limited color palette. So PIL is giving you the palette index, rather than the color information of that palette color.
Edit: Removed link to a blog post solution that had a typo. Other answers do the same thing without the typo.
An alternative to converting the image is to create an RGB index from the palette.
from PIL import Image
def chunk(seq, size, groupByList=True):
"""Returns list of lists/tuples broken up by size input"""
func = tuple
if groupByList:
func = list
return [func(seq[i:i + size]) for i in range(0, len(seq), size)]
def getPaletteInRgb(img):
"""
Returns list of RGB tuples found in the image palette
:type img: Image.Image
:rtype: list[tuple]
"""
assert img.mode == 'P', "image should be palette mode"
pal = img.getpalette()
colors = chunk(pal, 3, False)
return colors
# Usage
im = Image.open("image.gif")
pal = getPalletteInRgb(im)
Not PIL, but imageio.imread might still be interesting:
import imageio
im = scipy.misc.imread('um_000000.png', flatten=False, mode='RGB')
im = imageio.imread('Figure_1.png', pilmode='RGB')
print(im.shape)
gives
(480, 640, 3)
so it is (height, width, channels). So the pixel at position (x, y) is
color = tuple(im[y][x])
r, g, b = color
Outdated
scipy.misc.imread is deprecated in SciPy 1.0.0 (thanks for the reminder, fbahr!)

Categories