<- Back to Spectre home page



Introduction


This tutorial introduces the user to manipulating and analysing IMC data using FlowJo, following segmentation and pre-processing using one of Spectre’s segmentation approaches. This provides a straightforward method for quantifying cellular expression and frequency data derived from segmented images.

Key preparation steps via Spectre

  1. Arcsinh transformation. To prepare the data for analysis, the raw values are subject to arcsinh transformation with a co-factor of 1. These channels are indicated by the "_asinh" that is appended to the end of the channel name. These can be plotted on a linear axis typically between 0 and ~5.

  2. Y-axis inversion. Programs such as HistoCat that typically deal with imaging data position x=0, y=0 in the top left corner. Programs that deal with numerical data, such as FlowJo, typically position x=0, y=0 in the bottom left corner. The result of this is that IMC images tend to be ‘flipped’ on the Y-axis. A key modification to the data within this R script is the addition of a second ‘Y-axis’ column, where the values are converted to negative. By doing this, FlowJo can render the image in the correct orientation.



1. Generate FCS files using R


Following the use of one of Spectre’s segmentation approaches, you can use an R script to convert TIFF files and masks into FCS files, for use in FlowJo. You can find the script here on the Spectre Github:

workflows/Spatial - FlowJo/Spectre TIFF to FCS/

Script and data

Organise your folders and data like this:

  1. Script:

  1. ROI TIFFs:

  1. Masks

  1. ROI metadata


Run script in R

###################################################################################
### Spectre: TIFF to FCS
###################################################################################
        ### Load libraries

        library('Spectre')

        Spectre::package.check(type = 'spatial')
        Spectre::package.load(type = 'spatial')
        ### Set PrimaryDirectory

        dirname(rstudioapi::getActiveDocumentContext()$path)            # Finds the directory where this script is located
        setwd(dirname(rstudioapi::getActiveDocumentContext()$path))     # Sets the working directory to where the script is located
        getwd()
        PrimaryDirectory <- getwd()
        PrimaryDirectory
        ### Set InputDirectory (ROI TIFFs)

        setwd(PrimaryDirectory)
        setwd("../data/ROIs/")
        InputDirectory <- getwd()
        InputDirectory
## [1] "/Users/thomasa/OneDrive - The University of Sydney (Staff)/Library/Github (public)/Spectre/workflows/Spatial - FlowJo/data/ROIs"
        setwd(PrimaryDirectory)
        setwd("../data/masks")
        MaskDirectory <- getwd()
        MaskDirectory
## [1] "/Users/thomasa/OneDrive - The University of Sydney (Staff)/Library/Github (public)/Spectre/workflows/Spatial - FlowJo/data/masks"
        ### Create output directory

        setwd(PrimaryDirectory)
        dir.create("Output - TIFF to FCS")
        setwd("Output - TIFF to FCS")
        OutputDirectory <- getwd()
        OutputDirectory
## [1] "/Users/thomasa/OneDrive - The University of Sydney (Staff)/Library/Github (public)/Spectre/workflows/Spatial - FlowJo/Spectre TIFF to FCS/Output - TIFF to FCS"
###################################################################################
### Check ROIs and TIFFs
###################################################################################
        ### Initialise the spatial data object with channel TIFF files

        setwd(InputDirectory)

        rois <- list.dirs(full.names = FALSE, recursive = FALSE)
        as.matrix(rois)
##      [,1]
## [1,] "ROI002"
## [2,] "ROI004"
## [3,] "ROI006"
## [4,] "ROI008"
## [5,] "ROI010"
## [6,] "ROI012"
        ### Check channel names

        tiff.list <- list()

        for(i in rois){
            setwd(InputDirectory)
            setwd(i)
            tiff.list[[i]] <- list.files(getwd())
        }

        t(as.data.frame(tiff.list))
##        [,1]               [,2]              [,3]             [,4]
## ROI002 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
## ROI004 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
## ROI006 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
## ROI008 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
## ROI010 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
## ROI012 "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff"
##        [,5]              [,6]              [,7]              [,8]
## ROI002 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
## ROI004 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
## ROI006 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
## ROI008 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
## ROI010 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
## ROI012 "CD45_Sm152.tiff" "CD8a_Dy162.tiff" "DNA1_Ir191.tiff" "DNA3_Ir193.tiff"
###################################################################################
### Read in TIFF files and create spatial objects
###################################################################################  
        ### Read in ROI channel TIFFs

        setwd(InputDirectory)
        spatial.dat <- read.spatial.files(dir = InputDirectory)
        ### Check results

        str(spatial.dat, 3)
## List of 6
##  $ ROI002:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
##  $ ROI004:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
##  $ ROI006:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
##  $ ROI008:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
##  $ ROI010:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
##  $ ROI012:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  : list()
##   .. ..@ DATA   : list()
        spatial.dat[[1]]@RASTERS
## class      : RasterStack
## dimensions : 501, 500, 250500, 8  (nrow, ncol, ncell, nlayers)
## resolution : 1, 1  (x, y)
## extent     : 0, 500, 0, 501  (xmin, xmax, ymin, ymax)
## crs        : NA
## names      : CD11b_Sm149, CD20_Dy161, CD3_Er170, CD4_Gd156, CD45_Sm152, CD8a_Dy162, DNA1_Ir191, DNA3_Ir193
## min values :           0,          0,         0,         0,          0,          0,          0,          0
## max values :          33,        779,        41,        40,        734,        109,       1670,       3007
###################################################################################
### Read in masks files
###################################################################################
        ### Define cell mask extension for different mask types

        setwd(MaskDirectory)

        all.masks <- list.files(pattern = '.tif')
        as.matrix(all.masks)
##       [,1]
##  [1,] "ROI002_ilastik_s2_Object Identities.tif"
##  [2,] "ROI002_ilastik_s2_Object Predictions.tif"
##  [3,] "ROI002_ilastik_s2_Simple Segmentation.tif"
##  [4,] "ROI004_ilastik_s2_Object Identities.tif"
##  [5,] "ROI004_ilastik_s2_Object Predictions.tif"
##  [6,] "ROI004_ilastik_s2_Simple Segmentation.tif"
##  [7,] "ROI006_ilastik_s2_Object Identities.tif"
##  [8,] "ROI006_ilastik_s2_Object Predictions.tif"
##  [9,] "ROI006_ilastik_s2_Simple Segmentation.tif"
## [10,] "ROI008_ilastik_s2_Object Identities.tif"
## [11,] "ROI008_ilastik_s2_Object Predictions.tif"
## [12,] "ROI008_ilastik_s2_Simple Segmentation.tif"
## [13,] "ROI010_ilastik_s2_Object Identities.tif"
## [14,] "ROI010_ilastik_s2_Object Predictions.tif"
## [15,] "ROI010_ilastik_s2_Simple Segmentation.tif"
## [16,] "ROI012_ilastik_s2_Object Identities.tif"
## [17,] "ROI012_ilastik_s2_Object Predictions.tif"
## [18,] "ROI012_ilastik_s2_Simple Segmentation.tif"
        mask.types <- list('cell.mask' = '_ilastik_s2_Object Identities.tif',
                           'cell.type' = '_ilastik_s2_Object Predictions.tif',
                           'region' = '_ilastik_s2_Simple Segmentation.tif')
        mask.types
## $cell.mask
## [1] "_ilastik_s2_Object Identities.tif"
##
## $cell.type
## [1] "_ilastik_s2_Object Predictions.tif"
##
## $region
## [1] "_ilastik_s2_Simple Segmentation.tif"
        ### Read in masks

        for(i in names(mask.types)){
            spatial.dat <- do.add.masks(dat = spatial.dat,
                                        mask.dir = MaskDirectory,
                                        mask.pattern = mask.types[[i]],
                                        mask.label = i)
        }

        str(spatial.dat, 3)
## List of 6
##  $ ROI002:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI004:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI006:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI008:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI010:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI012:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
        str(spatial.dat[[1]]@MASKS, 3)
## List of 3
##  $ cell.mask:List of 1
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
##  $ cell.type:List of 1
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
##  $ region   :List of 1
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
###################################################################################
### Rename rasters (if required)
###################################################################################
        ### Check channel names

        channel.names <- list()

        for(i in names(spatial.dat)){
            channel.names[[i]] <- names(spatial.dat[[i]]@RASTERS)
        }

        t(as.data.frame(channel.names))
##        [,1]          [,2]         [,3]        [,4]        [,5]
## ROI002 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
## ROI004 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
## ROI006 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
## ROI008 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
## ROI010 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
## ROI012 "CD11b_Sm149" "CD20_Dy161" "CD3_Er170" "CD4_Gd156" "CD45_Sm152"
##        [,6]         [,7]         [,8]
## ROI002 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
## ROI004 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
## ROI006 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
## ROI008 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
## ROI010 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
## ROI012 "CD8a_Dy162" "DNA1_Ir191" "DNA3_Ir193"
        ### List of corrections (first entry is the 'correct' one)

        # corrections <- list(c('CD4','Cd4'),
        #                     c('CD8','CD8a')
        #                     )
        ### Replace the 'incorrect' names

        # for(i in names(spatial.dat)){
        #   # i <- names(spatial.dat)[[1]]
        #
        #   for(a in c(1:length(corrections))){
        #     # a <- 1
        #
        #     trg <- which(names(spatial.dat[[i]]@RASTERS) == corrections[[a]][2])
        #     if(length(trg) != 0){
        #       names(spatial.dat[[i]]@RASTERS)[trg] <- corrections[[a]][1]
        #     }
        #   }
        # }
        ### Check channel names

        # channel.names <- list()
        #
        # for(i in names(spatial.dat)){
        #   channel.names[[i]] <- names(spatial.dat[[i]]@RASTERS)
        # }
        #
        # t(as.data.frame(channel.names))     
###################################################################################
### Generate polygons and outlines
###################################################################################
        ### Generate polygons and outlines

        for(i in names(spatial.dat[[1]]@MASKS)){
            spatial.dat <- do.create.outlines(dat = spatial.dat, mask.name = i)
        }
        ### Checks

        str(spatial.dat, 3)
## List of 6
##  $ ROI002:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI004:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI006:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI008:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI010:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
##  $ ROI012:Formal class 'spatial' [package "Spectre"] with 3 slots
##   .. ..@ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
##   .. ..@ MASKS  :List of 3
##   .. ..@ DATA   : list()
        str(spatial.dat[[1]]@MASKS, 2)
## List of 3
##  $ cell.mask:List of 4
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
##   ..$ polygons  :Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
##   ..$ outlines  :'data.frame':   102730 obs. of  7 variables:
##   ..$ centroids :Formal class 'SpatialPoints' [package "sp"] with 3 slots
##  $ cell.type:List of 4
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
##   ..$ polygons  :Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
##   ..$ outlines  :'data.frame':   23699 obs. of  7 variables:
##   ..$ centroids :Formal class 'SpatialPoints' [package "sp"] with 3 slots
##  $ region   :List of 4
##   ..$ maskraster:Formal class 'RasterLayer' [package "raster"] with 12 slots
##   ..$ polygons  :Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
##   ..$ outlines  :'data.frame':   8452 obs. of  7 variables:
##   ..$ centroids :Formal class 'SpatialPoints' [package "sp"] with 3 slots
###################################################################################
### Mask QC plots
###################################################################################    
        ### Mask plot setup

        setwd(OutputDirectory)
        dir.create('Plots - cell masks')
        setwd('Plots - cell masks')

        as.matrix(names(spatial.dat[[1]]@RASTERS))
##      [,1]
## [1,] "CD11b_Sm149"
## [2,] "CD20_Dy161"
## [3,] "CD3_Er170"
## [4,] "CD4_Gd156"
## [5,] "CD45_Sm152"
## [6,] "CD8a_Dy162"
## [7,] "DNA1_Ir191"
## [8,] "DNA3_Ir193"
        base <- 'DNA1_Ir191'
        base
## [1] "DNA1_Ir191"
        as.matrix(names(spatial.dat[[1]]@MASKS))
##      [,1]
## [1,] "cell.mask"
## [2,] "cell.type"
## [3,] "region"
        mask <- 'cell.mask'
        mask  
## [1] "cell.mask"
        ### Create plots

        for(i in names(spatial.dat)){
            make.spatial.plot(dat = spatial.dat,
                              image.roi = i,
                              image.channel = base,
                              mask.outlines = mask)
        }