Grayscale Heatmap to Color Gradient Heatmap - python

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

Related

Unique Color Detection and Storing images dynamically

If an image is given , find out the unique colors in that image and write output images corresponding to each unique color.
In that all other pixels which don't have that unique color should me marked white.
for eg , if an image has 3 colors - in the output folder there should be three images where each color is separated. Using Open CV & Python.
I've created the unique color list using my methods. What I want is to give a count of all those unique colors in the sample.png image and give the corresponding images output as per the question.
I believe the code below (with comments) should help you with this!
Feel free to follow up if any of the code is unclear!
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from copy import deepcopy
# Load image and convert it from BGR (opencv default) to RGB
fpath = "dog.png" # TODO: replace with your path
IMG = cv.cvtColor(cv.imread(fpath), cv.COLOR_BGR2RGB)
# Get dimensions and reshape into (H * W, C) vector - i.e. a long vector, where each element is a tuple corresponding to a color!
H, W, C = IMG.shape
IMG_FLATTENED = np.vstack([IMG[:, w, :] for w in range(W)])
# Get unique colors using np.unique function, and their counts
colors, counts = np.unique(IMG_FLATTENED, axis=0, return_counts = True)
# Jointly loop through colors and counts
for color, count in zip(colors, counts):
print("COLOR: {}, COUNT: {}".format(color, count))
# Create placeholder image and mark all pixels as white
SINGLE_COLOR = (255 * np.ones(IMG.shape)).astype(np.uint8) # Make sure casted to uint8
# Compute binary mask of pixel locations where color is, and set color in new image
color_idx = np.all(IMG[..., :] == color, axis=-1)
SINGLE_COLOR[color_idx, :] = color
# Write file to output with color and counts specified
cv.imwrite("color={}_count={}.png".format(color, count), SINGLE_COLOR)
Ack, he beat me to it. Well, here's what I've got.
Oh no, I don't think the line
blank[img == color] = img[img == color]
behaves how I think it does. I think it just coincidentally works for this case. I'll edit the code with a solution I'm more confident works for all cases.
Original Image
import cv2
import numpy as np
# load image
img = cv2.imread("circles.png");
# get uniques
unique_colors, counts = np.unique(img.reshape(-1, img.shape[-1]), axis=0, return_counts=True);
# split off each color
splits = [];
for a in range(len(unique_colors)):
# get the color
color = unique_colors[a];
blank = np.zeros_like(img);
mask = cv2.inRange(img, color, color); # edited line 1
blank[mask == 255] = img[mask == color]; # edited line 2
# show
cv2.imshow("Blank", blank);
cv2.waitKey(0);
# save each color with its count
file_str = "";
for b in range(3):
file_str += str(color[b]) + "_";
file_str += str(counts[a]) + ".png";
cv2.imwrite(file_str, blank);

Tensorflow Deeplab image colormap removal confusion

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

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!)

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!)

PIL: Convert RGB image to a specific 8-bit palette?

Using the Python Imaging Library, I can call
img.convert("P", palette=Image.ADAPTIVE)
or
img.convert("P", palette=Image.WEB)
but is there a way to convert to an arbitrary palette?
p = []
for i in range(0, 256):
p.append(i, 0, 0)
img.convert("P", palette=p)
where it'll map each pixel to the closest colour found in the image? Or is this supported for Image.WEB and nothing else?
While looking through the source code of convert() I saw that it references im.quantize.
quantize can take a palette argument. If you provide an Image that has a palette, this function will take that palette and apply it to the image.
Example:
src = Image.open("sourcefilewithpalette.bmp")
new = Image.open("unconvertednew24bit.bmp")
converted = new.quantize(palette=src)
converted.save("converted.bmp")
The other provided answer didn't work for me (it did some really bad double palette conversion or something,) but this solution did.
The ImagePalette module docs's first example shows how to attach a palette to an image, but that image must already be of mode "P" or "L". One can, however, adapt the example to convert a full RGB image to a palette of your choice:
from __future__ import division
import Image
palette = []
levels = 8
stepsize = 256 // levels
for i in range(256):
v = i // stepsize * stepsize
palette.extend((v, v, v))
assert len(palette) == 768
original_path = 'original.jpg'
original = Image.open(original_path)
converted = Image.new('P', original.size)
converted.putpalette(palette)
converted.paste(original, (0, 0))
converted.show()

Categories