Creating an image from single bits in Python 3 - python

I have created an array of bits by using this:
Data = []
Bytes = numpy.fromfile(filename, dtype = "uint8")
Bits = numpy.unpackbits(Bytes)
for b in Bits:
Data.append(b)
"filename" ends with ".png".
Later on, I do some stuff with these bits. I want to save an image with another(or the same) set of bits. How do I do it? The best option would be using: saveToPNG(Data)

You can save those bits as a PNG file by simply reversing the steps you've used.
BTW, there's no need to create the Data list: you can access the bits in the Bits array with normal Python functions & operators as well as with Numpy. But if you really do want those bits in a plain Python list then there's no need for that slow for ... append loop: just pass the array to the list constructor.
I've changed your variable names to make them conform to the PEP-8 style guide.
import numpy as np
# File names
in_name = 'square.png'
out_name = 'square_out.png'
# Read data and convert to a list of bits
in_bytes = np.fromfile(in_name, dtype = "uint8")
in_bits = np.unpackbits(in_bytes)
data = list(in_bits)
# Convert the list of bits back to bytes and save
out_bits = np.array(data)
print(np.all(out_bits == in_bits))
out_bytes = np.packbits(out_bits)
print(np.all(out_bytes == in_bytes))
out_bytes.tofile(out_name)
However, I don't know why you want to do this. If you want access to the image data in the PNG file then you need to decode it properly. A simple way to do that is to use PIL (Pillow) to load the image file into a PIL Image object; Numpy can make an array from a PIL Image. You can then use standard Numpy tools to analyze or manipulate the raw image data, and then pass it back to PIL to save it as a PNG (or various other image file formats). See the final code block in this answer for an example.

Related

Is there any information in numpy array header?

There are 360 files with .bin extension which I know that they are 360 raw image files (16-bit grayscale). I guess
the size of images is something around 1518x999. I am puzzled how to get image data out of them. I examined them and found that
there are 149 bytes repeated at begining of all files and 15 bytes at end of all files (they are marked with white box in below pics).
Are these header and footer something common in numpy array? (I see numpy multiarray ... among header bytes. See below pics)
Can I extract some information about the image specs like width and height from the header and footer?
Here are three examples of the files.
Yes. The header contains information about the type and size of the array.
Using numpy (and pillow), you can easily retrieve the image as follows.
# Using python 3.6 or higher.
# To install numpy and pillow, run: pip3 install numpy pillow
from pathlib import Path
import numpy as np
from PIL import Image
input_dir = Path("./binFiles") # Directory where *.bin files are stored.
output_dir = Path("./_out") # Directory where you want to output the image files.
output_dir.mkdir(parents=True, exist_ok=True)
for path in input_dir.rglob("*.bin"):
buf = np.load(path, allow_pickle=True)
image = Image.fromarray(buf)
image.save(output_dir / (path.stem + ".png"))
Here is a sample.
(I couldn't upload in original png format, so this is converted one)
EDIT:
Questions
Is there any more information in header than what it was retrieved?
Is there any information in that footer?
Answer
Theoretically, both answers are no.
Your files are actually not in numpy file format, but numpy object in pickle file format.
I was able to rebuild the exact matching file using only dtype, shape, order, and an array of 3,032,964 (=999x1518x2) bytes. Thus, numpy or pickle may have added additional metadata, but only those four are the essential information (at least for the three files you provided).
If you want to know about "additional metadata", I don't have an answer for you, you might want to ask a refined new question since this is about pickle file format.
Here is the code I used for checking, in case you might want to check other files as well.
for input_path in input_dir.rglob("*.bin"):
# Load the original file.
numpy_array = np.load(input_path, allow_pickle=True)
# Convert to a byte array. 'A' means keep the order.
bytes_array = numpy_array.tobytes('A')
# Make sure there are no additional bytes other than the image pixels.
assert len(bytes_array) == numpy_array.size * numpy_array.itemsize
# Rebuild from byte array.
# Note that rebuilt_array is constructed using only dtype, shape, order,
# and a byte array matching the image size.
rebuilt_array = np.frombuffer(
bytes_array, dtype=numpy_array.dtype
).reshape(
numpy_array.shape, order='F' if np.isfortran(numpy_array) else 'C'
)
# Pickle the rebuilt array (mimicking the original file).
rebuilt_path = output_dir / (input_path.stem + ".pickle")
with rebuilt_path.open(mode='wb') as fo:
pickle.dump(rebuilt_array, fo, protocol=4)
# Make sure there are no additional bytes other than the rebuilt array.
assert rebuilt_path.read_bytes() == input_path.read_bytes()
print(f"{input_path.name} passed!")

Is there any way to use arithmetic ops on FITS files in Python?

I'm fairly new to Python, and I have been trying to recreate a working IDL program to Python, but I'm stuck and keep getting errors. I haven't been able to find a solution yet.
The program requires 4 FITS files in total (img and correctional images dark, flat1, flat2). The operations are as follows:
flat12 = (flat1 + flat2)/2
img1 = (img - dark)/flat12
The said files have dimensions (1024,1024,1). I have resized them to (1024,1024) to be able to even use im_show() function.
I have also tried using cv2.add(), but I get this:
TypeError: Expected Ptr for argument 'src1'
Is there any workaround for this? Thanks in advance.
To read your FITS files use astropy.io.fits: http://docs.astropy.org/en/latest/io/fits/index.html
This will give you Numpy arrays (and FITS headers if needed, there are different ways to do this, as explained in the documentation), so you could do something like:
>>> from astropy.io import fits
>>> img = fits.getdata('image.fits', ext=0) # extension number depends on your FITS files
>>> dark = fits.getdata('dark.fits') # by default it reads the first "data" extension
>>> darksub = img - dark
>>> fits.writeto('out.fits', darksub) # save output
If your data has an extra dimension, as shown with the (1024,1024,1) shape, and if you want to remove that axis, you can use the normal Numpy array slicing syntax: darksub = img[0] - dark[0].
Otherwise in the example above it will produce and save a (1024,1024,1) image.

How to save floating-point pixel values in image file

I want to save floating-point numbers as pixels in an image file. I am currently working in OpenCV-python, but I had also tried it with Pillow (PIL). Both packages convert float pixel data to integer before writing them to the file.
I want to save pixel values such as:
(245.7865, 123.18788, 98.9866)
But when I read back the image file I get:
(246, 123, 99)
Somehow my floating-point numbers get rounded off and converted to integers.
How to stop PIL or OpenCV from converting them to integer?
Raster images are normally stored as integer values only. Instead save the numpy array directly like so
x = numpy.array([1, 2, 3])
with open('x.npy', 'wb') as f:
numpy.save(f, x)
Then load the variable back like so
x = numpy.load('x.npy')
Other alternatives include
Save one or more GRAY16 png images, with your floats multiplied and truncated.
Use the Netpbm format supporting floats.
Save a pickle.
The behavior you observe depends on the file format in which you save the image. Few image formats have a specification for floating-point pixel values. Though some do, first and foremost TIFF.
To demonstrate the desired behavior with a TIFF image writer, consider the following script. It uses the versatile image input/output library ImageIO, which relies on PILlow as one of its back-ends:
# Use Stack Overflow logo as sample image.
import imageio
logo = 'https://cdn.sstatic.net/Sites/stackoverflow/img/logo.png'
image = imageio.imread(logo)
# Normalize to 1. Pixel values are now floating-point.
image = image / image.max()
# Save as image file and read back in.
format = 'tiff'
imageio.imwrite(f'image.{format}', image)
print(f'wrote: {image.dtype}')
image = imageio.imread(f'image.{format}')
print(f'read: {image.dtype}')
The output of that script is:
wrote: float64
read: float64
If, on the other hand, you change the format to PNG (format = 'png' in the code), the output is:
Lossy conversion from float64 to uint8. Range [0, 1].
Convert image to uint8 prior to saving to suppress this warning.
wrote: float64
read: uint8

How to convert from numpy array to file byte object?

I read an image file as
with open('abc.jpg', 'rb') as f:
a = f.read()
On the other hand, I use cv2 to read the same file
b = cv2.imread('abc.jpg', -1)
How to convert b to a directly?
Thanks.
Answer to your question:
success, a_numpy = cv2.imencode('.jpg', b)
a = a_numpy.tostring()
Things you should know:
First, type(a) is a binary string, and type(b) is a numpy array. It's easy to convert between those types, since you can make np.array(binary_string) to go from string to numpy, and np_array.tostring() to go from numpy to binary string.
However, a and b represent different things. In the string a, you're reading the JPEG encoded version of the image, and in b you have the decoded image. You can check that len(b.tostring()) is massively larger than len(a). You need to know which one you want to use. Also, keep in mind that each time you encode a JPEG, you will loose some quality.
How to save an image to disk:
Your question looks like you want an encoded binary string. The only use I can imagine for that is dumping it to the disk (or sending it over http?).
To save the image on your disk, you can use
cv2.imwrite('my_file.jpg', b)

Extract and save a slice from a volume with numpy

I'm trying to extract a slice from a volume using numpy.
The volume is 512x512x132 and I want the slice number 66.
Each voxel is an unsigned 16-bit integer.
My code is:
import numpy
original = numpy.fromfile(filepath, dtype=numpy.uint16)
original = numpy.reshape(original, (512,512,132))
slice = original[:,:,66]
f = open('test.rawl', 'w')
slice.tofile(f)
f.close()
The code complete cleanly, but when I open the slice with an external program, it is not the slice data but garbage.
What I am doing wrong?
Thanks
Your first problem is that you have your axes wrong. Assuming you have 132 layers of 512x512 images you want to use:
original = numpy.fromfile(filepath, dtype=numpy.uint16).reshape((132, 512, 512))
Then for the slice take:
slc = original[66]
Also, binary data such as Numpy arrays use:
f = open('test.raw', 'wb')
The 'b' in the mode is for binary. Otherwise Python will assume you're trying to write text and do things like convert newlines to the appropriate format for the system, among other things.
By the way, the ndarray.tofile() method also takes a filename, so unless you have a particular reason to it's not necessary to create a file handle first. You can just use
arr.tofile('test.raw')
One final note: Try not to use slice as a variable. That's a builtin name in Python and you could run into trouble by shadowing it with something else.

Categories

Resources