Search code examples
pythonimagepngtiff

How to combine 3 TIFF images into 1 PNG image with python?


I have 1 tif image for each RGB colour channel, and I would like to combine the 3 images to make a single RGB image with all 3 channels in png format using python. I have tried several experiments using the PIL library but I can't get it.

I uploaded 3 sample images to Google Drive here. Does anyone know how to do this?


Solution

  • The answer depends on what you are really trying to achieve...

    If you want an accurate merge of the 3 channels, you should probably use the tifffile module to understand the floating point values in your input files and accurately represent them in your output files. In fact, gdal would probably be even better as it understands the GeoTIFF tags in your file. PIL is unable to handle RGB float32 images.

    If you want something that vaguely allows some sort of approximate visualisation as a PNG, you will need to do some work to scale your values to something sensible (but not accurate) because PNG cannot represent float data like your images contain.


    Here is a more accurate merge of your channels with tifffile:

    from tifffile import imread, imwrite
    import numpy as np
    
    r = imread('r.tif')
    g = imread('g.tif')
    b = imread('b.tif')
    RGB = np.dstack((r,g,b))
    imwrite('result.tif', RGB)
    

    With PIL you would use Image.merge() but your data is float, so you will need to convert it to uint8/uint16 first to get something you can store in a PNG:

    from PIL import Image
    import numpy as np
    
    # Open images
    red   = Image.open('red_channel.tif')
    green = Image.open('green_channel.tif')
    blue  = Image.open('blue_channel.tif')
    
    # Convert PIL Images to Numpy arrays
    npRed   = np.array(red)
    npGreen = np.array(green)
    npBlue  = np.array(blue)
    
    # Get rid of the pesky -3.4e+38 marker for out-of-bounds pixels
    npRed[npRed < 0]     = 0
    npBlue[npBlue < 0]   = 0
    npGreen[npGreen < 0] = 0
    
    # Find maximum across all channels for scaling
    max = np.max([npRed,npGreen,npBlue])
    
    # Scale all channels equally to range 0..255 to fit in a PNG (could use 65,535 and np.uint16 instead)
    R = (npRed * 255/max).astype(np.uint8)
    G = (npGreen * 255/max).astype(np.uint8)
    B = (npBlue * 255/max).astype(np.uint8)
    
    # Build a PNG
    RGB = np.dstack((R,G,B))
    Image.fromarray(RGB).save('result.png')
    

    enter image description here