Skip to content

Commit ff48b03

Browse files
authored
Revert "in plotImage, add arguments to control contrast limits and saturation"
1 parent c9607a2 commit ff48b03

File tree

7 files changed

+62
-233
lines changed

7 files changed

+62
-233
lines changed

R/plotImage.R

Lines changed: 28 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@
1212
#' 800 x 800px; use Inf to plot the lowest resolution available.
1313
#' @param ch image channel(s) to be used for plotting (defaults to
1414
#' the first channel(s) available); use \code{channels()} to see
15-
#' which channels are available for a given \code{ImageArray}
16-
#' @param c character vector; colors to use for each channel.
17-
#' @param cl list of length-2 numeric vectors (non-negative, increasing);
18-
#' specifies channel-wise contrast limits - defaults to [0, 1] for all
19-
#' (ignored when \code{image(x, i)} is an RGB image;
20-
#' for convenience, any NULL = [0, 1], and n = [0, n]).
15+
#' which channels are available for a given \code{ImageArray}
16+
#'
17+
#' @param c plotting aesthetics; color
2118
#'
2219
#' @return ggplot
2320
#'
@@ -31,14 +28,6 @@
3128
#' plotImage(x, i=2, k=.))
3229
#' patchwork::wrap_plots(ms)
3330
#'
34-
#' # custom colors
35-
#' cmy <- c("cyan", "magenta", "yellow")
36-
#' plotSpatialData() + plotImage(x, c=cmy)
37-
#'
38-
#' # contrast limits
39-
#' plotSpatialData() + plotImage(x, c=cmy,
40-
#' cl=list(c(0.2,1), c(0,0.8), c(0,1)))
41-
#'
4231
#' @import SpatialData
4332
NULL
4433

@@ -47,53 +36,22 @@ NULL
4736
#' @export
4837
plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
4938

50-
.check_cl <- \(cl, d) {
51-
if (is.null(cl)) {
52-
# default to [0, 1] for all channels
53-
cl <- replicate(d, c(0, 1), FALSE)
54-
} else {
55-
# should be a list with as many elements as channels
56-
if (!is.list(cl)) stop("'cl' should be a list")
57-
if (length(cl) != d) stop("'cl' should be of length ", d)
58-
for (. in seq_len(d)) {
59-
# replace NULL by [0, 1] & n by [0, n]
60-
if (is.null(cl[[.]])) cl[[.]] <- c(0, 1)
61-
if (length(cl[[.]]) == 1) {
62-
if (cl[[.]] < 0) stop("scalar 'cl' can't be < 0")
63-
cl[[.]] <- c(0, cl[[.]])
64-
}
65-
}
66-
# elements should be length-2, numeric, non-negative, increasing
67-
.f <- \(.) length(.) == 2 && is.numeric(.) && all(. >= 0) && .[2] > .[1]
68-
if (!all(vapply(cl, .f, logical(1))))
69-
stop("elements of 'cl' should be length-2,",
70-
" non-negative, increasing numeric vectors")
71-
}
72-
return(cl)
73-
}
74-
7539
# merge/manage image channels
7640
# if no colors and channels defined, return the first channel
7741
#' @importFrom grDevices col2rgb
7842
#' @noRd
79-
.chs2rgb <- \(a, ch, c=NULL, cl=NULL) {
80-
cl <- .check_cl(cl, d <- dim(a)[1])
43+
.manage_channels <- \(a, ch, c=NULL){
8144
if (length(ch) > (n <- length(.DEFAULT_COLORS)) && is.null(c))
8245
stop("Only ", n, " default colors available, but",
8346
length(ch), " are needed; please specify 'c'")
8447
if (!is.null(c) || (is.null(c) && length(ch) > 1)) {
8548
if (is.null(c)) c <- .DEFAULT_COLORS[seq_along(ch)]
8649
c <- col2rgb(c)/255
8750
b <- array(0, dim=c(3, dim(a)[-1]))
88-
for (i in seq_len(d)) {
89-
for (j in seq_len(3)) {
90-
rgb <- a[i,,,drop=FALSE]*c[j,i]
91-
# apply upper contrast lim.
92-
rgb <- rgb*(1/cl[[i]][2])
93-
b[j,,] <- b[j,,,drop=FALSE] + rgb
94-
# apply lower contrast lim.
95-
b[j,,][b[j,,] < cl[[i]][1]] <- 0
96-
}
51+
for (i in seq_len(dim(a)[1])) {
52+
b[1,,] <- b[1,,,drop=FALSE] + a[i,,,drop=FALSE]*c[1,i]
53+
b[2,,] <- b[2,,,drop=FALSE] + a[i,,,drop=FALSE]*c[2,i]
54+
b[3,,] <- b[3,,,drop=FALSE] + a[i,,,drop=FALSE]*c[3,i]
9755
}
9856
a <- pmin(b, 1)
9957
} else {
@@ -106,22 +64,21 @@ plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
10664
#' @importFrom SpatialData getZarrArrayPath
10765
#' @importFrom Rarr zarr_overview
10866
#' @noRd
109-
.get_img_dt <- \(a) {
67+
.get_image_dtype <- \(a) {
11068
pa <- getZarrArrayPath(a)
11169
df <- zarr_overview(pa, as_data_frame=TRUE)
11270
if (!is.null(dt <- df$data_type)) return(dt)
11371
}
11472

11573
# normalize the image data given its data type
11674
#' @noRd
117-
.norm_ia <- \(a, dt) {
118-
d <- dim(a)[1]
119-
if (dt %in% names(.DTYPE_MAX_VALUES)) {
120-
a <- a / .DTYPE_MAX_VALUES[dt]
121-
} else if (max(a) > 1) {
122-
for (i in seq_len(d))
123-
a[i,,] <- a[i,,] / max(a[i,,])
124-
}
75+
.normalize_image_array <- \(a, dt){
76+
if (dt %in% names(.DTYPE_MAX_VALUES)) {
77+
a <- a/.DTYPE_MAX_VALUES[dt]
78+
} else if (max(a) > 1) {
79+
for (i in seq_len(dim(a)[1]))
80+
a[i,,] <- a[i,,]/max(a[i,,])
81+
}
12582
return(a)
12683
}
12784

@@ -167,26 +124,24 @@ plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
167124
which.min(d)
168125
}
169126

170-
.get_img_data <- \(x, k=NULL, w=800, h=800) {
127+
.get_plot_data <- \(x, k=NULL, w=800, h=800) {
171128
if (!is.null(k)) return(data(x, k))
172129
data(x, .guess_scale(x, w, h))
173130
}
174131

175132
#' @importFrom methods as
176133
#' @importFrom grDevices rgb
177134
#' @importFrom DelayedArray realize
178-
.df_i <- \(x, k=NULL, ch=NULL, c=NULL, cl=NULL) {
179-
a <- .get_img_data(x, k)
180-
ch <- .ch_idx(x, ch)
135+
.df_i <- \(x, k=NULL, ch=NULL, c=NULL) {
136+
a <- .get_plot_data(x, k)
137+
ch_i <- .ch_idx(x, ch)
138+
if (!.is_rgb(x))
139+
a <- a[ch_i, , , drop=FALSE]
140+
dt <- .get_image_dtype(a)
141+
a <- realize(as(a, "DelayedArray"))
142+
a <- .normalize_image_array(a, dt)
181143
if (!.is_rgb(x))
182-
a <- a[ch, , , drop=FALSE]
183-
dt <- .get_img_dt(a)
184-
a <- as(a, "DelayedArray")
185-
a <- .norm_ia(realize(a), dt)
186-
# enter when image isn't RGB already, either
187-
# custom colors or contrasts are specified
188-
if (!.is_rgb(x) || !is.null(c) || !is.null(cl))
189-
a <- .chs2rgb(a, ch, c, cl)
144+
a <- .manage_channels(a, ch_i, c)
190145
apply(a, c(2, 3), \(.) do.call(rgb, as.list(.)))
191146
}
192147

@@ -208,13 +163,13 @@ plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
208163

209164
#' @rdname plotImage
210165
#' @export
211-
setMethod("plotImage", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL, cl=NULL) {
166+
setMethod("plotImage", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL) {
212167
if (is.numeric(i))
213168
i <- imageNames(x)[i]
214169
y <- image(x, i)
215170
if (is.numeric(j))
216171
j <- CTname(y)[j]
217-
df <- .df_i(y, k, ch, c, cl)
172+
df <- .df_i(y, k, ch, c)
218173
wh <- .get_wh(x, i, j)
219174
.gg_i(df, wh$w, wh$h)
220175
})

inst/NEWS

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
changes in version 0.99.2
22

3-
- in 'plotImage', added support to visualize channels of choice,
4-
as well as 'cl' argument to control constrast limits
3+
- in 'plotImage', added support to visualize channels of choice
54
- updated vignette to include corresponding examples
65

76
changes in version 0.99.1

man/plotImage.Rd

Lines changed: 2 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-plotArray.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ test_that(".guess_scale", {
2222
dim <- lapply(c(6, 3), \(.) c(3, rep(., 2))), \(.)
2323
array(sample(seq_len(255), prod(.), replace=TRUE), dim=.)))
2424
# manual scale
25-
expect_identical(.get_img_data(img, k=1), lys[[1]])
26-
expect_identical(.get_img_data(img, k=2), lys[[2]])
25+
expect_identical(.get_plot_data(img, k=1), lys[[1]])
26+
expect_identical(.get_plot_data(img, k=2), lys[[2]])
2727
# automatic scale
28-
expect_identical(.get_img_data(img, k=NULL, w=5, h=7), lys[[1]])
29-
expect_identical(.get_img_data(img, k=NULL, w=2, h=2), lys[[2]])
28+
expect_identical(.get_plot_data(img, k=NULL, w=5, h=7), lys[[1]])
29+
expect_identical(.get_plot_data(img, k=NULL, w=2, h=2), lys[[2]])
3030
})
3131

3232
test_that("plotImage()", {

tests/testthat/test-plotImage.R

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -24,84 +24,7 @@ test_that(".ch_idx()", {
2424
expect_warning(expect_equal(.ch_idx(image(x,1), ch=99), 1))
2525
})
2626

27-
test_that(".check_cl", {
28-
# valid
29-
n <- sample(seq(3, 9), 1)
30-
v <- replicate(n, sort(runif(2)), FALSE)
31-
expect_identical(.check_cl(v, n), v)
32-
# single NULL
33-
n <- sample(seq(3, 9), 1)
34-
l <- .check_cl(NULL, n)
35-
expect_is(l, "list")
36-
expect_identical(l, replicate(n, c(0, 1), FALSE))
37-
# one NULL, rest scalar
38-
n <- sample(seq(3, 9), 1)
39-
i <- sample(n, 1)
40-
. <- replicate(n, NULL, FALSE)
41-
.[[i]] <- v <- c(0.2, 0.8)
42-
l <- .check_cl(., n)
43-
expect_is(l, "list")
44-
expect_identical(l[[i]], v)
45-
expect_identical(l[-i], replicate(n-1, c(0, 1), FALSE))
46-
# invalid
47-
expect_error(.check_cl(c(0.2, 0.4, 0.6), 3)) # non-list
48-
expect_error(.check_cl(as.list(seq_len(4)), 3)) # wrong length
49-
expect_error(.check_cl(list(NULL, NULL, c(-1, 1)), 3)) # negative entry
50-
expect_error(.check_cl(as.list(letters[seq_len(3)]), 3)) # non-numeric
51-
expect_error(.check_cl(list(NULL, NULL, c(1, 0)), 3)) # decreasing
52-
expect_error(.check_cl(list(NULL, NULL, -1), 3)) # negative scalar
53-
expect_error(.check_cl(list(NULL, NULL, 0), 3)) # zero scalar
27+
# TODO: any tests for image array normalization ?
28+
test_that(".normalize_image_array", {
29+
skip()
5430
})
55-
56-
dir.create(td <- tempfile())
57-
pa <- unzip_spd_demo(zipname="steinbock_io.zip", dest=td, source="biocOSN")
58-
x <- readSpatialData(pa, images=1, labels=FALSE, points=FALSE, shapes=FALSE, tables=FALSE)
59-
a <- data(image(x)[seq_len(3), seq_len(100), seq_len(100)], 1)
60-
61-
test_that(".get_img_dt", {
62-
da <- (za <- data(image(x)))[1,,]
63-
dt_za <- .get_img_dt(za) # from 'ZarrArray'
64-
dt_da <- .get_img_dt(da) # from 'DelayedArray'
65-
expect_is(dt_za, "character")
66-
expect_identical(dt_za, dt_da)
67-
})
68-
69-
test_that(".norm_ia", {
70-
# valid data type
71-
dt <- .get_img_dt(a)
72-
b <- .norm_ia(realize(a), dt)
73-
expect_equal(
74-
apply(b, 1, range),
75-
replicate(3, c(0, 1)))
76-
# invalid data type
77-
b <- .norm_ia(realize(a), "")
78-
expect_equal(
79-
apply(b, 1, range),
80-
replicate(3, c(0, 1)))
81-
})
82-
83-
test_that(".chs2rgb", {
84-
dt <- .get_img_dt(a)
85-
ch <- seq_len(d <- dim(a)[1])
86-
a <- .norm_ia(realize(a), dt)
87-
# no colors, no contrasts
88-
b <- .chs2rgb(a, ch)
89-
expect_identical(a, b)
90-
# colors
91-
cmy <- c("cyan", "magenta", "yellow")
92-
b <- .chs2rgb(a, ch, c=cmy)
93-
expect_equal(dim(a), dim(b))
94-
expect_equal(
95-
apply(b, 1, range),
96-
replicate(d, c(0, 1)))
97-
# lower contrast lim.
98-
lim <- list(c(0.5, 1), NULL, NULL)
99-
b <- .chs2rgb(a, ch, cl=lim)
100-
expect_identical(b[-1,,], a[-1,,])
101-
expect_true(sum(b[1,,] == 0) > sum(a[1,,] == 0))
102-
# upper contrast lim.
103-
lim <- list(c(0, 0.5), NULL, NULL)
104-
b <- .chs2rgb(a, ch, cl=lim)
105-
fac <- mean(b[1,,]/a[1,,], na.rm=TRUE)
106-
expect_equal(fac, 2, tolerance=0.05)
107-
})

vignettes/SpatialData.plot.Rmd

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -276,30 +276,15 @@ pa <- unzip_spd_demo(
276276
x <- readSpatialData(pa, anndataR=FALSE)
277277
```
278278

279-
### channels
279+
Plotting with multiple image channels.
280280

281-
```{r steinbock-ch}
282-
plotSpatialData() + plotImage(x,
281+
```{r steinbock-plot}
282+
plotSpatialData() + plotImage(x,
283283
i="Patient3_003_image",
284284
ch=c(6, 22, 39),
285285
c=c("blue", "cyan", "yellow"))
286286
```
287287

288-
### contrasts
289-
290-
```{r steinbock-cl, fig.width=9, fig.height=3}
291-
i <- image(x, "Patient3_003_image")
292-
image(x, "crop") <- i[, 200:400, 200:400]
293-
lapply(list(c(0.2, 1), c(0, 0.8), c(0, 1.2)), \(.) {
294-
plotSpatialData() + plotImage(x,
295-
i="crop",
296-
ch=c(6, 22, 39),
297-
cl=list(1, 1, .),
298-
c=c("blue", "cyan", "yellow")) +
299-
ggtitle(sprintf("[%s, %s]", .[1], .[2]))
300-
}) |> wrap_plots(nrow=1)
301-
```
302-
303288
# Masking
304289

305290
Back to blobs...

vignettes/SpatialData.plot.html

Lines changed: 21 additions & 41 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)