Home > Back-end >  How to prevent Pillow from converting uint16 to uint8 when changing pixel values in a tiff file?
How to prevent Pillow from converting uint16 to uint8 when changing pixel values in a tiff file?

Time:08-16

I have RGBA tiff files where I am trying to change specific pixel values of RGB to acceptable pixel values in a bunch of tiff files. Specifically I want to change R,G,B values of 0,0,0 to 5,5,5 and 255,255,255 to 250,250,250. The script below seems to do this, but in the process it changes the pixels from uint16 to uint8. This changes the quality and file size, which is unacceptable.

import PIL
from PIL import Image

with Image.open('dtmortho-000016_000052-14cm.tif') as im:
    img=im.load()
    [xs,ys]=im.size
    for x in range(0,xs):
        for y in range(0,ys):
            [r,g,b,a]=img[x,y]
    for x in range(0,xs):
        for y in range(0,ys):
            a = img[x,y]
            b = a[3]
            if a[0] == 0 and a[1] == 0 and a[2] == 0:
                im.putpixel((x,y),(5,5,5,b))
im.save('output.tif')

I have tried im.save('output.tif', quality='keep') to try and get it to save without data loss, but it consistently is half the file size and continues to save the bands as uint8 instead of uint16. Any help is appreciated.

CodePudding user response:

PIL/Pillow doesn't support multi-channel images with 16-bits/channel - see available modes.

Consider using tifffile, or wand, or wand or pyvips.

CodePudding user response:

pyvips can handle many band 16-bit images. You could code your problem like this:

#!/usr/bin/python3

import sys
import pyvips

image = pyvips.Image.new_from_file(sys.argv[1], access="sequential")

image = (image == 0).bandand().ifthenelse(5, image)

image.write_to_file(sys.argv[2])

The expression might be a little obscure ... image == 0 will test each band separately against 0, so an RGB pixel like [0, 255, 128], for example, would be come [255, 0, 0] (ie. [TRUE, FALSE, FALSE]).

You want pixels where all three bands are zero in one pixel, so you need to AND the bands together (bandand does this).

ifthenelse takes a condition image on the left (a one band TRUE/FALSE image in this case) and uses it to pick between a THEN image and an ELSE image. You want 5 if the mask is TRUE and the original image otherwise.

This sounds inefficient, but pyvips can execute this pretty quickly. With a 10,000 x 10,000 pixel, uncompressed 16-bit RGB TIFF I see:

$ time ./replace.py ~/pics/wtc.tif y.tif
real    0m0.996s
user    0m1.585s
sys 0m0.632s

About 1s, 100mb of ram, and a lot of disk traffic.

  • Related