2
votes

You can download a multi-channel, 16-bit png file from here (shown below). I have tried multiple Python packages for reading this multi-channel 16-bit-per-channel image. But none work, and if they do somehow they transform the images (scaling etc). I tried using imageio, PIL.Image, scipy.ndimage.imread and a couple more. It seems that they all can read single-channel 16-bit pngs properly but convert the multi-channel images into 8-bit-per-channel. For instance, this is a GitHub issue thst indicates imageio cannot read multi-channel 16-bit images. Another issue (here) for Pillow seems to say the same thing.

So I wonder, does anyone know how can I read multi-channel, 16-bit png files in Python without using OpenCV package properly? Feel free to offer solutions from other packages that I didn't mention anything about here.

enter image description here

2
Did my answer sort out your problem? If so, please consider accepting it as your answer - by clicking the hollow tick/checkmark beside the vote count. If not, please say what didn't work so that I, or someone else, can assist you further. Thanks. meta.stackexchange.com/questions/5234/… - Mark Setchell

2 Answers

4
votes

Option 1 - Split into channels with ImageMagick

You could use ImageMagick (it is installed on most Linux distros and is available for macOS and Windows) at the command line.

For example, this will separate your 16-bit 3-channel PNG into its constituent channels that you can then process individually in Pillow:

magick input.png -separate channel-$d.png

Now there are 3 separate channels:

-rw-r--r--   1 mark  staff     2276  1 Apr 16:47 channel-2.png
-rw-r--r--   1 mark  staff     3389  1 Apr 16:47 channel-1.png
-rw-r--r--   1 mark  staff     2277  1 Apr 16:47 channel-0.png

and they are each 16-bit, single channel images that Pillow can open:

magick identify channel-*

Sample Output

channel-0.png PNG 600x600 600x600+0+0 16-bit Grayscale Gray 2277B 0.000u 0:00.000
channel-1.png PNG 600x600 600x600+0+0 16-bit Grayscale Gray 3389B 0.000u 0:00.000
channel-2.png PNG 600x600 600x600+0+0 16-bit Grayscale Gray 2276B 0.000u 0:00.000

If you are using ImageMagick v6, replace magick with convert and replace magick identify with plain identify.


Option 2 - Split into channels with NetPBM

As an alternative to ImageMagick, you could use the much lighter weight NetPBM tools to do the same thing:

pngtopam < rainbow.png | pamchannel - 0 -tupletype GRAYSCALE > channel-0.pam
pngtopam < rainbow.png | pamchannel - 1 -tupletype GRAYSCALE > channel-1.pam
pngtopam < rainbow.png | pamchannel - 2 -tupletype GRAYSCALE > channel-2.pam

Pillow can then open the PAM files.


Option 3 - Use PyVips

As an alternative, you could use the extremely fast, memory-efficient pyvips to process your images. Here is an example from the documentation that:

  • crops 100 pixels off each side
  • shrinks an image by 10% with bilinear interpolation
  • sharpens with a convolution and re-saves the image.

Here is the code:

#!/usr/local/bin/python3
import sys
import pyvips

im = pyvips.Image.new_from_file(sys.argv[1], access='sequential')
im = im.crop(100, 100, im.width - 200, im.height - 200)
im = im.reduce(1.0 / 0.9, 1.0 / 0.9, kernel='linear')
mask = pyvips.Image.new_from_array([[-1, -1,  -1], 
                                    [-1,  16, -1], 
                                    [-1, -1,  -1]], scale=8)
im = im.conv(mask, precision='integer')
im.write_to_file("result.png")

The result is 16-bit like the input image:

identify result.png
result.png PNG 360x360 360x360+0+0 16-bit sRGB 2900B 0.000u 0:00.000

As you can see it is still 16-bit, and trimming 100px off each side results in 600px becoming 400px and then the 10% reduction makes that into 360px.


Option 4 - Convert to TIFF and use PyLibTiff

A fourth option, if the number of files is an issue, might be to convert your images to TIFF with ImageMagick

convert input.png output.tif

and they retain their 16-bit resolution, and you process them with PyLibTiff as shown here.


Option 5 - Multi-image TIFF processed as ImageSequence

A fifth option, could be to split your PNG files into their constituent channel and store them as a multi-image TIFF, i.e. with Red as the first image in the sequence, green as the second and blue as the third. This means there is no increase in he number of files and also you can store more than 3 channels per file - you mentioned 5 channels somewhere in your comments:

convert input.png -separate multiimage.tif

Check there are now 3 images, each 16-bit, but all in the same, single file:

identify multiimage.tif
multiimage.tif[0] TIFF 600x600 600x600+0+0 16-bit Grayscale Gray 10870B 0.000u 0:00.000
multiimage.tif[1] TIFF 600x600 600x600+0+0 16-bit Grayscale Gray 10870B 0.000u 0:00.000
multiimage.tif[2] TIFF 600x600 600x600+0+0 16-bit Grayscale Gray 10870B 0.000u 0:00.000

Then process them as an image sequence:

from PIL import Image, ImageSequence

im = Image.open("multiimage.tif")

index = 1
for frame in ImageSequence.Iterator(im):
    print(index)
    index = index + 1
1
votes

I had the same problem and I found out that imageio can do the job:

img = imageio.imread('path/to/img', format='PNG-FI')

With this option you can read and write multi-channel 16-bit png images (by default imageio uses PNG-PIL as format for reading png files). This works for png images, but changing the format can probably help when dealing with other image types (here a full list of available imageio formats).

To use this format you may need to install the FreeImage plugin as shown in the documentation.