What is an efficient and clear way to read 16-bit PGM images in Python with numpy?
I cannot use PIL to load 16-bit PGM images due to a PIL bug. I can read in the header with the following code:
dt = np.dtype([('type', 'a2'),
('space_0', 'a1', ),
('x', 'a3', ),
('space_1', 'a1', ),
('y', 'a3', ),
('space_2', 'a1', ),
('maxval', 'a5')])
header = np.fromfile( 'img.pgm', dtype=dt )
print header
This prints the correct data: ('P5', ' ', '640', ' ', '480', ' ', '65535') But I have a feeling that is not quite the best way. And beyond that, I'm having trouble how to figure out how to read in the following data of x by y (in this case 640x480) by 16-bit with the offset of size(header).
EDIT: IMAGE ADDED
MATLAB code to read and display the image is:
I = imread('foo.pgm');
imagesc(I);
And looks like this:
import re
import numpy
def read_pgm(filename, byteorder='>'):
"""Return image data from a raw PGM file as numpy array.
Format specification: http://netpbm.sourceforge.net/doc/pgm.html
"""
with open(filename, 'rb') as f:
buffer = f.read()
try:
header, width, height, maxval = re.search(
b"(^P5\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups()
except AttributeError:
raise ValueError("Not a raw PGM file: '%s'" % filename)
return numpy.frombuffer(buffer,
dtype='u1' if int(maxval) < 256 else byteorder+'u2',
count=int(width)*int(height),
offset=len(header)
).reshape((int(height), int(width)))
if __name__ == "__main__":
from matplotlib import pyplot
image = read_pgm("foo.pgm", byteorder='<')
pyplot.imshow(image, pyplot.cm.gray)
pyplot.show()
I'm not terribly familar with the PGM format, but generally speaking you'd just use numpy.fromfile. fromfile will start at whatever position the file pointer you pass to it is at, so you can simply seek (or read) to the end of the header, and then use fromfile to read the rest in.
You'll need to use infile.readline() instead of next(infile).
import numpy as np
with open('foo.pgm', 'r') as infile:
header = infile.readline()
width, height, maxval = [int(item) for item in header.split()[1:]]
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
On a side note, the "foo.pgm" file you pointed to in your comment appears to specify the wrong number of rows in the header.
If you're going to be reading in a lot of files that potentially have that problem, you can just pad the array with zeros or truncate it, like this.
import numpy as np
with open('foo.pgm', 'r') as infile:
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
image = np.fromfile(infile, dtype=np.uint16)
if image.size < width * height:
pad = np.zeros(width * height - image.size, dtype=np.uint16)
image = np.hstack([image, pad])
if image.size > width * height:
image = image[:width * height]
image = image.reshape((height, width))
Indeed, the 'string' after the header is a binary in your file. I solved that below (found the following: ndarray: [2047 2047 2047 ..., 540 539 539]) but there is another problem: the file is not long enough; counts only 289872 numbers instead of 640*480...
I am terribly sorry for my exageration by making a class for it...
import numpy as np
import Image
class PGM(object):
def __init__(self, filepath):
with open(filepath) as f:
# suppose all header info in first line:
info = f.readline().split()
self.type = info[0]
self.width, self.height, self.maxval = [int(v) for v in info[1:]]
size = self.width * self.height
lines = f.readlines()
dt = [np.int8, np.int16][self.maxval > 255]
try:
# this will work if lines are integers separated by e.g. spaces
self.data = np.array([l.split() for l in lines], dtype=dt).T
except ValueError:
# data is binary
data = np.fromstring(lines[0], dtype=dt)
if data.size < size:
# this is the case for the 'db.tt/phaR587 (foo.pgm)'
#raise ValueError('data binary string probably uncomplete')
data = np.hstack((data, np.zeros(size-data.size)))
self.data = data[:size].reshape((self.width, self.height))
assert (self.width, self.height) == self.data.shape
assert self.maxval >= self.data.max()
self._img = None
def get_img(self):
if self._img is None:
# only executed once
size = (self.width, self.height)
mode = 'L'
data = self.data
self.img = Image.frombuffer(mode, size, data)
return self.img
Image = property(get_img)
mypgm = PGM('foo.pgm')
mypgm.Image
edit: great Idea from Joe Kington to fill image with zeros!
from here I understand that the header information can be separated by either spaces, carriage returns or others. If yours is separated by spaces (inform me if otherwise) you can do:
with open('img.pgm') as f:
lines = f.readlines()
data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T
your data is now an array in int16 format!
Suppose you are still interested in the header information, you can do:
class Header(object):
def __init__(self, type, width, height, maxval):
self.type = type
self.width = int(width)
self.height = int(height)
self.maxval = int(maxval)
h = Header(*lines[0].split()[:4])
so that you can check the image data against the read lines:
assert (h.width, h.height) == data.shape
assert h.maxval >= data.max()
Edit: with the image data being binary, the file has to be opened as 'rb' and read from after the header information:
import numpy as np
def as_array(filepath):
f = open(filepath, 'r')
w, h = size = tuple(int(v) for v in next(f).split()[1:3])
data_size = w * h * 2
f.seek(0, 2)
filesize = f.tell()
f.close()
i_header_end = filesize - (data_size)
f = open(filepath, 'rb')
f.seek(i_header_end)
buffer = f.read()
f.close()
# convert binary data to an array of the right shape
data = np.frombuffer(buffer, dtype=np.uint16).reshape((w, h))
return data
a = as_array('foo.pgm')
Thanks to the answer by #joe-kington for helping figure this out. The solution follows.
There is a little bit of extra work to not hard-code the known header length (17 bytes in
this case), but to determine it from the header. The PGM standard says that the header usually ends with a newline but can end with any whitespace. I think this code will break on a PGM that uses non-newline whitespace for the end-of-header delimeter. Header size in this case would be determined by the size of variables holding width, height, and maxsize, plus two bytes for 'P5', plus 4 bytes of whitespace.
Other cases where this might break are if the width or height are larger than an int (very big image). Or if the PGM is 8-bit rather than 16-bit (which can be determined from maxval, and possible width, height, and the filesize).
#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt
file='foo.pgm'
infile = open(file,'r')
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
infile.seek(len(header))
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
print width, height, maxval
plt.figimage(image)
Related
I'm trying to read a '.waltex' image, which is a 'walaber image'. It's basically just in raw image format. The problem is, it uses 'RGBA8888', 'RGBA4444', 'RGB565' and 'RGB5551' (all of which can be determined from the header), and I could not find a way to use these color specs in PIL.
I've tried doing this
from PIL import Image
with open('Carl.waltex', 'rb') as file:
rawdata = file.read()
image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
image.show()
I've tried all the 16-bit raw modes in the last input, and I couldn't find a single one that worked. To be clear, this file is specifically 'RGBA4444' with little endian, 8 bytes per pixel.
If you need the file, then I can link it.
Updated Answer
I have made some changes to my original code so that:
you can pass a filename to read as parameter
it parses the header and checks the magic string and derives the format (RGBA8888 or RGBA4444) and height and width automatically
it now handles RGBA8888 like your newly-shared sample image
So, it looks like this:
#!/usr/bin/env python3
import struct
import sys
import numpy as np
from PIL import Image
def loadWaltex(filename):
# Open input file in binary mode
with open(filename, 'rb') as fd:
# Read 16 byte header and extract metadata
# https://zenhax.com/viewtopic.php?t=14164
header = fd.read(16)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
if magic != b'WALT':
sys.exit(f'ERROR: {filename} does not start with "WALT" magic string')
# Check if fmt=0 (RGBA8888) or fmt=3 (RGBA4444)
if fmt == 0:
fmtdesc = "RGBA8888"
# Read remainder of file (part following header)
data = np.fromfile(fd, dtype=np.uint8)
R = data[0::4].reshape((h,w))
G = data[1::4].reshape((h,w))
B = data[2::4].reshape((h,w))
A = data[3::4].reshape((h,w))
# Stack the channels to make RGBA image
RGBA = np.dstack((R,G,B,A))
else:
fmtdesc = "RGBA4444"
# Read remainder of file (part following header)
data = np.fromfile(fd, dtype=np.uint16).reshape((h,w))
# Split the RGBA444 out from the uint16
R = (data>>12) & 0xf
G = (data>>8) & 0xf
B = (data>>4) & 0xf
A = data & 0xf
# Stack the channels to make RGBA image
RGBA = np.dstack((R,G,B,A)).astype(np.uint8) << 4
# Debug info for user
print(f'Filename: {filename}, version: {vers}, format: {fmtdesc} ({fmt}), w: {w}, h: {h}')
# Make into PIL Image
im = Image.fromarray(RGBA)
return im
if __name__ == "__main__":
# Load image specified by first parameter
im = loadWaltex(sys.argv[1])
im.save('result.png')
And when you run it with:
./decodeRGBA.py objects.waltex
You get:
The debug output for your two sample images is:
Filename: Carl.waltex, version: 1, format: RGBA4444 (3), w: 1024, h: 1024
Filename: objects.waltex, version: 1, format: RGBA8888 (0), w: 256, h: 1024
Original Answer
I find using Numpy is the easiest approach for this type of thing, and it is also highly performant:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Define the known parameters of the image and read into Numpy array
h, w, offset = 1024, 1024, 16
data = np.fromfile('Carl.waltex', dtype=np.uint16, offset=offset).reshape((h,w))
# Split the RGBA4444 out from the uint16
R = (data >> 12) & 0xf
G = (data >> 8) & 0xf
B = (data >> 4) & 0xf
A = data & 0xf
# Stack the 4 individual channels to make an RGBA image
RGBA = np.dstack((R,G,B,A)).astype(np.uint8) << 4
# Make into PIL Image
im = Image.fromarray(RGBA)
im.save('result.png')
Note: Your image has 16 bytes of padding at the start. Sometimes that amount is variable. A useful technique in that case is to read the entire file, work out how many useful samples of pixel data there are (in your case 1024*1024), and then slice the data to take the last N samples - thereby ignoring any variable padding at the start. That would look like this:
# Define the known parameters of the image and read into Numpy array
h, w = 1024, 1024
data = np.fromfile('Carl.waltex', dtype=np.uint16)[-h*w:].reshape((h,w))
If you don't like Numpy and prefer messing about with lists and structs, you can get exactly the same result like this:
#!/usr/bin/env python3
import struct
from PIL import Image
# Define the known parameters of the image
h, w, offset = 1024, 1024, 16
data = open('Carl.waltex', 'rb').read()[offset:]
# Unpack into bunch of h*w unsigned shorts
uint16s = struct.unpack("H" * h *w, data)
# Build a list of RGBA tuples
pixels = []
for RGBA4444 in uint16s:
R = (RGBA4444 >> 8) & 0xf0
G = (RGBA4444 >> 4) & 0xf0
B = RGBA4444 & 0xf0
A = ( RGBA4444 & 0xf) << 4
pixels.append((R,G,B,A))
# Push the list of RGBA tuples into an empty image
RGBA = Image.new('RGBA', (w,h))
RGBA.putdata(pixels)
RGBA.save('result.png')
Note that the Numpy approach is 60x faster than the list-based approach:
Numpy: 3.6 ms ± 73.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
listy: 213 ms ± 712 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
Note: These images and the waltex file format seem to be from the games "Where's My Water?" and "Where's My Perry?". I got some hints as to the header format from ZENHAX.
Now that I have a better idea of how your Waltex files work, I attempted to write a custom PIL Plugin for them - a new experience for me. I've put it as a different answer because the approach is very different.
You use it very simply like this:
from PIL import Image
import WaltexImagePlugin
im = Image.open('objects.waltex')
im.show()
You need to save the following as WaltexImagePlugin.py in the directory beside your main Python program:
from PIL import Image, ImageFile
import struct
def _accept(prefix):
return prefix[:4] == b"WALT"
class WaltexImageFile(ImageFile.ImageFile):
format = "Waltex"
format_description = "Waltex texture image"
def _open(self):
header = self.fp.read(HEADER_LENGTH)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
# size in pixels (width, height)
self._size = w, h
# mode setting
self.mode = 'RGBA'
# Decoder
if fmt == 0:
# RGBA8888
# Just use built-in raw decoder
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, (self.mode,
0, 1))]
elif fmt == 3:
# RGBA4444
# Use raw decoder with custom RGBA;4B unpacker
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, ('RGBA;4B',
0, 1))]
Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
Image.register_extensions(
WaltexImageFile.format,
[
".waltex"
],
)
HEADER_LENGTH = 16
It works perfectly for your RGBA888 images, but cannot quite handle the byte ordering of your RGBA444 file, so you need to reverse it for those images. I used this:
...
...
im = Image.open(...)
# Split channels and recombine in correct order
a, b, c, d = im.split()
im = Image.merge((c,d,a,b))
If anyone knows how to use something in the Unpack.c file to do this correctly, please ping me. Thank you.
I have a code for finding entropy info of an image by resizing the image and dividing it into its RGB channels.
import os
from PIL import Image
import numpy as np
from scipy.misc import imread
import cv2
import imageio
#读取RGB图像
def openRGB(image_path):
f = open(image_path,"rb")
data = f.read()
f.close()
data = [int(x) for x in data]
data = np.array(data).reshape((256*256, 3)).astype(np.uint8)
return data
def entropy(X):
n = len(X)
counts = np.bincount(X)
probs = counts[np.nonzero(counts)] / n
en = 0
for i in range(len(probs)):
en = en - probs[i] * np.log(probs[i])/np.log(2)
return en
def getEntropy(image_path):
data =openRGB(image_path)
data_B = data[:, 0]
data_G = data[:, 1]
data_R = data[:, 2]
B = entropy(data_B)
G = entropy(data_G)
R = entropy(data_R)
return (R+B+G)/2;
However, whenever I run the getentropy() function on a given image it keeps giving back this error
ValueError: cannot reshape array of size 37048 into shape (65536,3)
Any idea how I can reformat the image to fit that array shape?
There is a simple explanation: image_path length in bytes is only 37048.
When using np.array(data).reshape((256*256, 3)), the length of data must be 256*256*3 = 196608 bytes.
You are getting an exception because the lengths do not match.
It's simple to reproduce the problem.
Create an input sample file in size 196608 bytes, and there is no exception.
Create an input sample file in size 37048 bytes, get an exception.
Here is a code sample that reproduces the problem:
import os
#from PIL import Image
import numpy as np
#from scipy.misc import imread
#import cv2
#import imageio
def openRGB(image_path):
f = open(image_path, "rb")
data = f.read()
f.close()
data = [int(x) for x in data]
data = np.array(data).reshape((256*256, 3)).astype(np.uint8)
return data
def entropy(X):
n = len(X)
counts = np.bincount(X)
probs = counts[np.nonzero(counts)] / n
en = 0
for i in range(len(probs)):
en = en - probs[i] * np.log(probs[i])/np.log(2)
return en
def getEntropy(image_path):
data = openRGB(image_path)
data_B = data[:, 0]
data_G = data[:, 1]
data_R = data[:, 2]
B = entropy(data_B)
G = entropy(data_G)
R = entropy(data_R)
return (R+B+G)/2
# Create a binary file with random bytes
image_path = 'tmp.bin'
# When n_bytes=196608, there is no exception.
################################################################################
n_bytes = 256*256*3
tmp = np.random.randint(0, 255, n_bytes, np.uint8) # Build random array of n_bytes bytes
with open(image_path, 'wb') as f:
f.write(tmp) # Write tmp to a binary file
file_size_in_bytes = os.path.getsize(image_path)
print('file_size_in_bytes = ' + str(file_size_in_bytes))
res = getEntropy(image_path)
print(res)
################################################################################
# When n_bytes=37048, an exception is raised: ValueError: cannot reshape array of size 37048 into shape (65536,3)
################################################################################
n_bytes = 37048
tmp = np.random.randint(0, 255, n_bytes, np.uint8) # Build random array of n_bytes bytes
with open(image_path, 'wb') as f:
f.write(tmp) # Write tmp to a binary file
file_size_in_bytes = os.path.getsize(image_path)
print('file_size_in_bytes = ' + str(file_size_in_bytes))
res = getEntropy(image_path)
print(res)
################################################################################
Why are you reading 37048 bytes instead of 196608 bytes?
image_path is a JPEG image file, and you are reading the image using f = open(image_path, "rb") and data = f.read().
You may read and reshape the image as follows:
import cv2
def openRGB(image_path):
# For example: image_path = 'img.jpg'
img = cv2.imread(image_path) # Read image in BGR color format.
data = np.array(img).reshape(img.shape[0]*img.shape[1], 3) # Reshape to rows x cols x 3 (Blue in data[:, 0], green in data[:, 1], red in data[:, 2]).
return data
In the above example I used img.shape[0]*img.shape[1], the image resolution is:
height = img.shape[0]
width = img.shape[1]
I would like to write a data converter tool. I need analyze the bitstream in a file to display the 2D cross-sections of a 3D volume.
The dataset I am trying to view can be found here: https://figshare.com/articles/SSOCT_test_dataset_for_OCTproZ/12356705.
It's the file titled: burned_wood_with_tape_1664x512x256_12bit.raw (832 MB)
Would extremely appreciate some direction. Willing to award a bounty if I could get some software to display the dataset as images using a data conversion.
As I'm totally new to this concept, I don't have code to show for this problem. However, here's a little something I tried using inspiration from other questions on SO:
import rawpy
import imageio
path = "Datasets/burned_wood_with_tape_1664x512x256_12bit.raw"
for item in path:
item_path = path + item
raw = rawpy.imread(item_path)
rgb = raw.postprocess()
rawpy.imshow(rgb)
Down below I implemented next visualization.
Example RAW file burned_wood_with_tape_1664x512x256_12bit.raw consists of 1664 samples per A-scan, 512 A-scans per B-scan, 16 B-scans per buffer, 16 buffers per volume, and 2 volumes in this file, each sample is encoded as 2-bytes unsigned integer in little endian order, only 12 higher bits are used, lower 4 bits contain zeros. Samples are centered approximately around 2^15, to be precise data has these stats min 0 max 47648 mean 32757 standard deviation 454.5.
I draw gray images of size 1664 x 512, there are total 16 * 16 * 2 = 512 such images (frames) in a file. I draw animated frames on screen using matplotlib library, also rendering these animation into GIF file. One example of rendered GIF at reduced quality is located after code.
To render/draw images of different resulting resolution you need to change code line with plt.rcParams['figure.figsize'], this fig size contains (widht_in_inches, height_in_inches), by default DPI (dots per inch) equals to 100, meaning that if you want to have resulting GIF of resolution 720x265 then you need to set this figure size to (7.2, 2.65). Also resulting GIF contains animation of a bit smaller resolution because axes and padding is included into resulting figure size.
My next code needs pip modules to be installed one time by command python -m pip install numpy matplotlib.
Try it online!
# Needs: python -m pip install numpy matplotlib
def oct_show(file, *, begin = 0, end = None):
import os, numpy as np, matplotlib, matplotlib.pyplot as plt, matplotlib.animation
plt.rcParams['figure.figsize'] = (7.2, 2.65) # (4.8, 1.75) (7.2, 2.65) (9.6, 3.5)
sizeX, sizeY, cnt, bits = 1664, 512, 16 * 16 * 2, 12
stepX, stepY = 16, 8
fps = 5
try:
fsize, opened_here = None, False
if type(file) is str:
fsize = os.path.getsize(file)
file, opened_here = open(file, 'rb'), True
by = (bits + 7) // 8
if end is None and fsize is not None:
end = fsize // (sizeX * sizeY * by)
imgs = []
file.seek(begin * sizeY * sizeX * by)
a = file.read((end - begin) * sizeY * sizeX * by)
a = np.frombuffer(a, dtype = np.uint16)
a = a.reshape(end - begin, sizeY, sizeX)
amin, amax, amean, stdd = np.amin(a), np.amax(a), np.mean(a), np.std(a)
print('min', amin, 'max', amax, 'mean', round(amean, 1), 'std_dev', round(stdd, 3))
a = (a.astype(np.float32) - amean) / stdd
a = np.maximum(0.1, np.minimum(a * 128 + 128.5, 255.1)).astype(np.uint8)
a = a[:, :, :, None].repeat(3, axis = -1)
fig, ax = plt.subplots()
plt.subplots_adjust(left = 0.08, right = 0.99, bottom = 0.06, top = 0.97)
for i in range(a.shape[0]):
title = ax.text(
0.5, 1.02, f'Frame {i}',
size = plt.rcParams['axes.titlesize'],
ha = 'center', transform = ax.transAxes,
)
imgs.append([ax.imshow(a[i], interpolation = 'antialiased'), title])
ani = matplotlib.animation.ArtistAnimation(plt.gcf(), imgs, interval = 1000 // fps)
print('Saving animated frames to GIF...', flush = True)
ani.save(file.name + '.gif', writer = 'imagemagick', fps = fps)
print('Showing animated frames on screen...', flush = True)
plt.show()
finally:
if opened_here:
file.close()
oct_show('burned_wood_with_tape_1664x512x256_12bit.raw')
Example output GIF:
I don't think it's a valid RAW file at all.
If you try this code:
import rawpy
import imageio
path = 'Datasets/burned_wood_with_tape_1664x512x256_12bit.raw'
raw = rawpy.imread(path)
rgb = raw.postprocess()
You will get a following error:
----> 5 raw = rawpy.imread(path)
6 rgb = raw.postprocess()
~\Anaconda3\envs\py37tf2gpu\lib\site-packages\rawpy\__init__.py in imread(pathOrFile)
18 d.open_buffer(pathOrFile)
19 else:
---> 20 d.open_file(pathOrFile)
21 return d
rawpy\_rawpy.pyx in rawpy._rawpy.RawPy.open_file()
rawpy\_rawpy.pyx in rawpy._rawpy.RawPy.handle_error()
LibRawFileUnsupportedError: b'Unsupported file format or not RAW file'
I want to read pfm format images in python. I tried with imageio.read but it is throwing an error. Can I have any suggestion, please?
img = imageio.imread('image.pfm')
The following Python 3 implementation will decode .pfm files.
Download the example memorial.pfm from Paul Devebec's page.
from pathlib import Path
import numpy as np
import struct
def read_pfm(filename):
with Path(filename).open('rb') as pfm_file:
line1, line2, line3 = (pfm_file.readline().decode('latin-1').strip() for _ in range(3))
assert line1 in ('PF', 'Pf')
channels = 3 if "PF" in line1 else 1
width, height = (int(s) for s in line2.split())
scale_endianess = float(line3)
bigendian = scale_endianess > 0
scale = abs(scale_endianess)
buffer = pfm_file.read()
samples = width * height * channels
assert len(buffer) == samples * 4
fmt = f'{"<>"[bigendian]}{samples}f'
decoded = struct.unpack(fmt, buffer)
shape = (height, width, 3) if channels == 3 else (height, width)
return np.flipud(np.reshape(decoded, shape)) * scale
import matplotlib.pyplot as plt
image = read_pfm('memorial.pfm')
plt.imshow(image)
plt.show()
I am not at all familiar with Python, but here are a few suggestions on reading a PFM (Portable Float Map) file.
Option 1
The ImageIO documentation here suggests there is a FreeImage reader you can download and use.
Option 2
I pieced together a simple reader myself below that seems to work fine on a few sample images I found around the 'net and generated with ImageMagick. It may contain inefficiencies or bad practices because I do not speak Python.
#!/usr/local/bin/python3
import sys
import re
from struct import *
# Enable/disable debug output
debug = True
with open("image.pfm","rb") as f:
# Line 1: PF=>RGB (3 channels), Pf=>Greyscale (1 channel)
type=f.readline().decode('latin-1')
if "PF" in type:
channels=3
elif "Pf" in type:
channels=1
else:
print("ERROR: Not a valid PFM file",file=sys.stderr)
sys.exit(1)
if(debug):
print("DEBUG: channels={0}".format(channels))
# Line 2: width height
line=f.readline().decode('latin-1')
width,height=re.findall('\d+',line)
width=int(width)
height=int(height)
if(debug):
print("DEBUG: width={0}, height={1}".format(width,height))
# Line 3: +ve number means big endian, negative means little endian
line=f.readline().decode('latin-1')
BigEndian=True
if "-" in line:
BigEndian=False
if(debug):
print("DEBUG: BigEndian={0}".format(BigEndian))
# Slurp all binary data
samples = width*height*channels;
buffer = f.read(samples*4)
# Unpack floats with appropriate endianness
if BigEndian:
fmt=">"
else:
fmt="<"
fmt= fmt + str(samples) + "f"
img = unpack(fmt,buffer)
Option 3
If you cannot read your PFM files in Python, you could convert them at the command line using ImageMagick to another format, such as TIFF, that can store floating point samples. ImageMagick is installed on most Linux distros and is available for macOS and Windows:
magick input.pfm output.tif
I want to use SimpleITK or wedpy to convert the 3d images into 2d images.
Or i want to get a three-dimensional matrix, and then i divide the three-dimensional matrix into some two-dimensional matrices.
import SimpleITK as ITK
import numpy as np
#from medpy.io import load
url=r'G:\path\to\my.mha'
image = ITK.ReadImage(url)
frame_num, width, height = image_array.shape
print(frame_num,width,height)
Then only get it:155 240 240
but i want to get [[1,5,2,3,1...],[54,1,3,5...],[5,8,9,6....]]
Just to add to Dave Chen's answer, as it is unclear if you want to get a set of 2D SimpleITK images or numpy arrays. The following code covers all three available options:
import SimpleITK as sitk
import numpy as np
url = "my_file.mha"
image = sitk.ReadImage(url)
max_index = image.GetDepth() # or image.GetWidth() or image.GetHeight() depending on the axis along which you want to extract
# As list of 2D SimpleITK images
list_of_2D_images = [image[:,:,i] for i in range(max_index)]
# As list of 2D numpy arrays which cannot be modified (no data copied)
list_of_2D_images_np_view = [sitk.GetArrayViewFromImage(image[:,:,i]) for i in range(max_index)]
# As list of 2D numpy arrays (data copied to numpy array)
list_of_2D_images_np = [sitk.GetArrayFromImage(image[:,:,i]) for i in range(max_index)]
Also, if you really want to work with URLs and not local files I would suggest looking at the remote download approach used in the SimpleITK notebooks repository, the relevant file is downloaddata.py.
That's not a big deal.
CT images have originally all numbers in int16 type so you don't need to handle float numbers.. In this case, we can think that we can easily change from int16 to uint16 only removing negative values in the image (CT images have some negative numbers as pixel values). Note that we really need uint16, or uint8 type so that OpenCV can handle it... as we have a lot of values in the CT image array, the best choice is uint16, so that we don't lose too much precision.
Ok, now you just need to do as follows:
import SimpleITK as sitk
import numpy as np
import cv2
mha = sitk.ReadImage('/mha/directory') #Importing mha file
array = sitk.GetArrayFromImage(mha) #Converting to array int16 (default)
#Translating each slice to the positive side
for m in range(array.shape[0]):
array[m] = array[m] + abs(np.min(array[m]))
array = np.around(array, decimals=0) #remove any float numbers if exists.. probably not
array = np.asarray(array, dtype='uint16') #From int16 to uint16
After these steps the array is just ready to be saved as png images using opencv.imwrite module:
for image in array:
cv2.imwrite('/dir/to/save/'+'name_image.png', image)
Note that by default SimpleITK handles .mha files by the axial view. I really don't know how to change it because I've never needed it before. Anyway, in this case with some searches you can find something.
I'm not sure exactly what you want to get. But it's easy to extract a 2d slice from a 3d image in SimpleITK.
To get a Z slice where Z=100 you can do this:
zslice = image[100]
To get a Y slice for Y=100:
yslice = image[:, 100]
And a X slice for X=100:
xslice = image[:, :, 100]
#zivy#Dave Chen
I've solved my problem.In fact, running this code will give you 150 240*240 PNG pictures.It's i want to get.
# -*- coding:utf-8 -*-
import numpy as np
import subprocess
import random
import progressbar
from glob import glob
from skimage import io
np.random.seed(5) # for reproducibility
progress = progressbar.ProgressBar(widgets=[progressbar.Bar('*', '[', ']'), progressbar.Percentage(), ' '])
class BrainPipeline(object):
'''
A class for processing brain scans for one patient
INPUT: (1) filepath 'path': path to directory of one patient. Contains following mha files:
flair, t1, t1c, t2, ground truth (gt)
(2) bool 'n4itk': True to use n4itk normed t1 scans (defaults to True)
(3) bool 'n4itk_apply': True to apply and save n4itk filter to t1 and t1c scans for given patient. This will only work if the
'''
def __init__(self, path, n4itk = True, n4itk_apply = False):
self.path = path
self.n4itk = n4itk
self.n4itk_apply = n4itk_apply
self.modes = ['flair', 't1', 't1c', 't2', 'gt']
# slices=[[flair x 155], [t1], [t1c], [t2], [gt]], 155 per modality
self.slices_by_mode, n = self.read_scans()
# [ [slice1 x 5], [slice2 x 5], ..., [slice155 x 5]]
self.slices_by_slice = n
self.normed_slices = self.norm_slices()
def read_scans(self):
'''
goes into each modality in patient directory and loads individual scans.
transforms scans of same slice into strip of 5 images
'''
print('Loading scans...')
slices_by_mode = np.zeros((5, 155, 240, 240))
slices_by_slice = np.zeros((155, 5, 240, 240))
flair = glob(self.path + '/*Flair*/*.mha')
t2 = glob(self.path + '/*_T2*/*.mha')
gt = glob(self.path + '/*more*/*.mha')
t1s = glob(self.path + '/**/*T1*.mha')
t1_n4 = glob(self.path + '/*T1*/*_n.mha')
t1 = [scan for scan in t1s if scan not in t1_n4]
scans = [flair[0], t1[0], t1[1], t2[0], gt[0]] # directories to each image (5 total)
if self.n4itk_apply:
print('-> Applyling bias correction...')
for t1_path in t1:
self.n4itk_norm(t1_path) # normalize files
scans = [flair[0], t1_n4[0], t1_n4[1], t2[0], gt[0]]
elif self.n4itk:
scans = [flair[0], t1_n4[0], t1_n4[1], t2[0], gt[0]]
for scan_idx in xrange(5):
# read each image directory, save to self.slices
slices_by_mode[scan_idx] = io.imread(scans[scan_idx], plugin='simpleitk').astype(float)
for mode_ix in xrange(slices_by_mode.shape[0]): # modes 1 thru 5
for slice_ix in xrange(slices_by_mode.shape[1]): # slices 1 thru 155
slices_by_slice[slice_ix][mode_ix] = slices_by_mode[mode_ix][slice_ix] # reshape by slice
return slices_by_mode, slices_by_slice
def norm_slices(self):
'''
normalizes each slice in self.slices_by_slice, excluding gt
subtracts mean and div by std dev for each slice
clips top and bottom one percent of pixel intensities
if n4itk == True, will apply n4itk bias correction to T1 and T1c images
'''
print('Normalizing slices...')
normed_slices = np.zeros((155, 5, 240, 240))
for slice_ix in xrange(155):
normed_slices[slice_ix][-1] = self.slices_by_slice[slice_ix][-1]
for mode_ix in xrange(4):
normed_slices[slice_ix][mode_ix] = self._normalize(self.slices_by_slice[slice_ix][mode_ix])
print('Done.')
return normed_slices
def _normalize(self, slice):
'''
INPUT: (1) a single slice of any given modality (excluding gt)
(2) index of modality assoc with slice (0=flair, 1=t1, 2=t1c, 3=t2)
OUTPUT: normalized slice
'''
b, t = np.percentile(slice, (0.5,99.5))
slice = np.clip(slice, b, t)
if np.std(slice) == 0:
return slice
else:
return (slice - np.mean(slice)) / np.std(slice)
def save_patient(self, reg_norm_n4, patient_num):
'''
INPUT: (1) int 'patient_num': unique identifier for each patient
(2) string 'reg_norm_n4': 'reg' for original images, 'norm' normalized images, 'n4' for n4 normalized images
OUTPUT: saves png in Norm_PNG directory for normed, Training_PNG for reg
'''
print('Saving scans for patient {}...'.format(patient_num))
progress.currval = 0
if reg_norm_n4 == 'norm': #saved normed slices
for slice_ix in progress(xrange(155)): # reshape to strip
strip = self.normed_slices[slice_ix].reshape(1200, 240)
if np.max(strip) != 0: # set values < 1
strip /= np.max(strip)
if np.min(strip) <= -1: # set values > -1
strip /= abs(np.min(strip))
# save as patient_slice.png
io.imsave('Norm_PNG/{}_{}.png'.format(patient_num, slice_ix), strip)
elif reg_norm_n4 == 'reg':
for slice_ix in progress(xrange(155)):
strip = self.slices_by_slice[slice_ix].reshape(1200, 240)
if np.max(strip) != 0:
strip /= np.max(strip)
io.imsave('Training_PNG/{}_{}.png'.format(patient_num, slice_ix), strip)
else:
for slice_ix in progress(xrange(155)): # reshape to strip
strip = self.normed_slices[slice_ix].reshape(1200, 240)
if np.max(strip) != 0: # set values < 1
strip /= np.max(strip)
if np.min(strip) <= -1: # set values > -1
strip /= abs(np.min(strip))
# save as patient_slice.png
io.imsave('n4_PNG/{}_{}.png'.format(patient_num, slice_ix), strip)
def n4itk_norm(self, path, n_dims=3, n_iters='[20,20,10,5]'):
'''
INPUT: (1) filepath 'path': path to mha T1 or T1c file
(2) directory 'parent_dir': parent directory to mha file
OUTPUT: writes n4itk normalized image to parent_dir under orig_filename_n.mha
'''
output_fn = path[:-4] + '_n.mha'
# run n4_bias_correction.py path n_dim n_iters output_fn
subprocess.call('python n4_bias_correction.py ' + path + ' ' + str(n_dims) + ' ' + n_iters + ' ' + output_fn, shell = True)
def save_patient_slices(patients, type):
'''
INPUT (1) list 'patients': paths to any directories of patients to save. for example- glob("Training/HGG/**")
(2) string 'type': options = reg (non-normalized), norm (normalized, but no bias correction), n4 (bias corrected and normalized)
saves strips of patient slices to approriate directory (Training_PNG/, Norm_PNG/ or n4_PNG/) as patient-num_slice-num
'''
for patient_num, path in enumerate(patients):
a = BrainPipeline(path)
a.save_patient(type, patient_num)
def s3_dump(directory, bucket):
'''
dump files from a given directory to an s3 bucket
INPUT (1) string 'directory': directory containing files to save
(2) string 'bucket': name od s3 bucket to dump files
'''
subprocess.call('aws s3 cp' + ' ' + directory + ' ' + 's3://' + bucket + ' ' + '--recursive')
def save_labels(fns):
'''
INPUT list 'fns': filepaths to all labels
'''
progress.currval = 0
for label_idx in progress(range(len(labels))):
slices = io.imread(labels[label_idx], plugin = 'simpleitk')
for slice_idx in range(len(slices)):
io.imsave(r'{}_{}L.png'.format(label_idx, slice_idx), slices[slice_idx])
if __name__ == '__main__':
url = r'G:\work\deeplearning\BRATS2015_Training\HGG\brats_2013_pat0005_1\VSD.Brain.XX.O.MR_T1.54537\VSD.Brain.XX.O.MR_T1.54537.mha'
labels = glob(url)
save_labels(labels)
# patients = glob('Training/HGG/**')
# save_patient_slices(patients, 'reg')
# save_patient_slices(patients, 'norm')
# save_patient_slices(patients, 'n4')
# s3_dump('Graveyard/Training_PNG/', 'orig-training-png')