I am looking for a fast way to save a grayscale image as a 4-bit png with python. The images that I have to save are quite big, so saving them takes quite some time.
Suppose my image is stored in a numpy-array (dtype=8-bit). With PyPng I can do:
import png
data = map(lambda x: map(int, x/17), data)
png.from_array(data, 'L;4').save(filename)
This will save a proper 4-bit png. With Pillow, I can do:
import PIL.Image as Image
im = Image.fromarray(data)
im.save(filename)
The second approach (Pillow) is about 10 times as fast as the first one (even without the conversation), however the images are 8-bit pngs. I tried adding the lines
im = im.point(lambda i: i/17) # convert values
im.mode = 'L;4'
but then I get *** SystemError: unknown raw mode, even though the Mode 'L;4' is specified in https://github.com/python-pillow/Pillow/blob/master/PIL/PngImagePlugin.py
Does anyone know how to save 4-bit pngs with Pillow or is there another fast way to do it?
Pillow doesn't support 4-bits grayscale. However, if, like me, you just want to convert the 8-bit image to a 4-bit bytestring, you can.
Just dividing by 17 isn't enough, because each pixel will still be output as 1 byte. You need to pair each subsequent nibble with its neighbor nibble to get a full byte.
For that you could use something like this:
def convert_8bit_to_4bit(bytestring):
fourbit = []
for i in range(0,len(bytestring),2):
first_nibble = int(bytestring[i] / 17)
second_nibble = int(bytestring[i+1] / 17)
fourbit += [ first_nibble << 4 | second_nibble ]
fourbit = bytes(fourbit)
return fourbit
Dependent on how your other application will handle the order of the nibbles you might have to switch 'first_nibble' and 'second_nibble' with each other
Related
I am trying to read data from this png image, and then place the image length at the start of the data, and pad it a given number of spaces defined by my header variable. However, once I do that, the image length increases drastically for a reason beyond my knowledge. Please can someone inform me of what is happening? I must be missing something since I am still fairly new to this field.
HEADER = 10
PATH = os.path.abspath("penguin.png")
print(PATH)
with open(PATH,"rb") as f:
imgbin = f.read()
print(len(imgbin))
imgbin = f"{len(imgbin):<{HEADER}}"+str(imgbin)
print(len(imgbin))
when I first print the length of the data, I get a length of 163287, and on the second print, I get a length of 463797
This is because you are changing the data from binary string to a string when you load the image to when you pass it through str:
len(imgbin), len(str(imgbin))
>>> (189255, 545639)
(note I use a different image so the numbers are different). You can solve this issue by adding a binary string to the start like so:
with open(PATH,"rb") as f:
imgbin = f.read()
imgbin = f"{len(imgbin):<{HEADER}}".encode('utf-8')+imgbin
print(len(imgbin))
>>> 189245
>>> 189255
You can find out more about binary strings here.
For reference it is worth noting that png images are uint-8 in type (i.e. 0-255). It is possible to manipulate them as binary strings because they can be utf-8 (i.e. the same size). However, it might be worth using something like numpy where you have uint-8 as a data type so as to avoid this.
I was given this school project, which goal is to take a picture, apply the RLE method in order to compress it in a binary file (.txt), and save some space. And after struglling with it for 3 days now I'm having troubles.
def read():
f2=open("data2.txt",'rb') #open a binary file in reading mod.
im3=zeros((962,800,3),dtype="uint8") #create picture which size is 962*800 where every pixels are coded on 3 bytes.
d=0#pixel counter
i=0#lign indexation
j=0#column indexation
b=ord(f2.read(1))# the informations are read a first time
a=ord(f2.read(1))
red=ord(f2.read(1))
green=ord(f2.read(1))
blue=ord(f2.read(1))
while i!=im3.shape[0]: #as long as it doesn't reach the final lign
if d<=(a+b*255):
im3[i,j,0] = red
im3[i,j,1] = green
im3[i,j,2] = blue
d+=1
j+=1
if j==im3.shape[1]:
j=0
i+=1
else: #resets pixel counter and starts reading next informations
d=0
b=ord(f2.read(1))
a=ord(f2.read(1))
red=ord(f2.read(1))
green=ord(f2.read(1))
blue=ord(f2.read(1))
f2.close()
imsave("done.bmp",im3)
return im3
imshow(read());
show();
When I execute the pgrm, it gives me this error written in the tittle. Which is something I feel like impossible to correct, since it is writting itself in hex.
Here's further information :
A pixel is not coded here using bytes, like we usually do on format such as .bmp.
Here RLE will search for identic pixel on a same lign, and count how many times it encounters it, by comparing 3 levels colors. Finally it stores in two additional bytes than the RGB ones : a and b.
a is for the number of pixels.
b is the number of 255 stacks of pixels. (Since I'm coding data into 8bits and that pictures are usually larger than 255*255 size)
Your file is too short, your ran out of data. file.read(..) returns an empty string when the EOF is found. Perhaps your remaining bytes should just be left at 0?
You are reading your data extremely inefficiently. Use the struct module to unpack your data into integers, and create a single long numpy array of (R, G, B) triplets, then reshape the data to form your image matrix:
import numpy as np
import struct
def lecture_fichier_compresse():
width, height = 800, 962
with open("data2.txt", 'rb') as rle_data:
image = np.zeros((width * height, 3), dtype="uint8")
pos = 0
# decompress RLE data; 2 byte counter followed by 3 RGB bytes
# read until the file is done
for data in iter(lambda: rle_data.read(5), ''):
if len(data) < 5:
# not enough data to store another RLE RGB chunk
break
count, r, g, b = struct.unpack('>HBBB', data)
image[pos:pos + count, :] = [[r, g, b]] * count
pos += count
# reshape continuous stream into an image matrix
image = image.reshape((height, width, 3))
imsave("Copie_compresse.bmp",im3)
return image
That said, using your sample file data I can't seem to construct a coherent image. Decompression produces data for 695046 pixels, which doesn't make for a coherent rectangular image (the highest short dimension that numbers factors into is 66, so extremely elongated). Even allowing for missing data at the end I can't seem to find any aspect ratio that produces a coherent image. The conclusion is that your input data is incomplete or incorrect.
I am new to Tkinter (and Python) and I would like to find the most efficient way to format RGB values into a string so it can be used with the PhotoImage.put() function.
Let's say I have a Numpy rank 3 array in which the RGB values are stored, the 3rd dimension having a length of 3 for red, green and blue respectively. The most intuitive way to proceed would be:
for i in range(0, n_pixels_x):
for j in range(0, n_pixels_y):
hexcode = "#%02x%02x%02x" % (array[i,j,0], array[i,j,1], array[i,j,2])
img.put(hexcode, (j,i))
Unfortunately, this is way too slow for large images.
As described in the PhotoImage Wiki, it is possible to pass one large string to put() so the function is called only once. Then, I need to efficiently convert my array into such a string, which should be formatted like this (for a 4x2 image):
"{#ff0000 #ff0000 #ff0000 #ff0000} {#ff0000 #ff0000 #ff0000 #ff0000}"
Again, this could easily be done with nested for loops, but I would like to avoid them for efficiency reasons. Is there any way to use join() in order to do what I want?
If needed, I can store the content of my array differently, the only constraint being that I should be able to modify the color values easily.
Edit: After working on this a bit, I found a way to format my values approximately 10 times faster than by using nested loops. Here is the commented piece of code:
# 1. Create RGB array
array = np.zeros((n_pixels_x*n_pixels_y, 3))
array = np.asarray(array, dtype = "uint32")
array[1,:] = [0, 100, 255]
# 2. Create a format string
fmt_str = "{" + " ".join(["#%06x"]*n_pixels_x) + "}"
fmt_str = " ".join([fmt_str]*n_pixels_y)
# 3. Convert RGB values to hex
array_hex = (array[:,0]<<16) + (array[:,1]<<8) + array[:,2]
# 4. Format array
img_str = fmt_str % tuple(array_hex)
For a 640x480 array, steps 3 and 4 take ~0.1s to execute on my laptop (evaluated with timeit.default_timer()). Using nested loops, it takes between 0.9s and 1.0s.
I would still like to reduce the computation time, but I'm not sure if any improvement is still possible at this point.
I was able to find another way to format my array, and this really seems to be the quickest solution. The solution is to simply use Image and ImageTk to generate an image object directly from the array:
array = np.zeros((height, width, 3), 'uint8')
imageObject = Image.fromarray(array)
img = ImageTk.PhotoImage(image = imageObject, mode = 'RGB'))
This takes approximately 0.02s to run, which is good enough for my needs, and there is no need to use the put() function.
I actually found this answer from another question: How do I convert a numpy array to (and display) an image?
I have a bunch of images (300 images of 400 X 400 pixels) with filenames like:
001.bmp
002.bmp
003.bmp
...
First, I tried reading one of them, e.g. using imread I get a (400L, 400L, 3L) matrix, the problem is the 3L (I think is RBG format), so the question here is: how can I read them and get a (400L, 400L, 1L) matrix that I need to proccess them?
Second, I tried to read the 300 images using a loop like the following:
data = np.zeros((400,400,300))
for i in range(300):
data[:,:,i] = imread('{0}.bmp'.format(i))
but it doesn't work, very probably my code is wrong. Actually doing this, I want to concatenate each (300) image data (400 X 400) into a matrix of (400 X 400 X 300).
When trying to use:
data[:,:,i] = imread('{0}.bmp'.format(i))
search for '1.bmp' and not '001.bmp', but due to the list go from 000 to 299, I got a problem with that and I cant write '00{0}.bmp'.format(i) to complete the filename, because for two- and three digits numbers I got '0012.bmp' or '00123.bmp'
Well, after hours, I got to do this
arrays = []
for number in range(0, 299):
numstr = str(number).zfill(3)
fname = numstr + '.bmp'
a = imread(fname, flatten=1)
arrays.append(a)
data = np.array(arrays)
This code its work well. Thankyou, for give me clues!
First, you are right that the last dimension are the color channels. I assume you want a grayscale image, which you can get with:
data = imread(fname, flatten=1)
That comes from the imread documentation here.
Second, your issue with the loop can be due to a couple of things. First, I don't see indentation in the code in your post, so make sure that is there on the loop body in the code that you are actually trying to run. Second, the code has a ".txt" extension. Are you sure you don't actually want ".bmp"?
I wish to implement a 2d bit map class in Python. The class would have the following requirements:
Allow the creating of arbitrarily sized 2d bitmaps. i.e. to create an 8 x 8 bitmap (8 bytes), something like:
bitmap = Bitmap(8,8)
provide an API to access the bits in this 2d map as boolean or even integer values, i.e.:
if bitmap[1, 2] or bitmap.get(0, 1)
Able to retrieve the data as packed Binary data. Essentially it would be each row of the bit map concatenated and returned as Binary data. It may be padded to the nearest byte or something similar.
bitmap.data()
Be able to create new maps from the binary data retrieved:
new_bitmap = Bitmap(8, 8, bitmap.data())
I know Python is able to perform binary operations, but I'd like some suggestions as how best to use them to implement this class.
Bit-Packing numpy ( SciPY ) arrays does what you are looking for.
The example shows 4x3 bit (Boolean) array packed into 4 8-bit bytes. unpackbits unpacks uint8 arrays into a Boolean output array that you can use in computations.
>>> a = np.array([[[1,0,1],
... [0,1,0]],
... [[1,1,0],
... [0,0,1]]])
>>> b = np.packbits(a,axis=-1)
>>> b
array([[[160],[64]],[[192],[32]]], dtype=uint8)
If you need 1-bit pixel images, PIL is the place to look.
No need to create this yourself.
Use the very good Python Imaging Library (PIL)