Skip to content

Commit b2e4d83

Browse files
committed
more docs
1 parent 0cadb85 commit b2e4d83

File tree

4 files changed

+215
-51
lines changed

4 files changed

+215
-51
lines changed

R/plot-image.R

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#' @name sd_plot_image
2+
#' @title Plot `ImageArray`
3+
#'
4+
#' @examples
5+
#' pa <- file.path("extdata", "blobs.zarr")
6+
#' pa <- system.file(pa, package="SpatialData")
7+
#' sd <- readSpatialData(pa)
8+
#'
9+
#' sd_plot() + sd_plot_image(sd)
10+
#'
11+
#' # custom colors
12+
#' pal <- c("cyan", "magenta", "gold")
13+
#' sd_plot() + sd_plot_image(sd, c=pal)
14+
#'
15+
#' @importFrom methods as
16+
#' @importFrom grDevices rgb
17+
#' @importFrom DelayedArray realize
18+
#' @export
19+
sd_plot_image <- \(x, i=1, k=NULL, c=NULL, ch=NULL, cl=NULL, w=800, h=800) {
20+
#x <- sd; i <- 1; w=h=800; ch <- cl <- c <- NULL; ch <- 2
21+
ia <- x@images[[i]]
22+
ch <- .ch_idx(ia, ch)
23+
if (is.null(k))
24+
k <- .guess_scale(ia, w, h)
25+
a <- data(ia, k)
26+
if (!.is_rgb(ia))
27+
a <- a[ch, , , drop=FALSE]
28+
dt <- .get_dt(a)
29+
a <- as(a, "DelayedArray")
30+
a <- .norm_ia(realize(a), dt)
31+
# enter when image isn't RGB already, either
32+
# custom colors or contrasts are specified
33+
if (!.is_rgb(ia) || !is.null(c) || !is.null(cl))
34+
a <- .chs2rgb(a, ch, c, cl)
35+
a <- apply(a, c(2, 3), \(.) do.call(rgb, as.list(.)))
36+
w <- c(0, dim(ia)[3])
37+
h <- c(0, dim(ia)[2])
38+
pal <- if (!.is_rgb(ia) && dim(ia)[1] > 1) {
39+
nms <- channels(ia)[ch]
40+
pal <- if (is.null(c)) .DEFAULT_COLORS else c
41+
setNames(pal[seq_along(ch)], nms)
42+
}
43+
lgd <- if (!is.null(pal)) list(
44+
guides(col=guide_legend(override.aes=list(alpha=1, size=2))),
45+
scale_color_identity(NULL, guide="legend", labels=names(pal)),
46+
geom_point(aes(col=.data$foo), data.frame(foo=pal), x=0, y=0, alpha=0))
47+
list(lgd,
48+
scale_x_continuous(limits=w), scale_y_reverse(limits=rev(h)),
49+
annotation_raster(a, w[2],w[1], h[1],h[2], interpolate=FALSE))
50+
}
51+
52+
# default colors (from ImageJ/Fiji)
53+
.DEFAULT_COLORS <- c("red", "green", "blue", "gray", "cyan", "magenta", "yellow")
54+
55+
# image data type factors (max values)
56+
# TODO: add more cases from other data types
57+
# https://doc.embedded-wizard.de/uint-type
58+
.DTYPE_MAX_VALUES <- c(
59+
"uint8"=2**8-1, "uint16"=2**16-1,
60+
"uint32"=2**32-1, "uint64"=2**64-1)
61+
62+
.ch_idx <- \(x, ch) {
63+
if (is.null(ch))
64+
return(1)
65+
lbs <- channels(x)
66+
if (is.integer(ch)) {
67+
return(lbs[ch])
68+
} else if (all(ch %in% lbs)) {
69+
return(match(ch, lbs))
70+
} else if (!any(ch %in% lbs)) {
71+
warning("Couldn't find some channels; picking first one(s)!")
72+
return(1)
73+
} else {
74+
warning("Couldn't find channels; picking first one(s)!")
75+
return(1)
76+
}
77+
return(NULL)
78+
}
79+
80+
.check_cl <- \(cl, d) {
81+
if (is.null(cl)) {
82+
# default to [0, 1] for all channels
83+
cl <- replicate(d, c(0, 1), FALSE)
84+
} else {
85+
# should be a list with as many elements as channels
86+
if (!is.list(cl)) stop("'cl' should be a list")
87+
if (length(cl) != d) stop("'cl' should be of length ", d)
88+
for (. in seq_len(d)) {
89+
# replace NULL by [0, 1] & n by [0, n]
90+
if (is.null(cl[[.]])) cl[[.]] <- c(0, 1)
91+
if (length(cl[[.]]) == 1) {
92+
if (cl[[.]] < 0) stop("scalar 'cl' can't be < 0")
93+
cl[[.]] <- c(0, cl[[.]])
94+
}
95+
}
96+
# elements should be length-2, numeric, non-negative, increasing
97+
.f <- \(.) length(.) == 2 && is.numeric(.) && all(. >= 0) && .[2] > .[1]
98+
if (!all(vapply(cl, .f, logical(1))))
99+
stop("elements of 'cl' should be length-2,",
100+
" non-negative, increasing numeric vectors")
101+
}
102+
return(cl)
103+
}
104+
105+
# merge/manage image channels;
106+
# if no colors and channels defined,
107+
# return the first channel
108+
#' @importFrom grDevices col2rgb
109+
.chs2rgb <- \(a, ch, c=NULL, cl=NULL) {
110+
cl <- .check_cl(cl, d <- dim(a)[1])
111+
if (length(ch) > (n <- length(.DEFAULT_COLORS)) && is.null(c))
112+
stop("Only ", n, " default colors available, but",
113+
length(ch), " are needed; please specify 'c'")
114+
if (!is.null(c) || (is.null(c) && length(ch) > 1)) {
115+
if (is.null(c)) c <- .DEFAULT_COLORS[seq_along(ch)]
116+
c <- col2rgb(c)/255
117+
b <- array(0, dim=c(3, dim(a)[-1]))
118+
for (i in seq_len(d)) {
119+
for (j in seq_len(3)) {
120+
rgb <- a[i,,,drop=FALSE]*c[j,i]
121+
# apply upper contrast lim.
122+
rgb <- rgb*(1/cl[[i]][2])
123+
b[j,,] <- b[j,,,drop=FALSE] + rgb
124+
# apply lower contrast lim.
125+
b[j,,][b[j,,] < cl[[i]][1]] <- 0
126+
}
127+
}
128+
a <- pmin(b, 1)
129+
} else {
130+
a <- a[rep(1, 3), , ]
131+
}
132+
return(a)
133+
}
134+
135+
.guess_scale <- \(x, w, h) {
136+
n <- length(dim(x))
137+
i <- ifelse(n == 3, -1, TRUE)
138+
d <- vapply(x@data, dim, numeric(n))
139+
d <- apply(d, 2, \(.) sum(abs(.[i]-c(h, w))))
140+
which.min(d)
141+
}
142+
143+
# check if an image is rgb or not
144+
#' @importFrom Rarr zarr_overview
145+
#' @noRd
146+
.get_dt <- \(a) {
147+
pa <- a@seed@zarr_array_path
148+
df <- zarr_overview(pa, as_data_frame=TRUE)
149+
if (!is.null(dt <- df$data_type)) return(dt)
150+
}
151+
152+
.norm_ia <- \(a, dt) {
153+
d <- dim(a)[1]
154+
if (dt %in% names(.DTYPE_MAX_VALUES)) {
155+
a <- a / .DTYPE_MAX_VALUES[dt]
156+
} else if (max(a) > 1) {
157+
for (i in seq_len(d))
158+
a[i,,] <- a[i,,] / max(a[i,,])
159+
}
160+
return(a)
161+
}
162+
163+
# check if an image is RGB or not
164+
# (NOTE: some RGB channels are named 0, 1, 2)
165+
#' @importFrom S7 S7_inherits
166+
.is_rgb <- \(x) {
167+
stopifnot("invalid 'x'"=S7_inherits(x, ImageArray) && !is.null(zattrs(x)))
168+
x <- channels(x)
169+
is_len <- length(x) == 3
170+
is_012 <- setequal(x, seq(0, 2))
171+
is_rgb <- setequal(x, c("r", "g", "b"))
172+
return(is_len && (is_012 || is_rgb))
173+
}

R/plot.R

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
#' pal <- c("cyan", "magenta", "gold")
1818
#' sd_plot() + sd_plot_image(sd, c=pal)
1919
#'
20+
#' @seealso
21+
#' \code{\link{sd_plot_image}}
22+
#' \code{\link{sd_plot_point}}
23+
#' \code{\link{sd_plot_shape}}
24+
#'
2025
#' @import ggplot2
2126
#' @export
2227
sd_plot <- \() ggplot() +
@@ -151,43 +156,3 @@ sd_plot <- \() ggplot() +
151156
is_rgb <- setequal(x, c("r", "g", "b"))
152157
return(is_len && (is_012 || is_rgb))
153158
}
154-
155-
options(sd.csr="global")
156-
157-
#' @rdname sd_plot
158-
#' @importFrom methods as
159-
#' @importFrom grDevices rgb
160-
#' @importFrom DelayedArray realize
161-
#' @export
162-
sd_plot_image <- \(x, i=1, k=NULL, c=NULL, ch=NULL, cl=NULL, w=800, h=800) {
163-
#x <- sd; i <- 1; w=h=800; ch <- cl <- c <- NULL
164-
ch <- .ch_idx(x, ch)
165-
ia <- x@images[[i]]
166-
if (is.null(k))
167-
k <- .guess_scale(ia, w, h)
168-
a <- data(ia, k)
169-
if (!.is_rgb(ia))
170-
a <- a[ch, , , drop=FALSE]
171-
dt <- .get_dt(a)
172-
a <- as(a, "DelayedArray")
173-
a <- .norm_ia(realize(a), dt)
174-
# enter when image isn't RGB already, either
175-
# custom colors or contrasts are specified
176-
if (!.is_rgb(ia) || !is.null(c) || !is.null(cl))
177-
a <- .chs2rgb(a, ch, c, cl)
178-
a <- apply(a, c(2, 3), \(.) do.call(rgb, as.list(.)))
179-
w <- c(0, dim(ia)[3])
180-
h <- c(0, dim(ia)[2])
181-
pal <- if (!.is_rgb(ia) && dim(ia)[1] > 1) {
182-
nms <- channels(ia)[ch]
183-
pal <- if (is.null(c)) .DEFAULT_COLORS else c
184-
setNames(pal[seq_along(ch)], nms)
185-
}
186-
lgd <- if (!is.null(pal)) list(
187-
guides(col=guide_legend(override.aes=list(alpha=1, size=2))),
188-
scale_color_identity(NULL, guide="legend", labels=names(pal)),
189-
geom_point(aes(col=.data$foo), data.frame(foo=pal), x=0, y=0, alpha=0))
190-
list(lgd,
191-
scale_x_continuous(limits=w), scale_y_reverse(limits=rev(h)),
192-
annotation_raster(a, w[2],w[1], h[1],h[2], interpolate=FALSE))
193-
}

man/sd_plot.Rd

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

man/sd_plot_image.Rd

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

0 commit comments

Comments
 (0)