Loading multi-channel / multi-position folders of tif-files#

Some microscopes write image data to disc as tif-files slice-by slice. You find then many files named for example image_z03_ch01.tif in these folders. For loading these folders you typically need to know details such as how many z-slices and how many channels were imaged, or if the image data has multiple time-points or not.

See also this discussion.

from skimage import io
import matplotlib.pyplot as plt
import tifffile as tif
import numpy as np

import shutil
import os

path = "../../data/tif_folder/"

For demonstration purposes, we just create such a folder with dummy image data.

if not os.path.exists(path):
    os.mkdir(path)
for z in range(1,7):
    for c in range(1,5):
        #image = io.imread("c:/structure/data/blobs.tif")
        #io.imsave(f"c:/structure/data/images/r01c01f34p0{z}-ch0{c}t01.tiff", image)
        
        shutil.copy("../../data/blobs.tif",
                    path + f"image_z{str(z).zfill(2)}-ch{str(c).zfill(2)}.tiff")

To get an overview, we can print out the file names in the folder.

for file in os.listdir(path):
    print(file)
image_z01-ch01.tiff
image_z01-ch02.tiff
image_z01-ch03.tiff
image_z01-ch04.tiff
image_z02-ch01.tiff
image_z02-ch02.tiff
image_z02-ch03.tiff
image_z02-ch04.tiff
image_z03-ch01.tiff
image_z03-ch02.tiff
image_z03-ch03.tiff
image_z03-ch04.tiff
image_z04-ch01.tiff
image_z04-ch02.tiff
image_z04-ch03.tiff
image_z04-ch04.tiff
image_z05-ch01.tiff
image_z05-ch02.tiff
image_z05-ch03.tiff
image_z05-ch04.tiff
image_z06-ch01.tiff
image_z06-ch02.tiff
image_z06-ch03.tiff
image_z06-ch04.tiff

scikit-image offers a imread_collection for loading files matching to a pattern, e.g. containing *.

im_collection = io.imread_collection(path + "*")
im_collection
<skimage.io.collection.ImageCollection at 0x2244cd228e0>

You can turn this collection of images into an numpy-array-based image stack. Unfortunately, the number of z-slices and channels is unknown at this point.

image_stack = im_collection.concatenate()
image_stack.shape
(24, 254, 256)

If you know the number of z-slices and channels, you can reshape the image to a 3D+ch or 4D image.

num_channels = 4
num_z_slices = 6
image4d = np.reshape(image_stack, (num_channels, num_z_slices, image_stack.shape[-2], image_stack.shape[-1]))
image4d.shape
(4, 6, 254, 256)

Alternatively, you can also build your own for-loops for loading the data from disc. This gives you a bit more freedom, e.g. for sorting slices and channels into the dimensions used.

num_channels = 4
num_z_slices = 6

image4d_loaded = np.asarray([
    [io.imread(path + f"image_z{str(z).zfill(2)}-ch{str(c).zfill(2)}.tiff") for c in range(1, 5)]
    for z in range(1, 7)
])
image4d_loaded.shape
(6, 4, 254, 256)