How to convert from numpy array to file byte object? - python

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)

Related

How to store a massive number of byte strings while having easy access to any small partion of them

Here's my problem. I have a huge dataset of videos. To save space as much as possible, I encode each image frame to a byte string with PyTurboJPEG. In this way, each video is a list of byte strings. Then I use pickle to dump this list of byte strings to the disk. When I want to access a certain video, I load the .pkl file with pickle.load first and then decode the byte strings into numpy.ndarray.
This strategy is efficient when I want to access the whole video. However, if I want to access only a part of the whole video, I still have to load the whole video first and then slice it to get a small partition (for example, in some video recognition tasks, the original videos of some dataset are too long and not suitable for training. For each video, we only use a part of it). This method is super io-burdensome and greatly slows the training process.
Is there any solution that we can store a list of byte strings in the disk and only load the part we want to RAM? A possible approach is using NpyAppendArray, but it doesn't support object arrays (in our cases, byte string) and directly saving the full numpy.ndarray instead of byte strings on the disk takes too much space.
A solution that we can store a list of byte strings in the disk and only load the part we want to RAM
I found an elegant solution. Just read the original binary stream. For examples, we have three images in a video, the length of the bytes of the first image is 8192, the second is 8633, and the third is 8321. We save the bytes of all three images in one file named video.bin, and the following codes shows how we can access the 2nd and 3rd images without reading the 1st image:
lens = [8192, 8633, 8321] # should be saved when you create the binary file
fp = open("./video.bin", "rb+")
st = 1 # the start index of images we want
ed = 2 # the end index of images we want
ed_offsets = lens.cumsum()
st_offsets = ed_offsets.copy()
st_offsets[0] = 0
st_offsets[1:] = ed_offsets[:-1] # calculate the start positions and end positions of each image
fp.seek(st_offsets[st])
hyp = fp.read(lens[st: ed].sum())
s = st_offsets[st: ed]
s -= s[0]
e = s.copy()
e[:-1] = s[1:]
e[-1] = len(hyp)
hyp = [hyp[s[i]: e[i]] for i in range(len(s))]
hyp is the array only containing the 2nd and the 3rd images.
The core functions we use is fp.seek and fp.read. See the official documentation for more information.

Pillow encoding and decoding result with different base64

I am trying to convert from base64 to PIL image and PIL image to base64. And I am using these scripts.
def convert_base64_2_pil_image(image_b64: bytes) -> Image:
image_str = base64.b64decode(image_b64)
return Image.open(io.BytesIO(image_str))
def convert_pil_image_2_base64(image: Image, format: str = 'PNG') -> bytes:
buffered = io.BytesIO()
image.save(buffered, format=format)
buffered.seek(0)
return base64.b64encode(buffered.getvalue())
When I try to convert one base64 string to a PIL image and again convert that PIL image to base64 the base64 strings are not the same. I am losing some information.
image_b64 = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='
image = convert_base64_2_pil_image(image_b64)
image_b64_converted = convert_pil_image_2_base64(image)
assert image_b64_converted == image_b64
How can I fix this problem?
The fact that your base-64 strings differ does not necessarily mean that you have lost information. If you want a better analysis/answer you will need to provide your images and base-64 representations in their exact, original form - probably via Dropbox or Google Drive - because most websites strip images of geographic and other information which affects the base-64 representation.
So, going back to your question, there are several possible explanations of why the base-64 representation could differ:
firstly, a PNG image often includes a date/time of last change as the ancillary tIME chunk, so merely opening and resaving a solid black image 1 second later could change the base-64 representation
secondly, although lossless, it is not guaranteed that two libraries will make the same decisions in terms of bit-depth, palette, alpha, filtering and so on. For example, one library may choose to save an image with a limited number of colours as a palette image, whereas another may choose an RGB representation and another may choose an RGBA representation. See here. One may choose an 8-bit per sample PNG, another may choose a 16-bit per sample PNG.

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

Creating an image from single bits in Python 3

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.

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