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