Loading and preparing images

This is a tutorial on how to load images and make them ready for downstream analysis.

The images has to be .tiff or .tif files

The images can be either binary or greyscale. Therefore, different colours/channels should be split in different .tiff files. If they are greyscale they should be thresholded before further analysis (see section on thresholding below)

The .tiff files can contain multiple layers, or there can be a .tiff for each layer. Both are accepted (see details in loadIMG). Also, a .tiff can contain multiple images. As long as the channels are in different .tiff files.

Load package


Loading the images

Specify paths of images

path <- "C:/Images"

Convert tiff to R-friendly RDS files

loadIMG creates the RDS files, and when we run the script again we can just use findIMG to get the paths for the RDS files.

channels should be unique and exclusive names in the filenames of the images.

The following might give warnings about TIFF image tags that cannot be read. This should not be a problem.

img <- loadIMG(path, channels = c("C0","C1"))

Naming of images before loading

Naming the images properly is important before loading them into R.

The channels has to be unique strings in the names.

In general having unique strings for all treatments/timepoint/etc. makes subsequent analysis easier, as the functions can grap this information from the image names.

Good naming:

  • C1_Control_24hours.tif
  • C2_Control_24hours.tif
  • C1_Control_48hours.tif
  • C2_Control_48hours.tif
  • C1_Treat_24hours.tif
  • C2_Treat_24hours.tif
  • C1_Treat_48hours.tif
  • C2_Treat_48hours.tif

Bad Naming:

  • C1_C101.tif
  • C2_C102.tif
  • C1_C103.tif
  • C2_C104.tif
  • C1_C201.tif
  • C2_C202.tif
  • C1_C203.tif
  • C2_C204.tif

The above naming is bad, because the channel naming “C1” and “C2” (before the underscore) is part of the naming (after underscore), which will confuse the functions. Calling the channels “C1_” and “C2_” in loadIMG would fix the problem.

Check the loaded images

Check that the number of images are as expected:


If the images has been loaded previously, we can use findIMG to get the paths:

img <- findIMG(path)

We test the dimensions of the loaded images to ensure they are as expected:

lapply(img, function(x) dim(readRDS(x)))

img contains the paths for the RDS files, and this is the input for subsequent analyses.


If the images has not been thresholded yet, they should be before further analysis.

threshIMG applies either Otsu’s threshold, BEM (https://www.nature.com/articles/s41598-018-31012-5), or manual thresholds on the images. Use Otsu’s method if the intensity histogram is bimodal (two peaks), and use BEM if the intensity histogram peaks at zero.

The output of threshIMG are the paths for the thresholded images, which can be used for further analysis.

The default options in threshIMG is no threshold, so remember to choose a method!

imgT <- threshIMG(img, method = "Otsu")

You can use the mmand package to plot the output:

# The 1 means image number 1
# The 5 means layer number 5

We can run different algorithms on different channels by simply subsetting img:

# Splitting by channels
img.C0 <- img[grep("C0",img)]
img.C1 <- img[grep("C1",img)]

# Thresholding
img.C0T <- threshIMG(img.C0, method = "Otsu")
img.C1T <- threshIMG(img.C1, method = "BEM")

# Combining
img.T <- c(img.C0T,img.C1T)

Prepare images (optional)

If the channels do not correspond to the microbial strains we need to prepare the images before further analysis.

In this example channel C1 stains both strains. C0 correspond to only one of the strains (Xanthomonas). Xanthomonas is therefore the pixels where both C0 and C1 is detected. The other strain, Paenibacillus, is only the C1 pixels where C0 is not observed

Paenibacillus is C1 - C0

img.pan <- merge_channels(path, img, channels = c("C1","C0"), method = "subtract")

Xanthomonas is C0 AND C1

img.xan <- merge_channels(path, img, channels = c("C1","C0"), method = "intersect")

If they have already been created:

# img.pan <- img[grep("RCon3D.C1.C0.subtract",img)]
# img.xan <- img[grep("RCon3D.C1.C0.intersect",img)]

Check that the paths are correct:


Smoothing (optional)

We might want to smooth images before downstream analysis. So far only median smoothing is included

img.s <- smoothIMG(img, kern.smooth = c(3, 3, 3), type.smooth = "box", cores = 1)

type.smooth: Type of kernel for smooth “box” includes diagonals, “diamond” is without diagonals kern.smooth: Range of median smoothing in the x,y,z directions. Has to be odd integers. c(3,3,3) means immediate neighbours in all directions; imagine a box of size 3x3x3 centered on a focal pixel. The value of the focal pixel will then be the median of all 27 pixels in the box.

Morphing (optional)

We might want to morph images before downstream analysis. This is basically image manipulation, and should only be applied if there is good reason for it. See details in ?morphIMG and ?mmand::erode.

img.m <- morphIMG(imgs, morph = NULL, kern = c(3, 3, 3), type = "box", cores = 1)

The null channel (optional)

For some analyses we might want arrays corresponding to all the empty space in the images. This could for example be used to estimate the amount of empty space around a certain channel.

img_empty <- create_nulls(img, path, channels = c("C0","C1"))

img_empty then contains the paths for the RDS files corresponding to the empty space, or more precisely, the space note occupied by either “C0” or “C1”.