Changing palette's of 8-bit .png images using python PIL - python

I'm looking for a fast way to apply a new palette to an existing 8-bit .png image. How can I do that? Is the .png re-encoded when I save the image? (Own answer: it seems so)
What I have tried (edited):
import Image, ImagePalette
output = StringIO.StringIO()
palette = (.....) #long palette of 768 items
im = Image.open('test_palette.png') #8 bit image
im.putpalette(palette)
im.save(output, format='PNG')
With my testimage the save function takes about 65 millis. My thought: without the decoding and encoding, it can be a lot faster??

If you want to change just the palette, then PIL will just get in your way. Luckily, the PNG file format was designed to be easy to deal with when you only are interested in some of the data chunks. The format of the PLTE chunk is just an array of RGB triples, with a CRC at the end. To change the palette on a file in-place without reading or writing the whole file:
import struct
from zlib import crc32
import os
# PNG file format signature
pngsig = '\x89PNG\r\n\x1a\n'
def swap_palette(filename):
# open in read+write mode
with open(filename, 'r+b') as f:
f.seek(0)
# verify that we have a PNG file
if f.read(len(pngsig)) != pngsig:
raise RuntimeError('not a png file!')
while True:
chunkstr = f.read(8)
if len(chunkstr) != 8:
# end of file
break
# decode the chunk header
length, chtype = struct.unpack('>L4s', chunkstr)
# we only care about palette chunks
if chtype == 'PLTE':
curpos = f.tell()
paldata = f.read(length)
# change the 3rd palette entry to cyan
paldata = paldata[:6] + '\x00\xff\xde' + paldata[9:]
# go back and write the modified palette in-place
f.seek(curpos)
f.write(paldata)
f.write(struct.pack('>L', crc32(chtype+paldata)&0xffffffff))
else:
# skip over non-palette chunks
f.seek(length+4, os.SEEK_CUR)
if __name__ == '__main__':
import shutil
shutil.copyfile('redghost.png', 'blueghost.png')
swap_palette('blueghost.png')
This code copies redghost.png over to blueghost.png and modifies the palette of blueghost.png in-place.
->

im.palette is not callable -- it's an instance of the ImagePalette class, in mode P, otherwise None. im.putpalette(...) is a method, so callable: the argument must be a sequence of 768 integers giving R, G and B value at each index.

Changing palette's without decoding and (re)encoding does not seem possible. The method in the question seems best (for now). If performance is important, encoding to GIF seems a lot faster.

Related

How to conserve header when saving an edited .fits file with Astropy?

I'm editing a .fits file I have in python but I want the header to stay the exact same. This is the code:
import numpy as np
from astropy.io import fits
import matplotlib.pyplot as plt
# read in the fits file
im = fits.getdata('myfile.fits')
header = fits.getheader('myfile.fits')
ID = 1234
newim = np.copy(im)
newim[newim == ID] = 0
newim[newim == 0] = -99
newim[newim > -99] = 0
newim[newim == -99] = 1
plt.imshow(newim,cmap='gray', origin='lower')
plt.colorbar()
hdu = fits.PrimaryHDU(newim)
hdu.writeto('mynewfile.fits')
All this is fine and does exactly what I want it to do except that it does not conserve the header after it saves the new file. Is there any way to fix this such that the original header file is not lost?
First of all don't do this:
im = fits.getdata('myfile.fits')
header = fits.getheader('myfile.fits')
As explained in the warning here, this kind of usage is discouraged (newer versions of the library have a caching mechanism that makes this less inefficient than it used to be, but it's still a problem). This is because the first one returns just the data array from the file, and the latter returns just the header from a file. At that point there's no longer any association between them; it's just a plain Numpy ndarray and a plain Header and their associations with a specific file are not tracked.
You can return the full HDUList data structure which represents the HDUs in a file, and for each HDU there's an HDU object associating headers with their arrays.
In your example you can just open the file, modify the data array in-place, and then use the .writeto method on it to write it to a new file, or if you open it with mode='update' you can modify the existing file in-place. E.g.
hdul = fits.open('old.fits')
# modify the data in the primary HDU; this is just an in-memory operation and will not change the data on disk
hdul[0].data +=1
hdul.writeto('new.fits')
There's also no clear reason for doing this in your code
newim = np.copy(im)
Unless you have a specific reason to keep an unmodified copy of the original array in memory, you can just directly modify the original array in-place.

How to compare two image files contents in python?

I want to compare two image files (.png) basically reading two .png files and assert if the contents are equal.
I have tried below,
def read_file_contents(file1, file2):
with open(file1, 'r', errors='ignore') as f:
contents1 = f.readlines()
f.close()
with open(file1, 'r', errors='ignore') as f:
contents2 = f.readlines()
f.close()
return {contents1, contents2}
then to assert if both the contents are equal I use
assert contents1 == contents2
but this gives me assertionerror. could someone help me with this. thanks.
There are multiple ways to accomplish this task using various Python libraries, including numpy & math, imagehash and pillow.
Here is one way (which I modified to only compare 2 images).
# This module is used to load images
from PIL import Image
# This module contains a number of arithmetical image operations
from PIL import ImageChops
def image_pixel_differences(base_image, compare_image):
"""
Calculates the bounding box of the non-zero regions in the image.
:param base_image: target image to find
:param compare_image: set of images containing the target image
:return: The bounding box is returned as a 4-tuple defining the
left, upper, right, and lower pixel coordinate. If the image
is completely empty, this method returns None.
"""
# Returns the absolute value of the pixel-by-pixel
# difference between two images.
diff = ImageChops.difference(base_image, compare_image)
if diff.getbbox():
return False
else:
return True
base_image = Image.open('image01.jpeg')
compare_image = Image.open('image02.jpeg')
results = image_pixel_differences (base_image, compare_image)
I have additional examples, so please let me know if this one does not work for you.
If you just want an exact match, you can compare the bytes directly:
def validate_file_contents(file1, file2):
with open(file1, 'rb', errors='ignore') as f1, open(file2, 'rb', errors='ignore') as f2:
contents1 = f1.read()
contents2 = f2.read()
return contents1 == contents2
You could use an assert if you want, but personally I'd check the True/False condition instead.
You also had a few errors in your code:
The content within the with block is not indented.
In a with block you don't need to close() the files.
You are returning a set of content1 and content2, where if they are actually equal, you will only have 1 item returned. You probably wanted to return (content1, content2) as a tuple.
I don't think using selenium as a tag here is a right choice but w/e.
Images can and are represented as bunch of pixels (basically numbers) arranged in such way that makes them what they are.
The idea is to take those numbers with their arrangement of both pictures and calculate the distance between them, there are multiple ways to do so like MSE.
For the code it self and a further explanation please check out the link below.
https://www.pyimagesearch.com/2014/09/15/python-compare-two-images/
Good luck buddy! (:

How to read the mask of an image using opencv in python

I am working on this challenge called Carvana Segmentation in kaggle. The dataset consists of 5088 images, for each image there is a mask. For eg, the below is a single image (.jpg file) and its corresponding mask (.gif file).
I was able to read .jpg files using cv2, but not the .gif files. The syntax i used to read .gif file is
>>> image = cv2.imread('filename.gif',cv2.IMREAD_GRAYSCALE)
When I try to print the image, returns None
>>> print(image) -> None
Can someone suggest any other method, please
imageio allows to read gifs like this:
import imageio
img = imageio.imread('filename.gif')
Following this repo:
https://github.com/asharma327/Read_Gif_OpenCV_Python/blob/master/gif_to_pic.py
you can do the following to read the image
import cv2
import os
def convert_gif_to_frames(gif):
# Initialize the frame number and create empty frame list
frame_num = 0
frame_list = []
# Loop until there are frames left
while True:
try:
# Try to read a frame. Okay is a BOOL if there are frames or not
okay, frame = gif.read()
# Append to empty frame list
frame_list.append(frame)
# Break if there are no other frames to read
if not okay:
break
# Increment value of the frame number by 1
frame_num += 1
except KeyboardInterrupt: # press ^C to quit
break
return frame_list
def output_frames_as_pics(frame_list):
# Reduce the list of frames by half to make the list more managable
frame_list_reduce = frame_list[0::2]
# Get the path of the current working directory
path = os.getcwd()
# Set then name of your folder
'''Replace this name with what you want your folder name to be'''
folder_name = 'Picturebook_Pics_Kiss'
# If the folder does not exist, then make it
if not os.path.exists(path + '/' + folder_name):
os.makedirs(path + '/' + folder_name)
for frames_idx in range(len(frame_list_reduce)):
cv2.imwrite(os.path.join(path + '/' + folder_name, str(frames_idx+1) + '.png'), frame_list_reduce[frames_idx])
return
gif = cv2.VideoCapture('/home/ahmedramzy/Documents/gif/giphy.gif')
# here you can get the frames and work on it
xx = convert_gif_to_frames(gif_kiss)
# here if you want to write it on hard disk using imwrite
output_frames_as_pics(xx)
You can't use imread(), there's no codec for that builtin (still a license problem)[https://answers.opencv.org/question/185929/how-to-read-gif-in-python/]
Since you are interested in python, you may use PIL library as mentioned here.
from PIL import Image
im = Image.open("animation.gif")
# To iterate through the entire gif
try:
while 1:
im.seek(im.tell()+1)
# do something to im
except EOFError:
pass # end of sequence

Creating image files that's named in numerical sequence

I have a script that's supposed to open a png image and then resize it and then save it as an jpg in numerical sequence. But the code for the number sequencing I copied from the internet isn't working with PIL. It gives me the exception "KeyError: 'W'"
import os
from PIL import Image
os.chdir('C:\\Users\\paul\\Downloads')
# open canvas.png
original = Image.open('canvas.png')
# resize image height to 2160
size = (3000, 2160)
original.thumbnail(size)
# convert to RGB
RGB = original.convert('RGB')
# save image as sequence
i = 0
while os.path.exists("image%s.jpg" % i):
i += 1
RGB.save("image%s.jpg" % i, "w")
Is there another way to do this?
Edit based on Haken Lid's comment
The PIL documentation says that the function save accepts these argument:
Image.save(fp, format=None, **params)
The parameter w you passed is not within the set of accepted file format.
Here you can see which formats are accepted. To make it works, just drop the w argument and substitute the %s with %d (i is an integer, not a string):
RGB.save("image%d.jpg" % i)
Note: from your tags it is not clear if you're using python2 or python3. If you are using python 3, I suggest to use the new method to format string:
RGB.save("image{}.jpg".format(i))
You can even specify a padding so that you can sort your file by name later on:
RGB.save("image{:04d}.jpg".format(i))
where 4 means that your number will be padded with zeros as to have length of at least 4.

Alternative method to compare original image with edited one that may not need the original

A while back I did a python script to store data in images, however it has a small problem and I'm just wondering if anyone might be able to think of an alternative method.
A very basic idea is it'll pickle something, then with the first version, it directly wrote the ASCII numbers as pixels (since everything is between 0 and 255). That'll result in an image that looks a little like TV noise.
When writing into an actual image, it'll detect the minimum bits per pixel it needs to adjust so it'll not be noticeable to the human eye, and it'll split the data and add or subtract a few bits from each pixel, with the very first pixel storing the method it's using. I then store the URL as a file in the image, and can reverse it by comparing the original image from the URL with the current image using the rules given in the first pixel.
A bit of python pseudocode in case I didn't explain that well:
original_image = (200, 200, 200, 100, 210, 255...)
stuff_to_store = "test"
#Convert anything into a list of bytes
data_numbers = [bin(ord(x)) for x in cPickle.dumps(stuff_to_store)]
#This is calculated by the code, but for now it's 2
bytes_per_pixel = 2
store_mode = 'subtract'
#Join the bytes and split them every 2nd character
new_bytes = "".join(data_bytes)
new_bytes_split = [new_bytes[i:i+bytes_per_pixel] for i in range(0, len(new_bytes), bytes_per_pixel)]
#Edit the pixels (by subtraction in this case)
pixel_data = []
for i in range(len(original_image)):
pixel_data = original_image[i] - int(new_bytes_split[i])
However, since the whole point of the script is to store things by modifying the pixels, storing the original image URL as a file feels a bit of a cheaty way to do it. I thought of storing the URL as the first few pixels, but it'd end up with a noticeable line whenever the image wasn't grey. Also, this way is incredibly inefficient as it needs two images to work properly, so it'd be great if anyone had an idea of how to avoid doing this.
The original code is here if anyone is interested, I did it before I learned about writing the documentation so it's a bit hard to figure out, just asking this now as I'm planning on rewriting it and would like to do it better.
Here's one way to embed data into the least significant bit of each colour channel of the pixels in a 8 bit per channel RGB image file, using PIL to do the image handling.
The code below illustrates bit stream handling in Python. It's reasonably efficient (as far as such operations can be efficient in Python), but it sacrifices efficiency for readability & simplicity of use when necessary. :)
#! /usr/bin/env python
''' Steganography with PIL (really Pillow)
Encodes / decodes bits of a binary data file into the LSB of each color
value of each pixel of a non-palette-mapped image.
Written by PM 2Ring 2015.02.03
'''
import sys
import getopt
import struct
from PIL import Image
def readbits(bytes):
''' Generate single bits from bytearray '''
r = range(7, -1, -1)
for n in bytes:
for m in r:
yield (n>>m) & 1
def encode(image_bytes, mode, size, dname, oname):
print 'Encoding...'
with open(dname, 'rb') as dfile:
payload = bytearray(dfile.read())
#Prepend encoded data length to payload
datalen = len(payload)
print 'Data length:', datalen
#datalen = bytearray.fromhex(u'%06x' % datalen)
datalen = bytearray(struct.pack('>L', datalen)[1:])
payload = datalen + payload
databits = readbits(payload)
for i, b in enumerate(databits):
image_bytes[i] = (image_bytes[i] & 0xfe) | b
img = Image.frombytes(mode, size, str(image_bytes))
img.save(oname)
def bin8(i):
return bin(i)[2:].zfill(8)
bit_dict = dict((tuple(int(c) for c in bin8(i)), i) for i in xrange(256))
def decode_bytes(data):
return [bit_dict[t] for t in zip(*[iter(c&1 for c in data)] * 8)]
def decode(image_bytes, dname):
print 'Decoding...'
t = decode_bytes(image_bytes[:24])
datalen = (t[0] << 16) | (t[1] << 8) | t[2]
print 'Data length:', datalen
t = decode_bytes(image_bytes[24:24 + 8*datalen])
with open(dname, 'wb') as dfile:
dfile.write(str(bytearray(t)))
def process(iname, dname, oname):
with Image.open(iname) as img:
mode = img.mode
if mode == 'P':
raise ValueError, '%s is a palette-mapped image' % fname
size = img.size
image_bytes = bytearray(img.tobytes())
#del img
print 'Data capacity:', len(image_bytes) // 8 - 24
if oname:
encode(image_bytes, mode, size, dname, oname)
elif dname:
decode(image_bytes, dname)
def main():
#input image filename
iname = None
#data filename
dname = None
#output image filename
oname = None
def usage(msg=None):
s = msg + '\n\n' if msg else ''
s += '''Embed data into or extract data from the low-order bits of an image file.
Usage:
%s [-h] -i input_image [-d data_file] [-o output_image]
To encode, you must specify all 3 file names.
To decode, just specify the input image and the data file names.
If only the the input image is given, its capacity will be printed,
i.e., the maximum size (in bytes) of data that it can hold.
Uses PIL (Pillow) to read and write the image data.
Do NOT use lossy image formats for output, eg JPEG, or the data WILL get scrambled.
The program will abort if the input image is palette-mapped, as such images
are not suitable.
'''
print >>sys.stderr, s % sys.argv[0]
raise SystemExit, msg!=None
try:
opts, args = getopt.getopt(sys.argv[1:], "hi:d:o:")
except getopt.GetoptError, e:
usage(e.msg)
for o, a in opts:
if o == '-h': usage(None)
elif o == '-i': iname = a
elif o == '-d': dname = a
elif o == '-o': oname = a
if iname:
print 'Input image:', iname
else:
usage('No input image specified!')
if dname:
print 'Data file:', dname
if oname:
print 'Output image:', oname
process(iname, dname, oname)
if __name__ == '__main__':
main()

Categories

Resources