1010# ' @param k index of the scale of an image; by default (NULL), will auto-select
1111# ' scale in order to minimize memory-usage and blurring for a target size of
1212# ' 800 x 800px; use Inf to plot the lowest resolution available.
13+ # ' @param ch image channel(s) to be used for plotting (defaults to
14+ # ' 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]).
1321# '
1422# ' @return ggplot
1523# '
2331# ' plotImage(x, i=2, k=.))
2432# ' patchwork::wrap_plots(ms)
2533# '
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+ # '
2642# ' @import SpatialData
2743NULL
2844
3147# ' @export
3248plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
3349
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+
75+ # merge/manage image channels
76+ # if no colors and channels defined, return the first channel
77+ # ' @importFrom grDevices col2rgb
78+ # ' @noRd
79+ .chs2rgb <- \(a , ch , c = NULL , cl = NULL ) {
80+ cl <- .check_cl(cl , d <- dim(a )[1 ])
81+ if (length(ch ) > (n <- length(.DEFAULT_COLORS )) && is.null(c ))
82+ stop(" Only " , n , " default colors available, but" ,
83+ length(ch ), " are needed; please specify 'c'" )
84+ if (! is.null(c ) || (is.null(c ) && length(ch ) > 1 )) {
85+ if (is.null(c )) c <- .DEFAULT_COLORS [seq_along(ch )]
86+ c <- col2rgb(c )/ 255
87+ 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+ }
97+ }
98+ a <- pmin(b , 1 )
99+ } else {
100+ a <- a [rep(1 , 3 ), , ]
101+ }
102+ return (a )
103+ }
104+
105+ # check if an image is rgb or not
106+ # ' @importFrom SpatialData getZarrArrayPath
107+ # ' @importFrom Rarr zarr_overview
108+ # ' @noRd
109+ .get_img_dt <- \(a ) {
110+ pa <- getZarrArrayPath(a )
111+ df <- zarr_overview(pa , as_data_frame = TRUE )
112+ if (! is.null(dt <- df $ data_type )) return (dt )
113+ }
114+
115+ # normalize the image data given its data type
116+ # ' @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+ }
125+ return (a )
126+ }
127+
128+ # check if an image is RGB or not
129+ # (NOTE: some RGB channels are named 0, 1, 2)
130+ # ' @importFrom methods is
131+ # ' @noRd
132+ .is_rgb <- \(x ) {
133+ if (is(x , " ImageArray" ) &&
134+ ! is.null(md <- meta(x )))
135+ x <- md $ omero $ channels $ label
136+ if (! is.vector(x )) stop(" invalid 'x'" )
137+ is_len <- length(x ) == 3
138+ is_012 <- setequal(x , seq(0 , 2 ))
139+ is_rgb <- setequal(x , c(" r" , " g" , " b" ))
140+ return (is_len && (is_012 || is_rgb ))
141+ }
142+
143+ # check if channels are indices or channel names
144+ # ' @importFrom SpatialData channels
145+ # ' @noRd
146+ .ch_idx <- \(x , ch ) {
147+ if (is.null(ch ))
148+ return (1 )
149+ lbs <- channels(x )
150+ if (all(ch %in% lbs )) {
151+ return (match(ch , lbs ))
152+ } else if (! any(ch %in% lbs )) {
153+ warning(" Couldn't find some channels; picking first one(s)!" )
154+ return (1 )
155+ } else {
156+ warning(" Couldn't find channels; picking first one(s)!" )
157+ return (1 )
158+ }
159+ return (NULL )
160+ }
161+
34162.guess_scale <- \(x , w , h ) {
35163 n <- length(dim(x ))
36164 i <- ifelse(n == 3 , - 1 , TRUE )
@@ -39,22 +167,27 @@ plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
39167 which.min(d )
40168}
41169
42- .get_plot_data <- \(x , k = NULL , w = 800 , h = 800 ) {
170+ .get_img_data <- \(x , k = NULL , w = 800 , h = 800 ) {
43171 if (! is.null(k )) return (data(x , k ))
44172 data(x , .guess_scale(x , w , h ))
45173}
46174
47175# ' @importFrom methods as
48176# ' @importFrom grDevices rgb
49177# ' @importFrom DelayedArray realize
50- .df_i <- \(x , k = NULL ) {
51- a <- .get_plot_data(x , k )
52- a <- if (dim(a )[1 ] == 1 ) a [rep(1 ,3 ),,] else a
53- a <- realize(as(a , " DelayedArray" ))
54- img <- rgb(
55- maxColorValue = max(a ),
56- c(a [1 ,,]), c(a [2 ,,]), c(a [3 ,,]))
57- array (img , dim(a )[- 1 ])
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 )
181+ 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 )
190+ apply(a , c(2 , 3 ), \(. ) do.call(rgb , as.list(. )))
58191}
59192
60193.get_wh <- \(x , i , j ) {
@@ -75,13 +208,13 @@ plotSpatialData <- \() ggplot() + scale_y_reverse() + .theme
75208
76209# ' @rdname plotImage
77210# ' @export
78- setMethod ("plotImage ", "SpatialData", \(x, i=1, j=1, k=NULL) {
79- if (is.numeric(i ))
211+ setMethod ("plotImage ", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL, cl=NULL ) {
212+ if (is.numeric(i ))
80213 i <- imageNames(x )[i ]
81214 y <- image(x , i )
82- if (is.numeric(j ))
215+ if (is.numeric(j ))
83216 j <- CTname(y )[j ]
84- df <- .df_i(y , k )
217+ df <- .df_i(y , k , ch , c , cl )
85218 wh <- .get_wh(x , i , j )
86219 .gg_i(df , wh $ w , wh $ h )
87220})
0 commit comments