The image saved as JPEG with Pillow is different from the original image. - png

I have a 3-d numpy array and save it using Pillow as JPEG image. When I reloaded the image using Pillow, the resulting numpy array is different.
I write a demo code for this:
from PIL import Image
import numpy as np
file_extension = 'jpeg'
# generate a sample image
image = range(1, 2*2*3+1)
image = np.uint8(np.array(image).reshape(2,2,3))
print 'image', image
img = Image.fromarray(image, "RGB")
img.save('test.'+file_extension)
# load image
loaded_image = Image.open('test.'+file_extension)
loaded_image = np.array(loaded_image.convert('RGB'))
print 'loaded image', loaded_image
The output of the code is as follows:
image [[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
loaded image [[[ 3 4 6]
[ 3 4 6]]
[[ 7 8 10]
[ 8 9 11]]]
The loaded_image is different from the original image. However, if I change the file_extension to be 'png' or 'bmp' etc, The loaded_image will be the same as the original image.
I am wondering if anyone has a similar problem and know why saving image in JPEG format using Pillow gives such a problem?

The answer is very simple...
JPEG is "lossy". It discards the least obvious details to save space - see Wikipedia entry for JPEG and scroll down looking for "Quantisation". It also doesn't even get started with 16-bit per sample/channel data.
PNG, BMP and TIFF (other than JPEG-encoded TIFF) are lossless - that means you get back exactly what you saved.
GIF is a bit different as it has a limited palette, so you may get back something different from what you saved depending on how many colours your original image had.
If your data is 16-bit per sample/channel, you should probably use PNG, NetPBM or TIFF because BMP can not store 16-bit per sample data - what they call 24-bit means 3 channels of 8-bits each.

Related

Why are these red and green squares showing up on my PNG?

I have a VTF file that looks like this inside VTFEdit:
I tried to convert it to a PNG in Python using the code below:
import texture2ddecoder, numpy, cv2
from PIL import Image
img_width = 64
img_height = 64
encoded_binary = open('bracketsTest.vtf','rb').read()
#decompressing dxt5 (compression used for this VTF file) to get actual pixel colors, returns BGRA bytes
decoded_binary = texture2ddecoder.decode_bc5(encoded_binary, img_width, img_height)
#creating RGBA png, converting from BGRA (no support for BRGA in PIL it seems)
dec_img = Image.frombytes("RGBA", (img_width, img_height), decoded_binary, 'raw', ("BGRA"))
dec_img.show()
dec_img.save('testpng.png')
And the resulting image came out like this:
As the resulting image does not look the same as it does in VTFEdit, obviously something went wrong. I suspected that it was an issue with the color channel going from BGRA (VTFs are BRGA by default + texture2ddecoder produces BRGA bytes when decompressing) to RGBA, so I tried the following code to convert the image from RGBA to BRGA:
# trying to convert png back to BGRA
image = cv2.imread('testpng.png')
image_bgra = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
cv2.imshow('image',image_bgra)
But the resulting image came out basically the same as before the conversion only with blue squares instead of red ones. What's going on here and how can I fix it? Is there a name for these odd squares?
DXT5 is actually known as Block Compression 3 (BC3). In my case, I incorrectly assumed BC5 = DXT5, so the decompression was wrong (see this wikipedia article for a better explanation). I changed the line decoded_binary = texture2ddecoder.decode_bc5(encoded_binary, img_width, img_height) to decoded_binary = texture2ddecoder.decode_bc3(encoded_binary, img_width, img_height) and the resulting image looked like this:
There are still some odd squares at the top, but deleting/ignoring the header info and low-res thumbnail data seems to fix it:
Know your decompression algorithms!!!

Matlab Read Tif File shows Error "Cannot handle different values per sample for "BitsPerSample"."

I have a tif file that I can view it from Windows Photos App. You can download it from this link.
I tried to load it to Matlab using imread function, however it shows an error below.
TIFF library error - 'TIFFReadDirectory: Cannot handle different values per sample for
"BitsPerSample".'
I then further looking into the file's profile and find that the BitDepth and BitsPerSample value seems not correct. Also, the MaxSample value looks weird.
By checking the Matlab buildin tiff file profile, I learned that for a RGB image, BitDepth should be 24 and BitsPerSample should be [8,8,8]. However, when I tried to explicitly change them, I still get the same error.
fname = 'TifImg.tif';
info = imfinfo(fname);
% Explicitly Assign Correct Value to BitDepth and BitsPerSample (still doesn't work)
for i = 1: length(info)
info(i).BitDepth = 24;
info(i).BitsPerSample = [8 8 8];
end
% Read Tif Image
frame = imread(fname, 1, 'Info', info);
imshow(frame,[])
I hope someone can help me to load this image to Matlab and point me which profile I should change to successfully load the file.
As mentioned in the comments above, the file seemed to be corrupt. I was also unable to open it with gimp or other programs. So here is a hack. You get the size from the header with imfinfo (height by width by 3 colors), then you read the file from the end. A bit of reordering and the image is salvaged. I don't know about the large BitsPerSample you have in the header, if you use [8,8,8] you have the exact length you need. If you use larger color depth etc you don't have enough numbers in the file to fill the 520x696 pix image.
info = imfinfo('TifImg.tif');
fid = fopen('TifImg.tif','r');
s = fread(fid,'uint8=>uint8');
fclose(fid);
len = info.Height*info.Width*3;
data = s(length(s)-len+1:end);
img = reshape(data,3,info.Width,info.Height);
img = permute(img,[3,2,1]);
figure;
imshow(img)

Ground truth pixel labels in PASCAL VOC for semantic segmentation

I'm experimenting with FCN(Fully Convolutional Network), and trying to reproduce the results reported in the original paper (Long et al. CVPR'15).
In that paper the authors reported results on PASCAL VOC dataset. After downloading and untarring the train-val dataset for 2012 (http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
), I noticed there are 2913 png files in the SegmentationClass and same number of files in SegmentationObject subdirectory.
The pixel values in these png files seem to be multiples of 32 (e.g. 0, 128, 192, 224...), which don't fall in the range between 0 and 20. I'm just wondering what's the correspondence between the pixel values and ground truth labels for pixels. Or am I looking at the wrong files?
Just downloaded Pascal VOC. The pixel values in the dataset are as follows:
0: background
[1 .. 20] interval: segmented objects, classes [Aeroplane, ..., Tvmonitor]
255: void category, used for border regions (5px) and to mask difficult objects
You can find more info on the dataset here.
The previous answer by captainist discusses png files saved with color palettes, I think it's not related to the original question. The linked tensorflow code simply loads a png that was saved with color map (palette), then converts it to numpy array (at this step the color palette is lost), then saves the array as a png again. The numerical values are not changed in this process, only the color palette is removed.
I know that this question was asked some time ago. But I raised myself a similar question when trying on PASCAL VOC 2012 with tensorflow deeplab.
If you look at the file_download_and_convert_voc2012.sh, there are lines marked by "# Remove the colormap in the ground truth annotations". This part process the original SegmentationClass files and produce the raw segmented image files, which have each pixel value between 0 : 20. (If you may ask why, check this post: Python: Use PIL to load png file gives strange results)
Pay attention to this magic function:
def _remove_colormap(filename):
"""Removes the color map from the annotation.
Args:
filename: Ground truth annotation filename.
Returns:
Annotation without color map.
"""
return np.array(Image.open(filename))
I have to admit that I do not fully understand the operation by
np.array(Image.open(filename))
I have shown here below a set of images for your referece (from above down: orignal image, segmentation class, and segmentation raw class)
The values mentioned in the original question look like the "color map" values, which could be obtained by getpalette() function from PIL Image module.
For the annotated values of the VOC images, I use the following code snip to check them:
import numpy as np
from PIL import Image
files = [
'SegmentationObject/2007_000129.png',
'SegmentationClass/2007_000129.png',
'SegmentationClassRaw/2007_000129.png', # processed by _remove_colormap()
# in captainst's answer...
]
for f in files:
img = Image.open(f)
annotation = np.array(img)
print('\nfile: {}\nanno: {}\nimg info: {}'.format(
f, set(annotation.flatten()), img))
The three images used in the code are shown below (left to right, respectively):
The corresponding outputs of the code are as follows:
file: SegmentationObject/2007_000129.png
anno: {0, 1, 2, 3, 4, 5, 6, 255}
img info: <PIL.PngImagePlugin.PngImageFile image mode=P size=334x500 at 0x7F59538B35F8>
file: SegmentationClass/2007_000129.png
anno: {0, 2, 15, 255}
img info: <PIL.PngImagePlugin.PngImageFile image mode=P size=334x500 at 0x7F5930DD5780>
file: SegmentationClassRaw/2007_000129.png
anno: {0, 2, 15, 255}
img info: <PIL.PngImagePlugin.PngImageFile image mode=L size=334x500 at 0x7F5930DD52E8>
There are two things I learned from the above output.
First, the annotation values of the images in SegmentationObject folder are assigned by the number of objects. In this case there are 3 people and 3 bicycles, and the annotated values are from 1 to 6. However, for images in SegmentationClass folder, their values are assigned by the class value of the objects. All the people belong to class 15 and all the bicycles are class 2.
Second, as mkisantal has already mentioned, after the np.array() operation, the color palette was removed (I "know" it by observing the results but I still don't understand the mechanism under the hood...). We can confirm this by checking the image mode of the outputs:
Both the SegmentationObject/2007_000129.png and SegmentationClass/2007_000129.png have image mode=P while
SegmentationClassRaw/2007_000129.png has image mode=L. (ref: The modes of PIL Image)

Converting PIL image to VIPS image

I'm working on some large histological images using Vips image library. Together with the image I have an array with coordinates. I want to make a binary mask which masks out the part of the image within the polygon created by the coordinates. I first tried to do this using vips draw function, but this is very inefficiently and takes forever (in my real code the images are about 100000 x 100000px and the array of polygons are very large).
I then tried creating the binary mask using PIL, and this works great. My problem is to convert the PIL image into an vips image. They both have to be vips images to be able to use the multiply-command. I also want to write and read from memory, as I believe this is faster than writing to disk.
In the im_PIL.save(memory_area,'TIFF') command I have to specify and image format, but since I'm creating a new image, I'm not sure what to put here.
The Vips.Image.new_from_memory(..) command returns: TypeError: constructor returned NULL
from gi.overrides import Vips
from PIL import Image, ImageDraw
import io
# Load the image into a Vips-image
im_vips = Vips.Image.new_from_file('images/image.tif')
# Coordinates for my mask
polygon_array = [(368, 116), (247, 174), (329, 222), (475, 129), (368, 116)]
# Making a new PIL image of only 1's
im_PIL = Image.new('L', (im_vips.width, im_vips.height), 1)
# Draw polygon to the PIL image filling the polygon area with 0's
ImageDraw.Draw(im_PIL).polygon(polygon_array, outline=1, fill=0)
# Write the PIL image to memory ??
memory_area = io.BytesIO()
im_PIL.save(memory_area,'TIFF')
memory_area.seek(0)
# Read the PIL image from memory into a Vips-image
im_mask_from_memory = Vips.Image.new_from_memory(memory_area.getvalue(), im_vips.width, im_vips.height, im_vips.bands, im_vips.format)
# Close the memory buffer ?
memory_area.close()
# Apply the mask with the image
im_finished = im_vips.multiply(im_mask_from_memory)
# Save image
im_finished.tiffsave('mask.tif')
You are saving from PIL in TIFF format, but then using the vips new_from_memory constructor, which is expecting a simple C array of pixel values.
The easiest fix is to use new_from_buffer instead, which will load an image in some format, sniffing the format from the string. Change the middle part of your program like this:
# Write the PIL image to memory in TIFF format
memory_area = io.BytesIO()
im_PIL.save(memory_area,'TIFF')
image_str = memory_area.getvalue()
# Read the PIL image from memory into a Vips-image
im_mask_from_memory = Vips.Image.new_from_buffer(image_str, "")
And it should work.
The vips multiply operation on two 8-bit uchar images will make a 16-bit uchar image, which will look very dark, since the numeric range will be 0 - 255. You could either cast it back to uchar again (append .cast("uchar") to the multiply line) before saving, or use 255 instead of 1 for your PIL mask.
You can also move the image from PIL to VIPS as a simple array of bytes. It might be slightly faster.
You're right, the draw operations in vips don't work well with very large images in Python. It's not hard to write a thing in vips to make a mask image of any size from a set of points (just combine lots of && and < with the usual winding rule), but using PIL is certainly simpler.
You could also consider having your poly mask as an SVG image. libvips can load very large SVG images efficiently (it renders sections on demand), so you just magnify it up to whatever size you need for your raster images.

Using dicomwrite with color images

I am trying to write a sequence of color images to a dicom file in Matlab. Each image is of type uint16. The sequence is stored in a 4D matrix named output of size 200x360x3x360 (num of rows x num of cols x num of channels x num of images). When I execute dicomwrite(output,'outputfile.dcm'), it gives the following error:
It says data bit depth is 8 but I've ensured that each image is 16-bit. Not sure what's going wrong.
The documentation for dicomwrite says it can write color images as well. In fact dicomread can read color dicom images such that the size of the matrix which stores the read data is 200x360x3x360. So I guess it should be possible to write color images as well using dicomwrite. Any help in this regard is appreciated. There is a related post but it doesn't talk about color image sequence.
The comment by JohnnyQ is correct.
From this page down in section A.8.5.4, they list the Multi-frame True Color SC Image IOD Content Constraints (partial list quote):
In the Image Pixel Module, the following constraints apply:
Samples per Pixel (0028,0002) shall be 3
Bits Allocated (0028,0100) shall be 8
Bits Stored (0028,0101) shall be 8
High Bit (0028,0102) shall be 7
Pixel Representation (0028,0103) shall be 0
It seems matlab will not do the conversion for you, so you should down convert each 16-bit color channel to 8-bit for DICOM