how to save an array representing an image with 40 band to a .tif file

前端 未结 3 738
名媛妹妹
名媛妹妹 2020-12-21 13:02

I have an array with 600×600×40 dimension that each band(from 40 band) represent a 600×600 image I want to save it to a multiple band .tif image. I have tried this functions

相关标签:
3条回答
  • 2020-12-21 13:40

    tifffile (https://pypi.org/project/tifffile/) supports multi-channel .tiff's and has an API similar to the one of scikit-image or OpenCV:

    In [1]: import numpy as np
    
    In [2]: import tifffile
    
    In [3]: # Channel dimension should come first
    
    In [4]: x = np.random.randint(0, 255, 4*100*100).reshape((4, 100, 100))
    
    In [5]: tifffile.imsave('test.tiff', x)
    
    In [6]: y = tifffile.imread('test.tiff')
    
    In [7]: np.all(np.equal(x, y))
    Out[7]: True
    
    0 讨论(0)
  • 2020-12-21 13:42

    Mark's clever answer is making a multi-page TIFF. Unfortunately, imagemagick and PIL are really MONO / RGB / RGBA / CMYK libraries and they don't have direct support for multiband images.

    pyvips has true multiband support. For example:

    import sys
    import pyvips
    import numpy as np
    
    # make a (100, 100, 40) numpy image
    array = np.zeros((100, 100, 40), dtype=sys.argv[2])
    
    # convert to vips and save
    image = numpy2vips(array)
    image.write_to_file(sys.argv[1])
    
    # read it back, convert to numpy, and show info
    image2 = pyvips.Image.new_from_file(sys.argv[1])
    array = vips2numpy(image2)
    
    print("shape =", array.shape)
    print("format =", array.dtype)
    

    I can run it like this:

    $ ./try284.py x.tif uint8
    shape = (100, 100, 40)
    format = uint8
    $ vipsheader x.tif
    x.tif: 100x100 uchar, 40 bands, srgb, tiffload
    $ identify x.tif
    x.tif TIFF 100x100 100x100+0+0 8-bit sRGB 400KB 0.000u 0:00.000
    

    It supports other dtypes as well:

    $ ./try284.py x.tif uint32
    shape = (100, 100, 40)
    format = uint32
    $ ./try284.py x.tif float32
    shape = (100, 100, 40)
    format = float32
    

    etc. etc.

    You can load these TIFFs in gdal. I guess gdal can be used to write them as well, though I've not tried. Annoyingly, it moves the 40 to the outermost dimension.

    $ python3
    Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
    [GCC 8.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from osgeo import gdal
    >>> x = gdal.Open("x.tif")
    >>> a = x.ReadAsArray()
    >>> a.shape
    (40, 100, 100)
    

    vips2numpy() and numpy2vips() are defined here:

    https://github.com/libvips/pyvips/blob/master/examples/pil-numpy-pyvips.py

    Copy-pasted for reference:

    # map vips formats to np dtypes
    format_to_dtype = {
        'uchar': np.uint8,
        'char': np.int8,
        'ushort': np.uint16,
        'short': np.int16,
        'uint': np.uint32,
        'int': np.int32,
        'float': np.float32,
        'double': np.float64,
        'complex': np.complex64,
        'dpcomplex': np.complex128,
    }
    
    # map np dtypes to vips
    dtype_to_format = {
        'uint8': 'uchar',
        'int8': 'char',
        'uint16': 'ushort',
        'int16': 'short',
        'uint32': 'uint',
        'int32': 'int',
        'float32': 'float',
        'float64': 'double',
        'complex64': 'complex',
        'complex128': 'dpcomplex',
    }
    
    # numpy array to vips image
    def numpy2vips(a):
        height, width, bands = a.shape
        linear = a.reshape(width * height * bands)
        vi = pyvips.Image.new_from_memory(linear.data, width, height, bands,
                                          dtype_to_format[str(a.dtype)])
        return vi
    
    # vips image to numpy array
    def vips2numpy(vi):
        return np.ndarray(buffer=vi.write_to_memory(),
                          dtype=format_to_dtype[vi.format],
        shape=[vi.height, vi.width, vi.bands])
    
    0 讨论(0)
  • 2020-12-21 13:43

    You could save multiple images, each representing a single band (greyscale), or even multiple bands (colour) in a single TIFF file with PIL/Pillow like this:

    from PIL import Image
    # Synthesize 8 dummy images, all greyscale, all same size but with varying brightness
    size=(480,640)  
    b1 = Image.new('L', size, color=10)                                                         
    b2 = Image.new('L', size, color=20)                                                        
    b3 = Image.new('L', size, color=30)                                                       
    b4 = Image.new('L', size, color=40)                                                        
    b5 = Image.new('L', size, color=50)                                                        
    b6 = Image.new('L', size, color=60)                                                        
    b7 = Image.new('L', size, color=70)                                                        
    b8 = Image.new('L', size, color=80)                                                        
    
    # Save all 8 to single TIFF file
    b1.save('multi.tif', save_all=True, append_images=[b2,b3,b4,b5,b6,b7,b8]) 
    

    If you now examine that file with ImageMagick at the command line, you can see all 8 bands are present:

    magick identify multi.tif 
    multi.tif[0] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[1] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[2] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[3] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[4] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[5] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[6] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    multi.tif[7] TIFF 480x640 480x640+0+0 8-bit Grayscale Gray 2.34473MiB 0.000u 0:00.000
    

    In case you are using OpenCV or Numpy arrays for your processing, you can make an OpenCV or Numpy array into a PIL/Pillow image with:

    PILimage = Image.fromarray(numpyImage)
    

    and, going the other way, from a PIL/Pillow image to Numpy array:

    NumpyImage = np.array(PILimage)
    

    If you then want to read them back, you can do this:

    # Open the multi image
    im = Image.open('multi.tif')                                                               
    
    # Iterate through frames
    for frame in ImageSequence.Iterator(im):  
        frame.show() 
    


    If you want to move to a specific band, you can seek like this:

    im = Image.open('multi.tif')                                                               
    
    im.seek(3) 
    im.show()
    

    You can also extract band3 from the TIF and save as a PNG with ImageMagick at the command line with:

    magick multi.tif[3] band3.png
    

    Or make a band 1, 2, 7 RGB composite with:

    magick multi.tif[1] multi.tif[2] multi.tif[7] -colorspace RGB -combine 127rgb.png
    

    which will look dark blue because the red and the green channels are very low and only the blue channel has a large-ish value.


    I am not the world's best on Python, so am uncertain of any implications/errors, but I think if you have a 600x600x40 numpy array of images, you can do what I am suggesting like this:

    # Synthesize dummy array of 40 images, each 600x600
    nparr = np.random.randint(0,256,(600,600,40), dtype=np.uint8)
    
    # Make PIL/Pillow image of first
    a = Image.fromarray(nparr[:,:,0])
    
    # Save whole lot in one TIF
    a.save('multi.tif', save_all=True, append_images=[Image.fromarray(nparr[:,:,x]) for x in range(1,40)]) 
    

    Keywords: Multi-band, multi band, multi-spectral, multi spectral, satellite image, imagery, image processing, Python, Numpy, PIL, Pillow, TIFF, TIF, NDVI

    0 讨论(0)
提交回复
热议问题