---
title: "OpenCyto Tutorial
Robust and Reproducible Automated Gating of Cytometry Data
BioC 2014"
author: "Greg Finak, PhD
Staff Scientist
Vaccine and Infectious Disease Division,
Fred Hutchinson Cancer Research Center"
date: "July, 2014"
output:
ioslides_presentation:
fig_caption: yes
fig_retina: null
pandoc_args:
- -c
- mystyles.css
widescreen: yes
---
## What is OpenCyto? {.smaller}
**Not an algorithm, but a *framework* for automated gating.**
**Goals**
* Easily build *reproducible gating pipelines*.
* *Use any gating algorithm*
* interchange any algorithm at any step (support gating plugins)
* Simplify data handling and data management.
* Easily pass subsets of the data (cell subsets) to different gating algorithms.
* Simple(r) pipeline template definitions
* Pipeline defined via text file (csv)
* Templates and code are *re-usable* for standardized assays and data.
* Facilitate comparative analysis
* Import manually gated data from FlowJo workspaces
* Scale to *large* data sets
* HDF5 support - data sets limited by disk space not RAM.
## Overview {.smaller}
Raw data ➙ Preprocessing ➙ Annotation ➙ Gating ➙ Statistical analysis ➙ Output
```{r framework_figure, echo=FALSE,fig.cap='The OpenCyto Gating Framework is a collection of R/BioConductor packages for easily building reproducible flow data analysis pipelines.',message=FALSE,fig.align='center',fig.width=8,cache=TRUE}
require(png)
require(grid)
img<-readPNG("resources/Framework.png")
grid.raster(img)
```
## Getting Started | Installation
**Requirements: R + Bioconductor**
* Install *release version* of R from [CRAN](http://cran.r-project.org/).
* Install *release version* of BioConductor from [bioconductor.org/install](http://www.bioconductor.org/install/)
* Install OpenCyto and its dependencies
* Within R type the following:
```{r eval=FALSE}
require(BiocInstaller)
biocLite("openCyto")
```
This installs all the required packages.
Still have problems?
[Bioconductor mailing list](https://stat.ethz.ch/mailman/listinfo/bioconductor)
Email: [Mike Jiang](wjiang2@fhcrc.org) or [Greg Finak](gfinak@fhcrc.org)
Twitter: [\@OpenCyto](http://www.twitter.com/OpenCyto)
## Getting Started II
*Alternately* if you are brave and want the latest bug fixes and features - *github.com/RGLab*
```{r eval=FALSE}
require(devtools)
packages<-c("RGLab/flowStats","RGLab/flowCore","RGLab/flowViz","RGLab/ncdfFlow","RGLab/flowWorkspace","RGLab/openCyto")
install_github(packages,quick=TRUE)
```
You may use the *devtools* package to install the latest stable versions directly from github.
## A Worked Example | Intracellular Cytokine Staining of Antigen-stimulated T-cells
* Full data set at Flowrepository.org [FR-FCM-ZZ7U](http://flowrepository.org/experiments/254/public_id)
* Batch 0882, 76 sample files, 13 compensation controls.
```{r libs,echo=FALSE,message=FALSE,warning=FALSE}
require(openCyto)
require(data.table)
require(reshape2)
require(clue)
require(ggplot2)
```
```{r echo=1,results='markup',results='hide'}
ws<-openWorkspace("data/workspace/080 batch 0882.xml")
ws
```
FlowJo Workspace Version 2.0
File location: data/workspace
File name: 080 batch 0882.xml
Workspace is open.
Groups in Workspace
Name Num.Samples
1 All Samples 158
2 0882-L-080 157
3 Comps 13
4 0882 Samples 76
## Import Manual Gating (parseWorkspace) {.smaller}
Create a gating set of manual gates.
```{r parseWorkspace_echo,echo=TRUE,eval=FALSE}
gating_set<-parseWorkspace(ws,name="0882 Samples",path="data/FCS/",isNcdf=TRUE)
```
```{r parseWorkspace,echo=FALSE,results='hide',eval=TRUE}
if(!file.exists("data/manual_gating/")){
gating_set<-parseWorkspace(ws,name="0882 Samples",path="data/FCS/",isNcdf=TRUE,overwrite=TRUE)
save_gs(gating_set,path="data/manual_gating")
}else{
gating_set<-load_gs("data/manual_gating/")
}
```
Parsing 76 samples
calling c++ parser...
...
We now have gated, compensated and transformed data in an *HDF5* file represented in a *GatingSet* object. We can save it for later use.
```{r eval=FALSE,echo=TRUE,results='hide'}
save_gs(gating_set,path="data/manual_gating")
```
saving ncdf...
saving tree object...
saving R object...
Done
To reload it, use 'load_gs' function
The archived gating set contains all the information on transformation, compensation, single-cell events, and gates and can be *shared with collaborators*.
## Visualizing the Gating Layout (plotGate)
```{r clean_manual,echo=FALSE,results='hide',warning=FALSE,message=FALSE}
Rm("Not 4+",gating_set)
```
```{r vis_manual_gates,echo=TRUE,eval=TRUE,results='hide',cache=FALSE,message=FALSE,warning=FALSE,fig.align='center',fig.cap='Layout of manual gates'}
plotGate(gating_set[[1]],xbin=16,gpar=list(ncol=5)) # Binning for faster plotting
```
## Visualizing the Gating Tree (plot)
Calling `plot` on the gating set gives us a view of the gating tree.
```{r plot_manual_tree,echo=2,results="hide",message=FALSE,warning=FALSE,fig.cap=''}
plot(gating_set)
```
## Annotation {.smaller}
We annotate our gating set from the keywords and flowrepository. We'll keep only the GAG and negative control stimulations
```{r keyword,echo=c(2,3,4,8,9,10),eval=TRUE}
getKeywords<-function(gs,kv){
r<-as.data.frame(do.call(cbind,lapply(kv,function(k){
keyword(gs,k)[1]
})))
data.table::setnames(r,"$FIL","name")
r
}
keyword_vars<-c("$FIL","Stim","Sample Order","EXPERIMENT NAME") #relevant keywords
pd<-data.table(getKeywords(gating_set,keyword_vars)) #extract relevant keywords to a data table
annotations<-data.table:::fread("data/workspace/pd_submit.csv") # read the annotations from flowrepository
setnames(annotations,"File Name","name")
setkey(annotations,"name")
setkey(pd,"name")
pd<-data.frame(annotations[pd]) #data.table style merge
setnames(pd,c("Timepoint","Individual"),c("VISITNO","PTID"))
pData(gating_set)<-pd #annotate
gating_subset<-gating_set[subset(pd,!is.na(Condition))$name] #subset only the GAG and negctrl
```
```{r keywords_tbl,eval=TRUE,echo=FALSE,results='asis',cache=FALSE}
knitr::kable(head(subset(na.omit(pd)[,c(1:5)],PTID%in%"080-17"),4),row.names = FALSE)
```
## Clone and save for automated gating {.smaller}
We want to perform automated gating of this data.
* We'll clone the gating set, delete existing nodes and re-save the data as a new gating set.
```{r copy_and_save,eval=FALSE,message=FALSE,warining=FALSE,results='hide'}
auto_gating<-clone(gating_subset)
Rm("S",auto_gating)
save_gs(auto_gating,path="data/autogating",overwrite=TRUE)
```
```{r load_empty,eval=TRUE,echo=FALSE,results='hide',message=FALSE,warning=FALSE}
if(!file.exists("data/autogating")){
auto_gating<-clone(gating_subset)
try(Rm("S",auto_gating))
save_gs(auto_gating,path="data/autogating")
}else{
auto_gating<-load_gs("data/autogating/")
}
```
```{r echo=TRUE,eval=TRUE}
list.files("data/autogating")
```
* `.nc` file is the HDF5 file of event-level data..
* `.dat` file contains the gating set representation from the `C` data structure.
* `.rds` file is an `R` data file that contains the R-object information.
Send it to a friend, `load_gs()` will read it all in and the data will be available.
## Costructing a Template - I {.smaller}
```{r read_template,echo=FALSE,results='asis',cache=FALSE}
template<-read.csv("data//template//gt_080.csv",as.is=FALSE,allowEscapes=TRUE,stringsAsFactors=FALSE)
template$collapseDataForGating[is.na(template$collapseDataForGating)]<-" "
Kmisc::htmlTable(data.frame(template)[1:8,c(1:6,8,9)],attr="width=100%")
```
## Costructing a Template - II {.smaller}
Each row defines a cell population
* `alias`: how we refer to the population / shorthand
* `pop`: The population definition i.e. do we keep the positive (+) or negative (-) cells for a marker / pair of markers after gating.
* `parent`: The alias of the parent population on which the current population is defined
* `dims`: The dimensions / markers used to define this cell population.
* `gating_method`: Which gating algorithm to use.
* `gating_args`: additional arguments passed to the gating method to tweak various parameters
* `collaseDataForGating`: TRUE or FALSE. Together with groupBy will gate multiple samples with a common gate.
* `groupBy`: Specify metadata variables for combining samples (e.g. PTID)
* `preprocessing_method`: advanced use for some gating methods
* `preprocessing_args`: additional arguments
## Constructing a Template - III
We read in the template and visualize it
```{r read_plot_template,echo=TRUE,message=FALSE,warning=FALSE,results='hide',fig.align="center",fig.width=8,fig.height=5,out.width=600,dpi=200,fig.cap=''}
gt<-gatingTemplate("data/template/gt_080.csv")
plot(gt)
```
## Automated Gating
openCyto walks through the template and gates each population in each sample using the algoirthm named in the template.
```{r gate_subset,echo=TRUE,message=FALSE,warning=FALSE,results='hide',eval=FALSE,fig.cap=''}
gating(x = gt, y = auto_gating)
## Some output..
plot(auto_gating)
```
```{r plot_autogate_tree,echo=FALSE,message=FALSE,warning=FALSE,fig.align='center',out.width=600,results='hide'}
if(length(getNodes(auto_gating))<1){
gating(x = gt, y = auto_gating)
save_gs(auto_gating,path="data//autogating",overwrite = TRUE)
}
setNode(auto_gating,"nonNeutro",FALSE)
setNode(auto_gating,"DebrisGate",FALSE)
setNode(auto_gating,"cd4/cd57-/gzb_gate",FALSE)
setNode(auto_gating,"cd4/cd57-/prf_gate",FALSE)
setNode(auto_gating,"cd8/cd57-/gzb_gate",FALSE)
setNode(auto_gating,"cd8/cd57-",FALSE)
setNode(auto_gating,"cd4/cd57-",FALSE)
setNode(auto_gating,"cd8/cd57-/prf_gate",FALSE)
setNode(auto_gating,"cd4neg",FALSE)
setNode(auto_gating,"cd4pos",FALSE)
setNode(auto_gating,"cd8+",FALSE)
setNode(auto_gating,"cd8-",FALSE)
plot(auto_gating)
```
## Automated Gating - II {.smaller}
```{r compare_to_manual,echo=TRUE,fig.cap='Automated and Manual Gates for CD4/CD8',cache=FALSE,fig.align='center',fig.width=5,fig.height=2.5,out.width=600}
p1<-plotGate(auto_gating[[1]],c("cd8","cd4"),arrange=FALSE,
projections=list("cd4"=c(x="CD8",y="CD4"),"cd8"=c(x="CD8",y="CD4")),
main="Automated Gate",path=2)[[1]]
p2<-plotGate(gating_subset[[1]],c("8+","4+"),
projections=list("4+"=c(x="CD8",y="CD4"),"8+"=c(x="CD8",y="CD4")),
arrange=FALSE,main="Manual Gate",path=2)[[1]]
grid.arrange(arrangeGrob(p1,p2,ncol=2))
```
Stats and gates are comparable, could be tweaked if necessary, but importantly it's reproducible. Always generate the same result.
## Extract Stats and Compare Manual to Automated{.smaller}
```{r extract_stats,echo=FALSE,message=FALSE, warning=FALSE,error=FALSE,results='hide',fig.cap='Comparison of Manual vs Automated Gating Cell Subset Counts',fig.width=12,fig.height=5,cache=FALSE,fig.align='center',out.width=800}
auto_stats<-getPopStats(auto_gating,statistic="count")
manual_stats<-getPopStats(gating_subset,statistic="count")
gates_to_remove<-rownames(manual_stats)[which(sapply(basename(rownames(manual_stats)),function(x)length(which(strsplit(x,"")[[1]]=="+"))>1))]
gates_to_remove<-c(gates_to_remove,rownames(manual_stats)[which(sapply(basename(rownames(manual_stats)),function(x)length(which(strsplit(x,"")[[1]]=="-"))>1))],"/S/Lv/L/Not 4+","/S/Lv/L/3+/8+/Granzyme B-","/S/Lv/L/3+/8+/IFN\\IL2","/S/Lv/L/3+/4+/IFN\\IL2","/S/Lv/L/3+/8+/IFN+IL2-","/S/Lv/L/3+/4+/IFN+IL2-","/S/Lv/L/3+/8+/IFN-IL2+", "/S/Lv/L/3+/4+/Granzyme B-","4+/IFN-IL2+","/S/Lv/L/3+/8+/57-", "/S/Lv/L/3+/4+/57-" )
for(i in gates_to_remove){
try(Rm(i,gating_subset),silent=TRUE)
}
.getCounts<-function(stat="count"){
auto_stats<-getPopStats(auto_gating,statistic=stat)
manual_stats<-getPopStats(gating_subset,statistic=stat)
#combine
melted_auto<-melt(auto_stats)
melted_manual<-melt(manual_stats)
setnames(melted_manual,c("population","file","counts"))
setnames(melted_auto,c("population","file","counts"))
melted_auto<-subset(melted_auto,!population%in%c("boundary","root"))
melted_auto$population<-factor(melted_auto$population)
melted_manual<-subset(melted_manual,!population%in%c("4+/57-","4+/Granzyme B-","4+/IFN+IL2-","4+/IFN-IL2+","4+/IFN\\IL2","8+/57-","8+/Granzyme B-","8+/IFN+IL2-", "8+/IFN-IL2+" ,"8+/IFN\\IL2","root"))
melted_manual$population<-factor(melted_manual$population)
D<-adist((levels(melted_auto$population)),(levels(melted_manual$population)),ignore.case=TRUE,partial=FALSE)
D<-solve_LSAP(t(D))
data.frame(levels(melted_auto$population)[D],levels(melted_manual$population))
levels(melted_auto$population)[D]<-levels(melted_manual$population)
merged<-rbind(cbind(melted_auto,method="auto"),cbind(melted_manual,method="manual"))
merged
}
merged_counts<-.getCounts(stat="count")
merged_props<-.getCounts(stat="freq")
corr.coef<-cor(dcast(merged_counts,file+population~method,value.var = "counts")[,c("auto","manual")],use="complete")
corr.coef.freq<-cor(dcast(merged_props,file+population~method,value.var = "counts")[,c("auto","manual")],use="complete")
et<-element_text(size=20)
et2<-element_text(size=12)
p1<-ggplot(dcast(merged_counts,file+population~method,value.var = "counts"))+geom_point(aes(x=auto,y=manual,color=basename(as.character(population))))+scale_y_log10(Kmisc::wrap("Manual Gating Population Count",30))+scale_x_log10(Kmisc::wrap("Autogating Population Count",30))+theme_bw()+geom_abline(lty=3)+theme(axis.text.x=et,axis.text.y=et,axis.title.x=et,axis.title.y=et,legend.text=et2,legend.position="none",plot.title=et)+scale_color_discrete("Population")+geom_text(aes(x=10,y=100000,label=sprintf("rho=%s",signif(corr.coef[1,2],4))),data=data.frame(corr.coef))+ggtitle("Counts")
p2<-ggplot(dcast(merged_props,file+population~method,value.var = "counts"))+geom_point(aes(x=auto,y=manual,color=basename(as.character(population))))+scale_y_log10(Kmisc::wrap("Manual Gating Population Proportion",30))+scale_x_log10(Kmisc::wrap("Autogating Population Proportion",30))+theme_bw()+geom_abline(lty=3)+theme(axis.text.x=et,axis.text.y=et,axis.title.x=et,axis.title.y=et,legend.text=et2,legend.position="left",plot.title=et)+scale_color_discrete("Population")+geom_text(aes(x=0.01,y=0.75,label=sprintf("rho=%s",signif(corr.coef.freq[1,2],4))),data=data.frame(corr.coef.freq))+ggtitle("Proportions")
grid.draw(cbind(ggplotGrob(p1), ggplotGrob(p2), size="last"))
```
```{r extract_stats_echo,echo=TRUE,eval=FALSE}
#Extract stats
auto_stats<-getPopStats(auto_gating,statistic="count")
manual_stats<-getPopStats(gating_subset,statistic="count")
```
Note Perforin is incorrectly gated in the manual analysis. Minor differences at low end, but reproducible and objective.
## Perforin - Cytokine gate and reference gate{.smaller}
```{r example_Perforin,echo=FALSE,eval=TRUE,fig.cap='Automated and Manual Gating of Perforin/CD8',cache=FALSE,fig.align='center',fig.width=7.5,fig.height=3.5,out.width=500}
p1<-plotGate(auto_gating[[sampleNames(gating_subset)[7]]],"cd8/Prf",default.y="",xbin=256,margin=FALSE,path=2,arrange=FALSE,main="Automated",xlab=list(cex=1.5),ylab=list(cex=1.5),scales=list(cex=1.5),par.strip.text=list(cex=1.5))[[1]]
p2<-plotGate(gating_subset[[sampleNames(gating_subset)[7]]],"8+/Perforin+",default.y="",xbin=256,margin=FALSE,path=2,xlab=list(cex=1.5),ylab=list(cex=1.5),scales=list(cex=1.5),par.strip.text=list(cex=1.5),arrange=FALSE,main="Manual")[[1]]
grid.arrange(p1,p2,ncol=2)
```
```{r example_derivative_gate,echo=FALSE,eval=TRUE,fig.align='center',out.width=500,fig.width=7.5,fig.height=3.5}
fr<-getData(auto_gating[[7]],"cd8/cd57-")
cut<-tailgate(fr,channel="",filter_id="perforin_gate")
dens<-density(exprs(fr[,""]),adjust=2)
second_deriv <- diff(sign(diff(dens$y)))
which_maxima <- which(second_deriv == -2) + 1
which_maxima <- which_maxima[findInterval(dens$x[which_maxima], range(exprs(fr[,""]))) == 1]
which_maxima <- which_maxima[order(dens$y[which_maxima], decreasing = TRUE)]
peaks <- dens$x[which_maxima]
deriv_out <- openCyto:::.deriv_density(x = exprs(fr[,""]), adjust = 2, deriv = 1)
par(mfrow=c(1,2))
plot(dens,main="Density")
abline(v=peaks)
abline(v=cut@min,col="red")
plot(deriv_out,type="l",main="First Derivative",xlab="X",ylab="Density")
abline(v=cut@min,col="red")
abline(v=peaks)
```
Automated gate set on CD57- (reference). Perforin-negative cells included in the manual gate.
## TNFa
```{r example_TNFa,echo=FALSE,eval=TRUE,cache=FALSE,fig.cap="Automated and manual gating of TFNa",fig.align='center'}
p1<-plotGate(auto_gating[[sampleNames(gating_subset)[7]]],c("cd8/TNFa","cd4/TNFa"),default.y="",xbin=256,margin=FALSE,path=2,arrange=FALSE,main="Automated",xlab=list(cex=1.1),ylab=list(cex=1.1),scales=list(cex=1.1),par.strip.text=list(cex=1.1))
p2<-plotGate(gating_subset[[sampleNames(gating_subset)[7]]],c("8+/TNFa+","4+/TNFa+"),default.y="",xbin=256,margin=FALSE,path=2,xlab=list(cex=1.1),ylab=list(cex=1.1),scales=list(cex=1.1),par.strip.text=list(cex=1.1),arrange=FALSE,main="Manual")
do.call(grid.arrange,c(p1,p2,ncol=2))
```
## Some Useful Functions {.smaller}
Return a `flowSet` containing event-level data for the named cell population.
```{r getData,echo=TRUE,eval=FALSE}
cd3_population<-getData(auto_gating,"cd3")
```
Plot a named cell population.
```{r plotGate,echo=TRUE,eval=FALSE}
plotGate(auto_gating,"cd3")
```
Subset by FCS file(s).
```{r subset,echo=TRUE,eval=FALSE}
first_ten_fcs_files<-auto_gating[1:10]
```
List supported gating methods (that can be used in a template).
```{r listgtMethods,echo=TRUE,eval=FALSE}
listgtMethods()
```
Register a new gating or preprocessing plugin.
```{r register_plugin,echo=TRUE,eval=FALSE}
registerPlugins(myfunction,methodName,dependencies, "preprocessing"|"gating")
```
## Some More Useful Functions {.smaller}
Generate a Basic GatingTemplate from a Manual Gating Hierarchy.
```{r gen_template,echo=TRUE,eval=FALSE,results='asis'}
templateGen(gating_subset[[1]])
```
```{r gen_template_noecho,echo=FALSE,eval=TRUE,results='asis'}
hd<-head(data.frame(templateGen(gating_subset[[1]])),7)
hd[is.na(hd)]<-""
Kmisc::htmlTable(hd)
```
Just fill in the `gating_method` and `dims` to get started.
## Some Typical Gating Methods Use Cases
* `mindensity`: Finds the minimum density cut point between two primary populations. Can be restricted to a range of the data.
* `cytokine / tailgate`: Identifies rare populations that are in the tails of a large primary population. Estimates 2st derivative of the denstiy. Smoothing and tolerance can be adjusted.
* `flowClust`: 1D, 2D, or n-Dimensional clustering. Generally useful for lymphocytes. Can infer a data-driven empirical-Bayes prior across samples.
* `singletGate`: Fits a model that approximates a typical singlet gate on scatter area vs height or width.
* `boundary`: Filters out boundary events.
* `refGate`: A *reference* gate. Used to refer to a gate defined elsewhere in the hierarchy, the data-driven threshold can be reused. Similar to *"back-gating"*.
* `flowDensity`: Supported via plugin, density-based gating from our good friends at the BC Cancer Agency.
* Other methods: `rangeGate`, `quadrantGate`, `quantileGate`
## Your turn
Under `/data/OpenCyto` you'll find the data and some R code to reproduce the analysis in these slides. Begin there.
- Copy the code to your home directory and work from there.
- Paths are set up to write to your home directory.
- Load the workspace and reproduce the manual gating.
- Clone the gating set and clear the manual gates.
- Load the template and run the automated gating.
- Open up the OpenCytoPracticalComponent document and work through that.
- Play with the gating parameters to see what effect they have.
## Gating Method Examples - mindensity {.smaller}
Infers a gate threshold based on the minimum density separating two major populations in a 1-D density estimate.
- Open the `csv` template in a new window, the definition for the live/dead gate is:
`viable, viable-, singlet, AViD, mindensity, gate_range=c(500,1000)`
`gate_range` restricts the data region where the method searches for a cutpoint.
`adjust` alters the smoothing (larger value = more smoothing, less *bumpy*)
- Play with these parameters in the gating template.
- What happens if you remove the `gate_range` parameter?
- What happens if you set `adjust=0.75`?
```{r echo=TRUE,eval=FALSE}
# Work with a subset of the data
auto_subset<-auto_gating[1:20] # first 20 samples
# Make changes in the template. SAVE it, then re-load it in R.
gt<-gatingTemplate("data/template/gt_080.csv")
# Remove the old gate from auto_gating
Rm("viable",auto_subset)
# Gate with the new template, stop after the viable gate (which is nonNeutro)
gating(gt,auto_subset,stop.at="nonNeutro")
# View the new result
plotGate(auto_subset,"viable")
```
## Where we've used OpenCyto
Studies wtih 100s of GB of data.
None take more than a couple of hours to run.
* Gating of Lyoplate standardized staining panels (FlowCAP III)
* Many large clinical trial ICS data sets at the HVTN
* ICS data - MTB infected vs. healthy subjects.
* CyTOF combinatorial cytokine data
* Exhaustive gating of polyfunctional T-cell subsets.
Event-level data and cell population memberships can easily be shared with collaborators.
Quickly pushed to downstream analysis.
* MIMOSA (PMC3862207)
* COMPASS (http://rglab.github.org/COMPASS/)
We're usually pretty friendly and can help you get started
## Summary
* A framework for standardizing analysis pipelines.
* Flexible support of different gating approaches via plugins
* e.g flowDensity is supported.
* Re-usable templates and code
* Work is done up front for set up.
* Fully reproducible and *objective, data-driven gating*.
* OpenCyto simplifies
* Data import (raw data and/or manual gating from FlowJo workspace)
* Preprocessing
* Data manipulation (interacting with cell subsets)
* Plotting and visualization
* Extracting statistics for reports
* Downstream analysis with the full power of R's tools.
## Online Resources {.bigger}
* OpenCyto website: http://opencyto.org
* Documentation
* Reproducible examples and data
* Our Github repository: http://github.org/RGLab/OpenCyto
* These slides: http://gfinak.github.io/OpenCytoTutorial
* Code: http://www.github.com/gfinak/OpenCytoTutorial
* The BioConductor website: http://www.bioconductor.org
## Acknowledgements {.columns-2 .lessbigger}
*RGLab @ Fred Hutchinson Cancer Research Center*
**Raphael Gottardo**
**Mike Jiang**
**Jacob Frelinger**
*John Ramey*
*Collaborators*
**Steve De Rosa** @ HIV Vaccine Trials Network
**Adam Asare** @ Immune Tolerance Network
**Evan Newell** @ Singapore Immunology Network
**Mark Davis** @ Stanford
**Adam Triester** @ TreeStar Inc.
**Jay Almarode** @ TreeStar Inc.
*Funding*
National Institutes of Health
Human Immune Project Consortium (HIPC)