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