forked from BodenmillerGroup/IMCDataAnalysis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path10-image_visualization.Rmd
474 lines (390 loc) · 18.7 KB
/
10-image_visualization.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# Image visualization {#image-visualization}
The following section describes how to visualize the abundance of biomolecules
(e.g. protein or RNA) as well as cell-specific metadata on images. Section
\@ref(pixel-visualization) focuses on visualizing pixel-level information
including the generation of pseudo-color composite images. Section
\@ref(mask-visualization) highlights the visualization of cell metadata (e.g.
cell phenotype) as well as summarized pixel intensities on cell segmentation
masks.
The
[cytomapper](https://www.bioconductor.org/packages/release/bioc/html/cytomapper.html)
R/Bioconductor package was developed to support the handling and visualization
of multiple multi-channel images and segmentation masks [@Eling2020]. The main
data object for image handling is the
[CytoImageList](https://www.bioconductor.org/packages/release/bioc/vignettes/cytomapper/inst/doc/cytomapper.html#5_The_CytoImageList_object)
container which we used in Section \@ref(read-data) to store multi-channel
images and segmentation masks.
We will first read in the previously processed data and randomly select 3 images
for visualization purposes.
```{r read-data-img-viz, message=FALSE}
library(SpatialExperiment)
library(cytomapper)
spe <- readRDS("data/spe.rds")
images <- readRDS("data/images.rds")
masks <- readRDS("data/masks.rds")
# Sample images
set.seed(220517)
cur_id <- sample(unique(spe$sample_id), 3)
cur_images <- images[names(images) %in% cur_id]
cur_masks <- masks[names(masks) %in% cur_id]
```
## Pixel visualization {#pixel-visualization}
The following section gives examples for visualizing individual channels or
multiple channels as pseudo-color composite images. For this the `cytomapper`
package exports the `plotPixels` function which expects a `CytoImageList` object
storing one or multiple multi-channel images. In the simplest use case, a
single channel can be visualized as follows:
```{r single-channel}
plotPixels(cur_images,
colour_by = "Ecad",
bcg = list(Ecad = c(0, 5, 1)))
```
The plot above shows the tissue expression of the epithelial tumor marker
E-cadherin on the 3 selected images. The `bcg` parameter (default `c(0, 1, 1)`)
stands for "background", "contrast", "gamma" and controls these attributes of
the image. This parameter takes a named list where each entry specifies these
attributes per channel. The first value of the numeric vector will be added to
the pixel intensities (background); pixel intensities will be multiplied by the
second entry of the vector (contrast); pixel intensities will be exponentiated
by the third entry of the vector (gamma). In most cases, it is sufficient to
adjust the second (contrast) entry of the vector.
The following example highlights the visualization of 6 markers (maximum allowed
number of markers) at once per image. The markers indicate the spatial
distribution of tumor cells (E-caherin), T cells (CD3), B cells (CD20), CD8+ T
cells (CD8a), plasma cells (CD38) and proliferating cells (Ki67).
```{r 6-channel}
plotPixels(cur_images,
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"),
bcg = list(Ecad = c(0, 5, 1),
CD3 = c(0, 5, 1),
CD20 = c(0, 5, 1),
CD8a = c(0, 5, 1),
CD38 = c(0, 8, 1),
Ki67 = c(0, 5, 1)))
```
### Adjusting colors
The default colors for visualization are chosen by the additive RGB (red, green,
blue) color model. For six markers the default colors are: red, green, blue,
cyan (green + blue), magenta (red + blue), yellow (green + red). These colors
are the easiest to distinguish by eye. However, you can select other colors for
each channel by setting the `colour` parameter:
```{r setting-colors}
plotPixels(cur_images,
colour_by = c("Ecad", "CD3", "CD20"),
bcg = list(Ecad = c(0, 5, 1),
CD3 = c(0, 5, 1),
CD20 = c(0, 5, 1)),
colour = list(Ecad = c("black", "burlywood1"),
CD3 = c("black", "cyan2"),
CD20 = c("black", "firebrick1")))
```
The `colour` parameter takes a named list in which each entry specifies the
colors from which a color gradient is constructed via `colorRampPalette`. These
are usually vectors of length 2 in which the first entry is `"black"` and the
second entry specifies the color of choice. Although not recommended, you can
also specify more than two colors to generate a more complex color gradient.
### Image normalization
As an alternative to setting the `bcg` parameter, images can first be
normalized. Normalization here means to scale the pixel intensities per channel
between 0 and 1 (or a range specified by the `ft` parameter in the `normalize`
function). By default, the `normalize` function scales pixel intensities across
**all** images contained in the `CytoImageList` object (`separateImages = FALSE`).
Each individual channel is scaled independently (`separateChannels = TRUE`).
After 0-1 normalization, maximum pixel intensities can be clipped to enhance the
contrast of the image (setting the `inputRange` parameter). In the following
example, the clipping to 0 and 0.2 is the same as multiplying the pixel
intensities by a factor of 5.
```{r default-normalization}
# 0 - 1 channel scaling across all images
norm_images <- normalize(cur_images)
# Clip channel at 0.2
norm_images <- normalize(norm_images, inputRange = c(0, 0.2))
plotPixels(norm_images,
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"))
```
The default setting of scaling pixel intensities across all images ensures
comparable intensity levels across images. Pixel intensities can also be
scaled **per image** therefore correcting for staining/expression differences
between images:
```{r individual-normalization}
# 0 - 1 channel scaling per image
norm_images <- normalize(cur_images, separateImages = TRUE)
# Clip channel at 0.2
norm_images <- normalize(norm_images, inputRange = c(0, 0.2))
plotPixels(norm_images,
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"))
```
As we can see, the marker Ki67 appears brighter on image 2 and 3 in comparison
to scaling the channel across all images.
Finally, the `normalize` function also accepts a named list input for the
`inputRange` argument. In this list, the clipping range per channel can be set
individually:
```{r setting-inputRange}
# 0 - 1 channel scaling per image
norm_images <- normalize(cur_images,
separateImages = TRUE,
inputRange = list(Ecad = c(0, 50),
CD3 = c(0, 30),
CD20 = c(0, 40),
CD8a = c(0, 50),
CD38 = c(0, 10),
Ki67 = c(0, 70)))
plotPixels(norm_images,
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"))
```
## Cell visualization {#mask-visualization}
In the following section, we will show examples on how to visualize single
cells either as segmentation masks or outlined on composite images. This type
of visualization allows to observe the spatial distribution of cell phenotypes,
the visual assessment of morphological features and quality control in terms
of cell segmentation and phenotyping.
### Visualzing metadata
The `cytomapper` package provides the `plotCells` function that accepts a
`CytoImageList` object containing segmentation masks. These are defined as
single channel images where sets of pixels with the same integer ID identify
individual cells. This integer ID can be found as an entry in the `colData(spe)`
slot and as pixel information in the segmentation masks. The entry in
`colData(spe)` needs to be specified via the `cell_id` argument to the
`plotCells` function. In that way, data contained in the `SpatialExperiment`
object can be mapped to segmentation masks. For the current dataset, the cell
IDs are stored in `colData(spe)$ObjectNumber`.
As cell IDs are only unique within a single image, `plotCells` also requires
the `img_id` argument. This argument specifies the `colData(spe)` as well as the
`mcols(masks)` entry that stores the unique image name from which each cell was
extracted. In the current dataset the unique image names are stored in
`colData(spe)$sample_id` and `mcols(masks)$sample_id`.
Providing these two entries that allow mapping between the `SpatialExperiment`
object and segmentation masks, we can now color individual cells based on their
cell type:
```{r celltype}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype")
```
For consistent visualization, the `plotCells` function takes a named list as
`color` argument. The entry name must match the `colour_by` argument.
```{r setting-celltype-colors}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype",
colour = list(celltype = metadata(spe)$color_vectors$celltype))
```
If only individual cell types should be visualized, the `SpatialExperiment`
object can be subsetted (e.g., to only contain CD8+ T cells). In the following
example CD8+ T cells are colored in red and all other cells that are not
contained in the dataset are colored in white (as set by the `missing_color`
argument).
```{r selective-visualization}
CD8 <- spe[,spe$celltype == "CD8"]
plotCells(cur_masks,
object = CD8,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype",
colour = list(celltype = c(CD8 = "red")),
missing_colour = "white")
```
In terms of visualizing metadata, any entry in the `colData(spe)` slot can be
visualized the `plotCells` function automatically detects if the entry
is continuous or discrete. In this fashion, we can now visualize the area of each
cell:
```{r area}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "area")
```
### Visualizating expression
Similar to visualizing single-cell metadata on segmentation masks, we can
use the `plotCells` function to visualize the aggregated pixel intensities
per cell. In the current dataset pixel intensities were aggregated by computing
the mean pixel intensity per cell and per channel. The `plotCells` function
accepts the `exprs_values` argument (default `counts`) that allows selecting
the assay which stores the expression values that should be visualized.
In the following example, we visualize the asinh-transformed mean pixel
intensities of the epithelial marker E-cadherin on segmentation masks.
```{r Ecad-expression}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "Ecad",
exprs_values = "exprs")
```
We will now visualize the maximum number of
allowed markers as composites on the segmentation masks. As above the markers
indicate the spatial distribution of tumor cells (E-caherin), T cells (CD3), B
cells (CD20), CD8+ T cells (CD8a), plasma cells (CD38) and proliferating cells
(Ki67).
```{r 6-channel-expression}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"),
exprs_values = "exprs")
```
While visualizing 6 markers on the pixel-level may still allow the distinction
of different tissue structures, observing individual expression levels is
difficult when visualizing many markers simultaneously due to often overlapping
expression.
Similarly to adjusting marker colors when visualizing pixel intensities, we
can change the color gradients per marker by setting the `color` argument:
```{r setting-expression-colors}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = c("Ecad", "CD3", "CD20"),
exprs_values = "exprs",
colour = list(Ecad = c("black", "burlywood1"),
CD3 = c("black", "cyan2"),
CD20 = c("black", "firebrick1")))
```
### Outlining cells on images {#outline-cells}
The following section highlights the combined visualization of pixel- and
cell-level information at once. For this, besides the `SpatialExperiment` object,
the `plotPixels` function accepts two `CytoImageList` objects. One for the
multi-channel images and one for the segmentation masks. By specifying the
`outline_by` parameter, the outlines of cells can now be colored based on their
metadata.
The following example first generates a 3-channel composite images displaying
the expression of E-cadherin, CD3 and CD20 before coloring the cells' outlines
by their cell phenotype.
```{r outlining-all-cells}
plotPixels(image = cur_images,
mask = cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = c("Ecad", "CD3", "CD20"),
outline_by = "celltype",
bcg = list(Ecad = c(0, 5, 1),
CD3 = c(0, 5, 1),
CD20 = c(0, 5, 1)),
colour = list(celltype = metadata(spe)$color_vectors$celltype),
thick = TRUE)
```
Distinguishing individual cell phenotypes is nearly impossible in the images
above.
However, the `SpatialExperiment` object can be subsetted to only contain cells
of a single or few phenotypes. This allows the selective visualization of cell
outlines on composite images.
Here, we select all CD8+ T cells from the dataset and outline them on a 2-channel
composite image displaying the expression of CD3 and CD8a.
```{r outlining-CD8}
CD8 <- spe[,spe$celltype == "CD8"]
plotPixels(image = cur_images,
mask = cur_masks,
object = CD8,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = c("CD3", "CD8a"),
outline_by = "celltype",
bcg = list(CD3 = c(0, 5, 1),
CD8a = c(0, 5, 1)),
colour = list(celltype = c("CD8" = "white")),
thick = TRUE)
```
This type of visualization allows the quality control of two things: 1.
segmentation quality of individual cell types can be checked and 2. cell
phenotype quality can be visually assessed against expected marker expression.
## Adjusting plot annotations
The `cytomapper` package provides a number of function arguments to adjust the
visual appearance of figures that are shared between the `plotPixels` and
`plotCells` function.
For a full overview of the arguments please refer to `?plotting-param`.
We use the following example to highlight how to adjust the scale bar, the image
title, the legend appearance and the margin between images.
```{r adjusting-parameters}
plotPixels(cur_images,
colour_by = c("Ecad", "CD3", "CD20", "CD8a", "CD38", "Ki67"),
bcg = list(Ecad = c(0, 5, 1),
CD3 = c(0, 5, 1),
CD20 = c(0, 5, 1),
CD8a = c(0, 5, 1),
CD38 = c(0, 8, 1),
Ki67 = c(0, 5, 1)),
scale_bar = list(length = 100,
label = expression("100 " ~ mu * "m"),
cex = 0.7,
lwidth = 10,
colour = "grey",
position = "bottomleft",
margin = c(5,5),
frame = 3),
image_title = list(text = mcols(cur_images)$indication,
position = "topright",
colour = "grey",
margin = c(5,5),
font = 2,
cex = 2),
legend = list(colour_by.title.cex = 0.7,
margin = 10),
margin = 40)
```
## Displaying individual images
By default, all images are displayed on the same graphics device. This can be
useful when saving all images at once (see next section) to zoom into the
individual images instead of opening each image individually. However, when
displaying images in a markdown document these are more accessible when
visualized individually. For this, the `plotPixels` and `plotCells` function
accepts the `display` parameter that when set to `"single"` displays each
resulting image in its own graphics device:
```{r individual-images}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype",
colour = list(celltype = metadata(spe)$color_vectors$celltype),
display = "single",
legend = NULL)
```
## Saving and returning images
The final section addresses how to save composite images and how to return them
for integration with other plots.
The `plotPixels` and `plotCells` functions accept the `save_plot` argument which
takes a named list of the following entries: `filename` indicates the location
and file type of the image saved to disk; `scale` adjusts the resolution of the
saved image (this only needs to be adjusted for small images).
```{r saving-images}
plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype",
colour = list(celltype = metadata(spe)$color_vectors$celltype),
save_plot = list(filename = "data/celltype_image.png"))
```
The composite images (together with their annotation) can also be returned. In
the following code chunk we save two example plots to variables (`out1` and
`out2`).
```{r returning-images, results="hide", fig.show='hide'}
out1 <- plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = "celltype",
colour = list(celltype = metadata(spe)$color_vectors$celltype),
return_plot = TRUE)
out2 <- plotCells(cur_masks,
object = spe,
cell_id = "ObjectNumber", img_id = "sample_id",
colour_by = c("Ecad", "CD3", "CD20"),
exprs_values = "exprs",
return_plot = TRUE)
```
The composite images are stored in `out1$plot` and `out2$plot` and can be
converted into a graph object recognized by the
[cowplot](https://cran.r-project.org/web/packages/cowplot/vignettes/introduction.html)
package.
The final function call of the following chunk plots both object next to each
other.
```{r side-by-side-plot, message=FALSE}
library(cowplot)
library(gridGraphics)
p1 <- ggdraw(out1$plot, clip = "on")
p2 <- ggdraw(out2$plot, clip = "on")
plot_grid(p1, p2)
```
## Session Info
<details>
<summary>SessionInfo</summary>
```{r, echo = FALSE}
sessionInfo()
```
</details>