Introduction
Here we provide a brief script in R that requires minimal input to convert TIFF files (plus masks) into FCS and CSV files. FCS can then be used for subsequent analysis in FlowJo.
Setup
1. Install R, RStudio, and Spectre: instructions on this page.
2. Go to https://github.com/ImmuneDynamics/Spectre and download the repository
3. Once it's downloaded, you can explore the contents to find the 'TIFF to FCS.R' file.
4. Copy this 'TIFF to FCS.R' file into a folder, and create a folder in the same location called 'ROIs'. You can then copy your ROI folders into that location. Each ROI folder should contain one TIFF file per channel.
5. You will also need a folder containing 'masks'. You can have multiple masks types present, but each mask needs to contain the name of the ROI (e.g. 'ROI002'), and some name that clarifies the mask type (e.g. '_Cell_mask.tiff').
Setting up the run in RStudio
Open RStudio and then open the 'TIFF to FCS.R' file. If you aren't familiar with interacting with R/RStudio, check out out quick tutorial on this page.
In the first large section of code we can set up our run and our preferences.
################################################################################### ### Spectre: TIFF to FCS ###################################################################################
Load packages and set directories
x
################################################################################### ### Spectre: spatial 1 - add masks and extract cellular data ###################################################################################
### 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
### Set MaskDirectory (ROI mask TIFFs) setwd(PrimaryDirectory) setwd("data/masks/") MaskDirectory <- getwd() MaskDirectory
### Create output directory setwd(PrimaryDirectory) dir.create("Output 1 - add masks") setwd("Output 1 - add masks") OutputDirectory <- getwd() OutputDirectory
Check TIFFs
x
################################################################################### ### 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"
tiff.list <- list()
### Check channel names for(i in rois){ setwd(InputDirectory) setwd(i) tiff.list[[i]] <- list.files(getwd()) }
t(as.data.frame(tiff.list))
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] ROI002 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" ROI004 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" ROI006 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" ROI008 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" ROI010 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" ROI012 "140Ce_Ce140.tiff" "80ArAr_ArAr80.tiff" "alphaSMA_Pr141.tiff" "CD11b_Sm149.tiff" "CD20_Dy161.tiff" "CD3_Er170.tiff" "CD4_Gd156.tiff" "CD45_Sm152.tiff" "CD68_Tb159.tiff" "CD8a_Dy162.tiff" "CollagenI_Tm169.tiff" "DNA1_Ir191.tiff" [,13] [,14] [,15] ROI002 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff" ROI004 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff" ROI006 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff" ROI008 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff" ROI010 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff" ROI012 "DNA3_Ir193.tiff" "HistoneH3_Yb176.tiff" "VIM_Nd143.tiff"
Import channel TIFFs
x
################################################################################### ### Read in TIFF files and create spatial objects ###################################################################################
### Read in ROI channel TIFFs setwd(InputDirectory) spatial.dat <- read.spatial.files(rois = rois, roi.loc = getwd())
### Check results str(spatial.dat, 3)
List of 6 $ ROI002:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots $ ROI004:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots $ ROI006:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots $ ROI008:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots $ ROI010:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots $ ROI012:List of 1 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots
spatial.dat[[1]]$RASTERS
class : RasterStack dimensions : 501, 500, 250500, 15 (nrow, ncol, ncell, nlayers) resolution : 1, 1 (x, y) extent : 0, 500, 0, 501 (xmin, xmax, ymin, ymax) crs : NA names : X140Ce_Ce140f, X80ArAr_ArAr80f, alphaSMA_Pr141f, CD11b_Sm149f, CD20_Dy161f, CD3_Er170f, CD4_Gd156f, CD45_Sm152f, CD68_Tb159f, CD8a_Dy162f, CollagenI_Tm169f, DNA1_Ir191f, DNA3_Ir193f, HistoneH3_Yb176f, VIM_Nd143f min values : 0, 3399, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 max values : 443, 5297, 1231, 33, 779, 41, 40, 734, 1223, 109, 183, 1670, 3007, 3167, 7764
Import masks
x
################################################################################### ### 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_Cell_mask.tiff" [2,] "ROI002_Cytoplasm_mask.tiff" [3,] "ROI002_Nuclei_mask.tiff" [4,] "ROI004_Cell_mask.tiff" [5,] "ROI004_Cytoplasm_mask.tiff" [6,] "ROI004_Nuclei_mask.tiff" [7,] "ROI006_Cell_mask.tiff" [8,] "ROI006_Cytoplasm_mask.tiff" [9,] "ROI006_Nuclei_mask.tiff" [10,] "ROI008_Cell_mask.tiff" [11,] "ROI008_Cytoplasm_mask.tiff" [12,] "ROI008_Nuclei_mask.tiff" [13,] "ROI010_Cell_mask.tiff" [14,] "ROI010_Cytoplasm_mask.tiff" [15,] "ROI010_Nuclei_mask.tiff" [16,] "ROI012_Cell_mask.tiff" [17,] "ROI012_Cytoplasm_mask.tiff" [18,] "ROI012_Nuclei_mask.tiff"
### Import CELL masks as.matrix(all.masks) cell.mask.ext <- '_Cell_mask.tiff' cell.masks <- list.files(pattern = cell.mask.ext) as.matrix(cell.masks)
[,1] [1,] "ROI002_Cell_mask.tiff" [2,] "ROI004_Cell_mask.tiff" [3,] "ROI006_Cell_mask.tiff" [4,] "ROI008_Cell_mask.tiff" [5,] "ROI010_Cell_mask.tiff" [6,] "ROI012_Cell_mask.tiff"
spatial.dat <- do.add.masks(spatial.dat = spatial.dat, mask.loc = getwd(), masks = cell.masks, mask.label = 'cell.mask', mask.ext = cell.mask.ext)
Adding masks to cell.mask -- processing ROI002 ... correcting extent ... flipping Y -- processing ROI004 ... correcting extent ... flipping Y -- processing ROI006 ... correcting extent ... flipping Y -- processing ROI008 ... correcting extent ... flipping Y -- processing ROI010 ... correcting extent ... flipping Y -- processing ROI012 ... correcting extent ... flipping Y Returning spatial data object with added masks
### Review data str(spatial.dat, 3)
List of 6 $ ROI002:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1 $ ROI004:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1 $ ROI006:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1 $ ROI008:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1 $ ROI010:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1 $ ROI012:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 1
spatial.dat[[1]]$MASKS
$cell.mask $cell.mask$maskraster class : RasterLayer dimensions : 501, 500, 250500 (nrow, ncol, ncell) resolution : 1, 1 (x, y) extent : 0, 500, 0, 501 (xmin, xmax, ymin, ymax) crs : NA source : memory names : cell.mask values : 0, 65535 (min, max)
Create polygons for cell
x
################################################################################### ### Generate polygons and outlines ###################################################################################
### Generate polygons and outlines spatial.dat <- do.create.outlines(spatial.dat, 'cell.mask')
Creating polygons, outlines, and centroids using 'stars' method. Processing masks for ROI ROI002 although coordinates are longitude/latitude, st_union assumes that they are planar although coordinates are longitude/latitude, st_union assumes that they are planar although coordinates are longitude/latitude, st_union assumes that they are planar ... ... polygons complete Regions defined for each Polygons ... outlines complete ... centroids complete Returning spatial data
### Checks str(spatial.dat, 3)
List of 6 $ ROI002:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 $ ROI004:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 $ ROI006:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 $ ROI008:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 $ ROI010:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 $ ROI012:List of 2 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4
spatial.dat[[1]]$MASKS
$cell.mask $cell.mask$maskraster class : RasterLayer dimensions : 501, 500, 250500 (nrow, ncol, ncell) resolution : 1, 1 (x, y) extent : 0, 500, 0, 501 (xmin, xmax, ymin, ymax) crs : NA source : memory names : cell.mask values : 0, 65535 (min, max) $cell.mask$polygons class : SpatialPolygonsDataFrame features : 2750 extent : 0, 500, 0, 501 (xmin, xmax, ymin, ymax) crs : NA variables : 1 names : cell.mask min values : 0 max values : 65535 $cell.mask$outlines long lat order hole piece id group 1 19 103 1 FALSE 1 1 1.1 2 19 104 2 FALSE 1 1 1.1 3 20 104 3 FALSE 1 1 1.1
Mask QC plots
x
################################################################################### ### Mask QC plots ###################################################################################
### Mask plots setwd(OutputDirectory) dir.create('Plots - cell masks') setwd('Plots - cell masks')
as.matrix(names(spatial.dat[[1]]$RASTERS)) base <- 'DNA1_Ir191' mask <- 'cell.mask'
for(i in names(spatial.dat)){ make.spatial.plot(spatial.dat = spatial.dat, image.roi = i, image.channel = base, mask.outlines = 'cell.mask') }
Calculate 'per cell' data from masks
x
################################################################################### ### Extract 'per cell' data ###################################################################################
### Extract cellular data for each cell mask spatial.dat <- do.extract(spatial.dat, 'cell.mask')
Processing ROI002 ... X140Ce_Ce140f |==============================================================================================================| 100% ... X80ArAr_ArAr80f |==============================================================================================================| 100% ... alphaSMA_Pr141f |==============================================================================================================| 100% ...
str(spatial.dat, 3)
List of 6 $ ROI002:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 2750 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr> $ ROI004:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 4205 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr> $ ROI006:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 4200 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr> $ ROI008:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 1685 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr> $ ROI010:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 1301 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr> $ ROI012:List of 3 ..$ RASTERS:Formal class 'RasterStack' [package "raster"] with 11 slots ..$ MASKS :List of 1 .. ..$ cell.mask:List of 4 ..$ DATA :List of 1 .. ..$ CellData:Classes ‘data.table’ and 'data.frame': 1427 obs. of 19 variables: .. .. ..- attr(*, ".internal.selfref")=<externalptr>
Save spatial data object
x
################################################################################### ### Save spatial data object as qs file ###################################################################################
### Output setwd(OutputDirectory) dir.create('QS file') setwd('QS file') qsave(spatial.dat, "spatial.dat.qs")
Write FCS files
x
################################################################################### ### Write FCS files ###################################################################################
### Pull cellular data cell.dat <- do.pull.data(spatial.dat, 'CellData') cell.dat
ROI ID x y Area X140Ce_Ce140f X80ArAr_ArAr80f alphaSMA_Pr141f CD11b_Sm149f CD20_Dy161f ... 1: ROI002 1 276.24140 218.403834 100982 0.11077221 4291.782 9.292290 0.3623715 6.8142638 ... 2: ROI002 2 222.54000 3.200000 50 0.06000000 4143.240 8.840000 0.2800000 1.9000000 ... 3: ROI002 3 247.44643 3.660714 56 0.03571429 4150.036 3.392857 0.3928571 0.4464286 ... 4: ROI002 4 333.27941 4.250000 68 0.08823530 4184.838 5.720588 0.3529412 1.2205882 ... 5: ROI002 5 388.37755 4.785714 49 0.08163265 4123.775 2.102041 0.1428571 0.5714286 ... --- 15564: ROI012 1423 216.41892 494.986486 37 0.05405406 4016.324 64.027023 0.2972973 0.2972973 ... 15565: ROI012 1424 59.82099 495.919753 81 0.16049382 4092.963 5.938272 0.3333333 1.0000000 ... 15566: ROI012 1425 432.20732 495.731707 82 0.08536585 4042.378 9.524390 0.6829268 0.8780488 ... 15567: ROI012 1426 397.91463 495.548780 41 0.00000000 4051.805 8.341463 0.3170732 0.5121951 ... 15568: ROI012 1427 420.92105 496.578947 38 0.13157895 4051.763 4.315790 0.2105263 1.1578947 ...
as.matrix(names(cell.dat))
[,1] [1,] "ROI" [2,] "ID" [3,] "x" [4,] "y" [5,] "Area" [6,] "X140Ce_Ce140f" [7,] "X80ArAr_ArAr80f" [8,] "alphaSMA_Pr141f" [9,] "CD11b_Sm149f" [10,] "CD20_Dy161f" [11,] "CD3_Er170f" [12,] "CD4_Gd156f" [13,] "CD45_Sm152f" [14,] "CD68_Tb159f" [15,] "CD8a_Dy162f" [16,] "CollagenI_Tm169f" [17,] "DNA1_Ir191f" [18,] "DNA3_Ir193f" [19,] "HistoneH3_Yb176f" [20,] "VIM_Nd143f"
cellular.cols <- names(cell.dat)[c(6:20)] as.matrix(cellular.cols)
[,1] [1,] "X140Ce_Ce140f" [2,] "X80ArAr_ArAr80f" [3,] "alphaSMA_Pr141f" [4,] "CD11b_Sm149f" [5,] "CD20_Dy161f" [6,] "CD3_Er170f" [7,] "CD4_Gd156f" [8,] "CD45_Sm152f" [9,] "CD68_Tb159f" [10,] "CD8a_Dy162f" [11,] "CollagenI_Tm169f" [12,] "DNA1_Ir191f" [13,] "DNA3_Ir193f" [14,] "HistoneH3_Yb176f" [15,] "VIM_Nd143f"
### Arcsinh transformation cell.dat <- do.asinh(cell.dat, cellular.cols, cofactor = 1)
### Invert y axis all.neg <- function(test) -1*abs(test) y_invert <- cell.dat[['y']] y_invert <- all.neg(y_invert) cell.dat[['y_invert']] <- y_invert cell.dat
### Write FCS files setwd(OutputDirectory) dir.create('FCS files') setwd('FCS files') write.files(cell.dat, 'spatial_all', write.csv = FALSE, write.fcs = TRUE) write.files(cell.dat, 'spatial', divide.by = 'ROI', write.csv = FALSE, write.fcs = TRUE)
Further plots
x
################################################################################### ### Further plots ###################################################################################
### Plot settings as.matrix(names(spatial.dat)) plot.rois <- c("ROI002", "ROI004") plot.rois
[1] "ROI002" "ROI004"
as.matrix(names(spatial.dat[[1]]$RASTERS)) exp.plot <- names(spatial.dat[[1]]$RASTERS)[1:15] exp.plot
[1] "X140Ce_Ce140f" "X80ArAr_ArAr80f" "alphaSMA_Pr141f" "CD11b_Sm149f" "CD20_Dy161f" "CD3_Er170f" [7] "CD4_Gd156f" "CD45_Sm152f" "CD68_Tb159f" "CD8a_Dy162f" "CollagenI_Tm169f" "DNA1_Ir191f" [13] "DNA3_Ir193f" "HistoneH3_Yb176f" "VIM_Nd143f"
as.matrix(names(spatial.dat[[1]]$RASTERS)) factor.plot <- names(spatial.dat[[1]]$RASTERS)[0] factor.plot
character(0)
### Expression data point plots setwd(OutputDirectory) dir.create('Plots - expression') setwd('Plots - expression') for(i in plot.rois){ for(a in exp.plot){ make.spatial.plot(spatial.dat = spatial.dat, image.roi = i, image.channel = a, mask.outlines = 'cell.mask') } }
### Factor data point plots setwd(OutputDirectory) dir.create('Plots - data point expression') setwd('Plots - data point expression') for(i in plot.rois){ for(a in exp.plot){ make.spatial.plot(spatial.dat = spatial.dat, image.roi = i, image.channel = a, mask.outlines = 'cell.mask', cell.dat = 'CellData', cell.col = a) } for(a in factor.plot){ make.spatial.plot(spatial.dat = spatial.dat, image.roi = i, image.channel = a, mask.outlines = 'cell.mask', cell.dat = 'CellData', cell.col = a, cell.col.type = 'factor') } }