Gates are geometric shapes that define boundaries within which events
(cells) must be contained to be considered part of a population. Gates may be
any one of RectangleGate, PolygonGate, EllipseGate, RangeGate, QuadrantGate or
SplitGate. Each gate has a gid
value, explained below.
On a biological basis, populations are groups of cells that are analyzed
together. For example, you might have a "CD4+ T cells" population. In
CellEngine's implementation, populations are gates combined with Boolean
operators. For example, "CD4+ T cells" might be defined as {$and: [<Singlets>,
<T cells>, <CD4+>]}
, where the values in <brackets>
are the gid
values for
those gates. Any of $and
, $or
, $not
and $xor
can be used, but note that
the CellEngine UI cannot display certain complex populations.
Populations have two additional parameters of note besides their gates:
parentId
- This is used to construct the hierarchy.terminalGateGid
- This is an optional parameter that, in a typical,
step-wise hierarchy, indicates the one gate that distinguishes a population
from its parent. For example, given the hierarchy Ungated > Singlets > T cells,
the populations would look like this:``` { name: "Ungated", _id: null, gates: '{"$and": []}', terminalGateGid: null, parentId: null }
{
name: "Singlets",
_id: "64daccf0ff756d82e41155ba",
gates: '{"$and": ["
{
name: "T cells",
_id: "64daccf0ff756d82e41155bb",
gates: '{"$and": ["
Boolean populations that don't have a single gate that distinguishes them from
their parent have this value set to NULL
.
CellEngine allows each FCS file to have a unique gate geometry by "tailoring"
gates per-file. Tailored gates exist in a group. Within a group, there's
the global gate, which is used for any FCS file that does not have a
file-specific gate; and there are zero or more tailored gates, each of which is
used for a specific FCS file. All gates in the group have the same gid
(group
ID) value. The global gate has an fcsFileId
value of NA
, and the
file-specific gates have fcsFileId
values set to the _id
of their
corresponding FCS file.
Compound gates (quadrant and split) are made up of "sectors." Quadrant gates
have four sectors (upper-right, upper-left, lower-left, lower-right) and split
gates have two sectors (left and right). In addition to the top-level gid
(like simple gates), these gates have model.gids
and names
lists that
specify the group ID and name for each sector, in the order shown above.
Populations using compound gates must reference these sector group IDs.
The above is all you need to know to get started with creating gates with the CellEngine R Toolkit, but the CellEngine API documentation for gates and populations has more detailed information and examples.
First let's create a basic polygon gate.
library("cellengine") authenticate() # prompts for username and password # Get an existing experiment called "My experiment" experiment = getExperiment(byName("My experiment")) res = createPolygonGate( experimentId=experiment$`_id`, "FSC-A", "FSC-H", # the X and Y channels "my gate", # the gate name list( # the vertices c(37836.07, 971.51), c(1588732.12, 154.646), c(8139.405, 664.78), c(9441.949, 781.32) ), createPopulation = TRUE, parentPopulationId = cellengine::UNGATED )
Two things to observe:
We used createPopulation = TRUE
and parentPopulationId = cellengine::UNGATED
.
This tells CellEngine to create a corresponding population for the gate at the
root level, under "Ungated". The res
object contains res$gate
and
res$population
members, so you can access both the created gate and created
population.
The gate coordinates are in "scaled data space." That means that the scale
function that is used to show the data in CellEngine (i.e., linear, log or
arcsinh) also needs to be applied to the gate coordinates. You can see what
scale is in used by clicking "edit scales" in CellEngine, or by calling
getScaleSets()
in the R toolkit.
You can use the toolkit's applyScale()
function to convert from raw data
space to scaled data space.
To create the next gate and population in the hierarchy, below "my gate", repeat
the above, but set
parentPopulationId = byName("my gate")
or parentPopulationId = res$population$`_id`
.
First we're going to make the global gate. This is the same as above, except
we'll add tailoredPerFile = TRUE
:
res = createPolygonGate( experimentId=experiment$`_id`, "FSC-A", "FSC-H", "my other gate", list( c(37836.07, 971.51), c(1588732.12, 154.646), c(8139.405, 664.78), c(9441.949, 781.32) ), createPopulation = TRUE, parentPopulationId = byName("singlets"), tailoredPerFile = TRUE # <-- added )
Now we're going to make a tailored gate:
res2 = createPolygonGate( experimentId=experiment$`_id`, "FSC-A", "FSC-H", "my other gate", list( c(12345, 900), c(160002, 175), c(8000, 750), c(9410, 800) ), gid = res$gate$gid, # <-- added tailoredPerFile = TRUE, fcsFileId = byName("file1.fcs") # <-- added # createPopulation is FALSE or omitted # parentPopulationId is omitted )
Notice:
gid = res$gate$gid
so that this new gate will be in the same gate
group as the gate we created in the previous step.fcsFileId = byName("file1.fcs")
. This creates a gate specific to
that file.createPopulation
and parentPopulationId
because we're not
creating a population again; we're just creating a gate tailored to this file.Now if you open CellEngine and flip through the FCS files in this experiment, you should see the file-specific gates being used.
CellEngine has support for converting ellipse, polygon and rectangle gates to and from flowCore gates. More gate types will be supported eventually.
Given a flowCore gate:
rectGate <- rectangleGate( filterId = "Gate 1", "FL1-H" = c(0, 12), "FL2-H" = c(0, 12) )
We can add it to a CellEngine experiment as follows:
fromFlowCore(rectGate, experimentId = byName("my experiment"), name = "my gate") # "my gate" should now exist in the CellEngine experiment.
fromFlowCore()
will pass ...
arguments on to the create__Gate()
function, so you can, for example, create a population by passing createPopulation = TRUE, parentPopulationId = byName("parent")
, or tailor the gate by passing tailoredPerFile = TRUE, fcsFileId = byName("myfile.fcs")
.
Similarly, you can convert a CellEngine gate to flowCore:
ceGate <- getGate(experimentId = byName("my experiment"), gateId = byName("singlets")) fcGate <- toFlowCore(ceGate)
Using createPopulation = TRUE
works to create common, step-wise hierarchies.
What if you want to make a "not" or "or" population?
# Given GIDs from the gate creation procedures above: singletsGid = "64daccf0ff756d82e4115588" # the Singlets gate cd4Gid = "64daccf0ff756d82e4115599" # the CD4+ gate il2gid = "64daccf0ff756d82e41155aa" # the IL2+ gate il4gid = "64daccf0ff756d82e41155ab" # the IL4+ gate # Create an "or(IL2+, IL4+)" population below Singlets > CD4+: createPopulation( experimentId = byName("my experiment"), name = "or(IL2+, IL4+)", gates = sprintf('{"$and": ["%s", "%s", {"$or": ["%s", "%s"]}]}', singletsGid, cd4Gid, il2gid, il4gid), terminalGateGid = NULL, parentId = byName("CD4+") )
Notice:
terminalGateGid = NULL
. As described under basics, this
property is set to NULL
for Boolean populations.gates
property is set to a JSON string built up of Boolean operators and
GIDs. It can be extremely difficult to get R to convert lists and vectors to
the expected JSON; you can try to use jsonlite
(especially
jsonlite::unbox()
), or you can construct strings manually.Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.