Why does Nibabel change the size of my file? - python

I am working with images in the format nii.gz. Therefore, I am using nibabel in order to open them. The problem is that when I open the images, transform them to numpy arrays and convert them back to Nifti format, the output size is changed. The sequence is:
nifti_image = nib.load('/my_path_to_image/image.nii.gz')
np_img = ct_images.get_fdata()
nifti_final = nib.Nifti1Image(data, affine=np.eye(4)) # Convert them to nifti
nib.save(nifti_final , 'image.nii.gz')
The initial file is ~45 MB, after running the code above, the image is ~65 MB. I know that the original images are 16-bit encoded. My initial theory was that when transforming to numpy array, they were encoded as 64-bit which is indeed the case. So I tried the following:
nifti_image = nib.load('/my_path_to_image/image.nii.gz')
np_img = ct_images.get_fdata()
np_img = np_img.astype(numpy.float16, copy=False)
nifti_final = nib.Nifti1Image(data, affine=np.eye(4)) # Convert them to nifti
nib.save(nifti_final , 'image.nii.gz')
However, the ouput is still the same size ~65MB. Any ideas why this is happening?

You should add the original nifti affine and header information to the output nifti. E.g., in your case:
nifti_final = nib.Nifti1Image(data, nifti_image.affine, nifti_image.header)

Related

Converting byte array to 16 bit grayscale image in Python

I'm working with large image datasets stored in a non-standard image format (.Tsm). Essentially it's a binary file with some headers at the start, very similar to FITS standard except stored in little-endian as opposed to FITS big-endian.
After reading the file header and formatting the metadata, I can read a single image using the following code
def __read_slice(self, file, img_num, dimensions):
"""Read a single image slice from .tsm file"""
pixel_range = self.metadata["pixel range"]
bytes_to_read = self.metadata["bytes to read"]
# position file pointer to correct byte
file.seek(self.HEADER_TOTAL_LEN + (bytes_to_read * img_num), 0)
all_bytes = file.read(bytes_to_read) # read image bytes
img = np.empty(len(pixels), dtype='uint16') # preallocate image vector
byte_idx = 0
for idx, pixel in enumerate(pixel_range):
img[idx] = (all_bytes[byte_idx + 1] << 8) + all_bytes[byte_idx]
byte_idx += 2
return np.reshape(img, (dimensions[1], dimensions[0])) # reshape array to correct dimensions
the trouble is the images can be very large (2048x2048) so even just loading in 20-30 frames for processing can take a significant amount of time. I'm new to python so i'm guessing the code here is pretty inefficient, especially the loop.
Is there a more efficient way to convert the byte data into 16bit integers?
You can try:
img= np.frombuffer(all_bytes, dtype='uint16')
Example:
>>> np.frombuffer(b'\x01\x02\x03\x04', dtype='uint16')
array([ 513, 1027], dtype=uint16)

cant save an 4d array int .txt file

I am using the sliding window technic to an image and i am extracting the mean values of pixels of each one window. So the results are someting like this [[[[215.015625][123.55036272][111.66057478]]]].now the question is how could i save all these values for every one window into a txt file or at a CSV because i want to use them for further compare similarities? whatever i tried the error is same..that it is a 4D array and not an 1D or 2D. I ll appreciate any help really.! Thank you in advance
import cv2
import matplotlib.pyplot as plt
import numpy as np
# read the image and define the stepSize and window size
# (width,height)
image2 = cv2.imread("bird.jpg")# your image path
image = cv2.resize(image2, (224, 224))
tmp = image # for drawing a rectangle
stepSize = 10
(w_width, w_height) = (60, 60 ) # window size
for x in range(0, image.shape[1] - w_width, stepSize):
for y in range(0, image.shape[0] - w_height, stepSize):
window = image[x:x + w_width, y:y + w_height, :]
# classify content of the window with your classifier and
# determine if the window includes an object (cell) or not
# draw window on image
cv2.rectangle(tmp, (x, y), (x + w_width, y + w_height), (255, 0, 0), 2) # draw rectangle on image
plt.imshow(np.array(tmp).astype('uint8'))
# show all windows
plt.show()
mean_values=[]
mean_val, std_dev = cv2.meanStdDev(image)
mean_val = mean_val[:3]
mean_values.append([mean_val])
mean_values = np.asarray(mean_values)
print(mean_values)
Human Readable Option
Assuming that you want the data to be human readable, saving the data takes a little bit more work. My search showed me that there's this solution for saving 3D data to a text file. However, it's pretty simple to extend this example to 4D for your use case. This code is taken and adapted from that post, thank you Joe Kington and David Cheung.
import numpy as np
data = np.arange(2*3*4*5).reshape((2,3,4,5))
with open('test.csv', 'w') as outfile:
# We write this header for readable, the pound symbol
# will cause numpy to ignore it
outfile.write('# Array shape: {0}\n'.format(data.shape))
# Iterating through a ndimensional array produces slices along
# the last axis. This is equivalent to data[i,:,:] in this case.
# Because we are dealing with 4D data instead of 3D data,
# we need to add another for loop that's nested inside of the
# previous one.
for threeD_data_slice in data:
for twoD_data_slice in threeD_data_slice:
# The formatting string indicates that I'm writing out
# the values in left-justified columns 7 characters in width
# with 2 decimal places.
np.savetxt(outfile, twoD_data_slice, fmt='%-7.2f')
# Writing out a break to indicate different slices...
outfile.write('# New slice\n')
And then once the data has been saved all you need to do is load it and reshape it (np.load()) will default to reading in the data as a 2D array but np.reshape() will allow us to recover the structure. Again, this code is adapted from the previous post.
new_data = np.loadtxt('test.csv')
# Note that this returned a 2D array!
print(new_data.shape)
# However, going back to 3D is easy if we know the
# original shape of the array
new_data = new_data.reshape((2,3,4,5))
# Just to check that they're the same...
assert np.all(new_data == data)
Binary Option
Assuming that human readability is not necessary, I would recommend using the built-in *.npy format which is described here. This stores the data in a binary format.
You can save the array by doing np.save('NAME_OF_ARRAY.npy', ARRAY_TO_BE_SAVED) and then load it with SAVED_ARRAY = np.load('NAME_OF_ARRAY.npy').
You can also save several numpy array in a single zip file with the np.savez() function like so np.savez('MANY_ARRAYS.npz', ARRAY_ONE, ARRAY_TWO). And you load the zipped arrays in a similar fashion SEVERAL_ARRAYS = np.load('MANY_ARRAYS.npz').

How to preprocess NIfTI data format using NiBabel (Python)

after convertig a NIfTI-file to an array using NiBabel, the array has three dimensions and the numbers look like this:
[-9.35506855e-42 -1.78675141e-35 1.18329136e-30 -1.58892995e-24
5.25227377e-24 1.11677966e-23 -2.41237451e-24 -1.51333104e-25
6.79829196e-30 -9.84047188e-36 1.23314265e-43 -0.00000000e+00]
How can I preprocess this array for machine-learning? When choosing only the exponent, most of the information gets lost when plotting the image, so maybe the base is also important?
Any help is appreciated.
This will help you to convert a niftiimage to access as numpy array:
img = nib.load(example_filename)
data = img.get_fdata()

Reading MONO 16-bit images using PyCapture2

I am using the CMLN-13S2M-CS camera from PointGrey. This camera has a MONO 16-bit pixel format.Using the PyCapture2 wrapper from PointGrey I am unable to retrieve the image the camera is recording.
I have the following code:
import sys
import numpy
import PyCapture2
## Connect camera
bus = PyCapture2.BusManager()
c = PyCapture2.Camera()
c.connect(bus.getCameraFromIndex(0))
## Configure camera format7 settings
fmt7imgSet = PyCapture2.Format7ImageSettings(0, 0, 0, 1296, 964, PyCapture2.PIXEL_FORMAT.MONO16)
fmt7pktInf, isValid = c.validateFormat7Settings(fmt7imgSet)
c.setFormat7ConfigurationPacket(fmt7pktInf.recommendedBytesPerPacket, fmt7imgSet)
## Start capture and retrieve buffer
c.startCapture()
im = c.retrieveBuffer()
print im.getData().shape
print numpy.max(im.getData())
The following is returned by the print statements: (2498688,) and 240. The shape is exactly 2 x (964 x 1296). How should I reshape this? Also, the maximum value when saturated is 255. This is odd as this corresponds to MONO 8 Pixel format. What am I doing wrong?
Here's a quick demo that shows how to convert a 1D array of uint8 to a 2D array of uint16. The key function we need here is view.
import numpy as np
# Make 24 bytes of fake data
raw = np.arange(24, dtype=np.uint8)
#Convert
out = raw.view(np.uint16).reshape(3, 4)
print(out)
print(out.dtype)
output
[[ 256 770 1284 1798]
[2312 2826 3340 3854]
[4368 4882 5396 5910]]
uint16
Thanks to Andras Deak for his assistance!
If the resulting image doesn't look correct, you may need to swap the byte ordering of the 16 bit integers. You can read about byte ordering in Numpy here.
And if that still doesn't look correct, then the data may be organized as two planes, with one plane for the low-order bits of a pixel and the other plane for the high-order bits. That's also easy to deal with, but hopefully it won't come to that. ;)

How to convert (or scale) a FITS image with Astropy

Using the Astropy library, I created a FITS image which is made by interpolation from 2 actual FITS images (they are scaled as "int16", the right format for the software I use : Maxim DL).
But the scale of this image is float64 and not int16. And any astronomical processing software can't read it (except FITS Liberator)
Do you have an idea how to proceed ? Can we convert a FITS image just by changing the "BITPIX" in the header ?
I tried: (following this method : Why is an image containing integer data being converted unexpectedly to floats?
from astropy.io import fits
hdu1=fits.open('mypicture.fit')
image=hdu1[0]
print(image.header['BITPIX']) # it gives : -64
image.scale('int16')
data=image.data
data.dtype
print(image.header['BITPIX']) # it gives : 16
hdu1.close()
However, when I check the newly-modified scale of "mypicture.fit", it still displays -64 !
No change was saved and applied!
If I understand your problem correctly, this should work.
from astropy.io import fits
import numpy as np
# create dummy fits file
a = np.array([[1,2,3],
[4,5,6],
[7,8,9]],dtype=np.float64)
hdu = fits.PrimaryHDU()
hdu.data = a
# looking at the header object confirms BITPIX = -64
hdu.header
# change data type
hdu.data = np.int16(hdu.data)
# look again to confirm BITPIX = 16
hdu.header

Categories