How to Save a Numpy Array as a 16-Bit Image Using "Normal" (Enthought) Python

Can I save a numpy array as a 16-bit image using "normal" (Enthought) python?

One alternative is to use pypng. You'll still have to install another package, but it is pure Python, so that should be easy. (There is actually a Cython file in the pypng source, but its use is optional.)

Here's an example of using pypng to write numpy arrays to PNG:

import png

import numpy as np

# The following import is just for creating an interesting array
# of data. It is not necessary for writing a PNG file with PyPNG.
from scipy.ndimage import gaussian_filter


# Make an image in a numpy array for this demonstration.
nrows = 240
ncols = 320
np.random.seed(12345)
x = np.random.randn(nrows, ncols, 3)

# y is our floating point demonstration data.
y = gaussian_filter(x, (16, 16, 0))

# Convert y to 16 bit unsigned integers.
z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)

# Use pypng to write z as a color PNG.
with open('foo_color.png', 'wb') as f:
writer = png.Writer(width=z.shape[1], height=z.shape[0], bitdepth=16)
# Convert z to the Python list of lists expected by
# the png writer.
z2list = z.reshape(-1, z.shape[1]*z.shape[2]).tolist()
writer.write(f, z2list)

# Here's a grayscale example.
zgray = z[:, :, 0]

# Use pypng to write zgray as a grayscale PNG.
with open('foo_gray.png', 'wb') as f:
writer = png.Writer(width=z.shape[1], height=z.shape[0], bitdepth=16, greyscale=True)
zgray2list = zgray.tolist()
writer.write(f, zgray2list)

Here's the color output:

foo_color.png

and here's the grayscale output:

foo_gray.png


Update: I created a library called numpngw (available on PyPI and github) that provides a function for writing a numpy array to a PNG file. The repository has a setup.py file for installing it as a package, but the essential code is in a single file, numpngw.py, that could be copied to any convenient location. The only dependency of numpngw is numpy.

Here's a script that generates the same 16 bit images as those shown above:

import numpy as np
import numpngw

# The following import is just for creating an interesting array
# of data. It is not necessary for writing a PNG file.
from scipy.ndimage import gaussian_filter


# Make an image in a numpy array for this demonstration.
nrows = 240
ncols = 320
np.random.seed(12345)
x = np.random.randn(nrows, ncols, 3)

# y is our floating point demonstration data.
y = gaussian_filter(x, (16, 16, 0))

# Convert y to 16 bit unsigned integers.
z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)

# Use numpngw to write z as a color PNG.
numpngw.write_png('foo_color.png', z)

# Here's a grayscale example.
zgray = z[:, :, 0]

# Use numpngw to write zgray as a grayscale PNG.
numpngw.write_png('foo_gray.png', zgray)

Save numpy array as image with high precision (16 bits) with scikit-image

You wanna use the freeimage library to do so:

import numpy as np
from skimage import io, exposure, img_as_uint, img_as_float

io.use_plugin('freeimage')

im = np.array([[1., 2.], [3., 4.]], dtype='float64')
im = exposure.rescale_intensity(im, out_range='float')
im = img_as_uint(im)

io.imsave('test_16bit.png', im)
im2 = io.imread('test_16bit.png')

Result:

[[    0 21845]
[43690 65535]]

As for 3D arrays, you need to construct the array properly and then it'll work:

# im = np.array([[1, 2.], [3., 4.]], dtype='float64')
im = np.linspace(0, 1., 300).reshape(10, 10, 3)
im = exposure.rescale_intensity(im, out_range='float')
im = img_as_uint(im)

io.imsave('test_16bit.png', im)
im2 = io.imread('test_16bit.png')

Note that the read image is flipped, so something like np.fliplr(np.flipud(im2)) will bring it to original shape.

How to Stack N number of 2D arrays as an image together

You can write a (22,5,3884) array of float32 to a TIFF like this:

import tifffile
import numpy as np

# Synthesize image
signals = np.zeros((22,5,3844), dtype=np.float32)

# Save as TIFF
tifffile.imwrite('signals.tif', signals)

If you check the result with tiffinfo:

tiffinfo signals.tif

you can see there are 22 images in the file, each one a 3844x5 greyscale image of 32-bit IEEE-754 floating point.

Sample Output

TIFF Directory at offset 0x8 (8)
Image Width: 3844 Image Length: 5
Resolution: 1, 1 (unitless)
Bits/Sample: 32
Sample Format: IEEE floating point
Compression Scheme: None
Photometric Interpretation: min-is-black
Samples/Pixel: 1
Rows/Strip: 5
Planar Configuration: single image plane
ImageDescription: {"shape": [22, 5, 3844]}
Software: tifffile.py
TIFF Directory at offset 0x19d020 (1691680)
... abbreviated, but as above ...
TIFF Directory at offset 0x19d0d2 (1691858)
... abbreviated, but as above ...
TIFF Directory at offset 0x19d184 (1692036)
... abbreviated, but as above ...
TIFF Directory at offset 0x19d236 (1692214)
TIFF Directory at offset 0x19d2e8 (1692392)
TIFF Directory at offset 0x19d39a (1692570)
TIFF Directory at offset 0x19d44c (1692748)
TIFF Directory at offset 0x19d4fe (1692926)
TIFF Directory at offset 0x19d5b0 (1693104)
TIFF Directory at offset 0x19d662 (1693282)
TIFF Directory at offset 0x19d714 (1693460)
TIFF Directory at offset 0x19d7c6 (1693638)
TIFF Directory at offset 0x19d878 (1693816)
TIFF Directory at offset 0x19d92a (1693994)
TIFF Directory at offset 0x19d9dc (1694172)
TIFF Directory at offset 0x19da8e (1694350)
TIFF Directory at offset 0x19db40 (1694528)
TIFF Directory at offset 0x19dbf2 (1694706)
TIFF Directory at offset 0x19dca4 (1694884)
TIFF Directory at offset 0x19dd56 (1695062)
TIFF Directory at offset 0x19de08 (1695240)

If you want the signals stacked one-above-the-other vertically, you can reshape() like this:

tifffile.imwrite('signals.tif', signals.reshape(110,3844))

I made each of the 22 sub-images a solid grey that increases down the stack so you can see the image is dark at the top and light at the bottom:

Sample Image

Convert back to 2D numpy array from .jpg image

If your objective is to save a numpy array as an image, your approach have a problem. The function plt.savefig saves an image of the plot, not the array. Also transforming an array into an image may carry some precision loss (when converting from float64 or float32 to uint16). That been said, I suggest you use skimage and imageio:

import imageio
import numpy as np

from skimage import img_as_uint

data = np.load('0058_00086_brown_2_recording1.wav.npy')
print("original", data.shape)

img = img_as_uint(data)
imageio.imwrite('image.png', img)
load = imageio.imread('image.png')

print("image", load.shape)

This script loads the data you provided and prints the shape for verification

data = np.load('0058_00086_brown_2_recording1.wav.npy')
print("original", data.shape)

then it transform the data to uint, saves the image as png and loads it:

img = img_as_uint(data)
imageio.imwrite('image.png', img)
load = imageio.imread('image.png')

the output of the script is:

original (64, 25)
image (64, 25)

i.e. the image is loaded with the same shape that data. Some notes:

  • image.png is saved as a grayscale image
  • To save to .jpg just change to imageio.imwrite('image.jpg', img)
  • In the case of .png the absolute average distance from the original image was 3.890e-06 (this can be verified using np.abs(img_as_float(load) - data).sum() / data.size)

Information about skimage and imageio can be found in the respectives websites. More on saving numpy arrays as images can be found in the following answers: [1], [2], [3] and [4].

scipy: savefig without frames, axes, only content

EDIT

Changed aspect='normal to aspect='auto' since that changed in more recent versions of matplotlib (thanks to @Luke19).


Assuming :

import matplotlib.pyplot as plt

To make a figure without the frame :

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)

To make the content fill the whole figure

ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)

Then draw your image on it :

ax.imshow(your_image, aspect='auto')
fig.savefig(fname, dpi)

The aspect parameter changes the pixel size to make sure they fill the figure size specified in fig.set_size_inches(…). To get a feel of how to play with this sort of things, read through matplotlib's documentation, particularly on the subject of Axes, Axis and Artist.



Related Topics



Leave a reply



Submit