Skip to content

Commit acde5c3

Browse files
HelenaLCcsonesonmbstadler
authored
(v0.99.7) scalebar, 3/4D support, physical space
- added 'scalebar' display - near-comprehensive unit testing - default to random label coloring by instance - multi-scale adjustment (= mapping to physical space) - address R CMD check notes/warnings toward Bioc submission - support for 3D (z) and 4D (t) through slicing or max-projection - drop various code bits in favor of new 'spatialdataR' functionality --------- Co-authored-by: Charlotte Soneson <charlottesoneson@gmail.com> Co-authored-by: Michael Stadler <stadler.michael@gmail.com>
1 parent cd7aeb2 commit acde5c3

24 files changed

Lines changed: 735 additions & 2546 deletions

.github/workflows/R-CMD-check.yaml

Lines changed: 0 additions & 50 deletions
This file was deleted.

.github/workflows/pkgdown.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
33
on:
44
push:
5-
branches: [main, master]
5+
branches: [main]
66
pull_request:
77
release:
88
types: [published]
@@ -28,6 +28,11 @@ jobs:
2828
- uses: r-lib/actions/setup-pandoc@v2
2929

3030
- uses: r-lib/actions/setup-r@v2
31+
with:
32+
r-version: 'devel'
33+
http-user-agent: 'release'
34+
extra-repositories: 'https://bioc.r-universe.dev'
35+
use-public-rspm: true
3136

3237
- uses: r-lib/actions/setup-r-dependencies@v2
3338
with:
@@ -44,4 +49,4 @@ jobs:
4449
with:
4550
clean: false
4651
branch: gh-pages
47-
folder: docs
52+
folder: docs

.github/workflows/r-universe.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Test R-universe
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
build:
10+
name: R-universe testing
11+
uses: r-universe-org/workflows/.github/workflows/build.yml@v3
12+
with:
13+
universe: bioc
14+
organization: bioconductor

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
*.Rproj
66
*html
77
docs
8+
tests/testthat/Rplots.pdf

DESCRIPTION

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: SpatialData.plot
22
Title: SpatialData visualization
33
Depends: R (>= 4.6), spatialdataR
4-
Version: 0.99.6
4+
Version: 0.99.7
55
Description: Visualization suit for 'spatialdataR'. Current functionality
66
includes handling of multiscale 'images', visualizing 'labels', 'points',
77
and 'shapes'. For the latter, POINT, POLYGON, and MULTIPOLYGON geometries
@@ -35,19 +35,17 @@ Imports:
3535
rlang,
3636
sf,
3737
S4Vectors,
38-
SingleCellExperiment,
39-
Rarr,
40-
ZarrArray
38+
SingleCellExperiment
4139
Suggests:
4240
BiocStyle,
4341
ggnewscale,
4442
knitr,
4543
magick,
4644
patchwork,
47-
Rgraphviz,
4845
SpatialData.data,
4946
testthat,
50-
vdiffr
47+
vdiffr,
48+
ZarrArray
5149
Remotes:
5250
HelenaLC/spatialdataR,
5351
HelenaLC/SpatialData.data

NAMESPACE

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Generated by roxygen2: do not edit by hand
22

33
export(plotSpatialData)
4+
export(scalebar)
45
exportMethods(plotImage)
56
exportMethods(plotLabel)
67
exportMethods(plotPoint)
@@ -10,10 +11,9 @@ importFrom(DelayedArray,realize)
1011
importFrom(MatrixGenerics,rowQuantiles)
1112
importFrom(S4Vectors,metadata)
1213
importFrom(SingleCellExperiment,colData)
13-
importFrom(SingleCellExperiment,int_colData)
14-
importFrom(SingleCellExperiment,int_metadata)
1514
importFrom(ggforce,geom_circle)
1615
importFrom(ggplot2,aes)
16+
importFrom(ggplot2,annotate)
1717
importFrom(ggplot2,annotation_raster)
1818
importFrom(ggplot2,coord_fixed)
1919
importFrom(ggplot2,coord_sf)
@@ -38,6 +38,7 @@ importFrom(ggplot2,theme_bw)
3838
importFrom(ggplot2,unit)
3939
importFrom(grDevices,col2rgb)
4040
importFrom(grDevices,colorRampPalette)
41+
importFrom(grDevices,colors)
4142
importFrom(grDevices,hcl.colors)
4243
importFrom(methods,as)
4344
importFrom(methods,is)
@@ -46,6 +47,7 @@ importFrom(sf,st_as_sf)
4647
importFrom(sf,st_buffer)
4748
importFrom(sf,st_coordinates)
4849
importFrom(sf,st_geometry_type)
50+
importFrom(spatialdataR,"element<-")
4951
importFrom(spatialdataR,channels)
5052
importFrom(spatialdataR,data_type)
5153
importFrom(spatialdataR,transform)

R/plotFrame.R

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
#'
55
#' @param x \code{SpatialData} object.
66
#' @param i character string or index; the label element to plot.
7-
#' @param assay character string; in case of \code{c} denoting a row name,
8-
#' specifies which \code{assay} data to use (see \code{\link{valTable}}).
7+
#' @param j index or name of target coordinate system.
8+
#' @param assay character string; in case of \code{c}
9+
#' denoting a row name, specifies which \code{assay}
10+
#' data to use (see \code{\link[spatialdataR]{getTable}}).
911
#' (ignored when \code{x} is a \code{SpatialDataPoint}).
12+
#' @param ... option aesthetic arguments passed \code{geom_sf}.
1013
#'
1114
#' @examples
1215
#' x <- file.path("extdata", "blobs.zarr")
@@ -36,25 +39,24 @@ NULL
3639

3740
#' @importFrom sf st_as_sf st_coordinates st_geometry_type st_buffer
3841
#' @importFrom ggplot2 aes theme scale_type geom_sf coord_sf
39-
#' @importFrom spatialdataR transform
42+
#' @importFrom spatialdataR transform element<-
4043
#' @importFrom ggforce geom_circle
4144
#' @importFrom methods is
4245
#' @importFrom utils tail
43-
.plot <- \(x, y, key=NULL, n=Inf, assay=1, i=1, ...) {
46+
.plot <- \(x, y, key=NULL, n=NULL, assay=1, i=1, ...) {
4447
if (is(y, "SpatialDataPoint")) {
4548
if (!is.null(key)) {
49+
stopifnot(is.character(key), nchar(key) > 0)
4650
fk <- feature_key(y)
47-
y<- dplyr::filter(y, .data[[fk]] %in% key)
51+
y <- dplyr::filter(y, .data[[fk]] %in% key)
52+
if (!length(y)) stop("no instances of specified 'key'(s)")
4853
}
4954
}
50-
if (is.finite(n)) {
55+
if (!is.null(n)) {
56+
stopifnot(is.numeric(n), length(n) == 1, n > 0)
5157
n <- min(length(y), n)
5258
y <- y[sample(length(y), n)]
53-
if (is(y, "SpatialDataShape")) {
54-
shape(x, i) <- y
55-
} else {
56-
point(x, i) <- y
57-
}
59+
element(x, i) <- y
5860
}
5961
df <- st_as_sf(data(y))
6062
aes <- aes()

R/plotImage.R

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
#'
55
#' @description ...
66
#'
7-
#' @param x \code{\link{SpatialData}} object.
7+
#' @param x \code{\link[spatialdataR]{SpatialData}} object.
88
#' @param i element to use from a given layer.
9-
#' @param j name of target coordinate system.
10-
#' @param k index of the scale of an image; by default (NULL), will auto-select
9+
#' @param j index or name of target coordinate system.
10+
#' @param k index of the scale to render; 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.
1313
#' @param ch image channel(s) to be used for plotting (defaults to
@@ -18,6 +18,8 @@
1818
#' specifies channel-wise contrast limits - defaults to [0, 1] for all
1919
#' (ignored when \code{image(x, i)} is an RGB image;
2020
#' for convenience, any NULL = [0, 1], and n = [0, n]).
21+
#' @param t,z integer scalar to indicate a specific time- or z-slice;
22+
#' if left unspecified (default NULL), will perform a max-projection.
2123
#'
2224
#' @return ggplot
2325
#'
@@ -84,7 +86,7 @@ NULL
8486
n <- length(c)
8587
if (n < d) stop(
8688
"Only ", n, " default colors available, ",
87-
"but", d, " are needed; please specify 'c'")
89+
"but ", d, " are needed; please specify 'c'")
8890
c <- c[seq_len(d)]
8991
}
9092
}
@@ -117,8 +119,8 @@ NULL
117119
if (dt %in% names(.DTYPE_MAX_VALUES)) {
118120
a <- a / .DTYPE_MAX_VALUES[dt]
119121
} else if (max(a) > 1) {
120-
for (i in seq_len(d))
121-
a[i,,] <- a[i,,] / max(a[i,,])
122+
maxs <- apply(a, 1, max)
123+
a <- sweep(a, MARGIN = 1, STATS = maxs, FUN = "/")
122124
}
123125
return(a)
124126
}
@@ -159,59 +161,75 @@ NULL
159161
#' @importFrom methods as
160162
#' @importFrom DelayedArray realize
161163
#' @importFrom spatialdataR data_type
162-
.df_i <- \(x, k=NULL, ch=NULL, c=NULL, cl=NULL) {
163-
a <- .get_multiscale_data(x, k)
164-
a <- a[.ch_idx(x, ch),,,drop=FALSE]
164+
.df_i <- \(x, k=NULL, ch=NULL, t=NULL, c=NULL, cl=NULL, z=NULL) {
165+
a <- .get_ms_data(x, k)
166+
axisNames <- axes(x, "name")
167+
# 2D max-projection
168+
a <- .project(x, a)
169+
axisNames <- axisNames[axisNames != "z"]
170+
ti <- which(axisNames == "t")
171+
tn <- length(ti)
172+
# subset channels and timepoint of interest
173+
if (tn) {
174+
if (is.null(t)) {
175+
t <- 1
176+
} else if (length(t) > 1) {
177+
stop("Only a single timepoint can be selected")
178+
}
179+
}
180+
a <- .subset_array_by_axes(a=a, axisNames=axisNames,
181+
c=.ch_idx(x, ch), t=t, drop=FALSE)
182+
# remove time axis if it exists
183+
if (tn) {
184+
dim(a) <- dim(a)[axisNames != "t"]
185+
axisNames <- axisNames[-ti]
186+
}
187+
# if no channel axis, add dummy axis
188+
if (!("c" %in% axisNames)) {
189+
dim(a) <- c(1, dim(a))
190+
axisNames <- c("c", axisNames)
191+
}
165192
a <- .norm_ia(a, data_type(x))
193+
# color merging & contrasts
166194
a <- .prep_ia(a, c, cl)
167195
}
168196

169-
#' @importFrom spatialdataR transform
170-
.get_wh <- \(x) {
171-
wh <- metadata(x)$wh
172-
if (!is.null(wh)) {
173-
df <- data.frame(x=wh[[1]], y=wh[[2]])
174-
} else {
175-
ds <- dim(data(x, 1))
176-
df <- data.frame(x=c(0, ds[3]), y=c(0, ds[2]))
177-
}
178-
list(w=df[, 1], h=df[, 2])
179-
}
180-
197+
#' @importFrom rlang .data
181198
#' @importFrom ggplot2 guides geom_point geom_blank annotation_raster
182199
#' @importFrom ggplot2 scale_color_identity scale_x_continuous scale_y_reverse
183200
.gg_i <- \(x, w, h, pal=NULL) {
184201
l <- if (!is.null(names(pal))) list(
185202
guides(col=guide_legend(override.aes=list(alpha=1, size=2))),
186203
geom_point(aes(col=.data$foo), data.frame(foo=pal), x=0, y=0, alpha=0))
187204
list(l,
188-
geom_blank(aes(x=x, y=y), data.frame(x=w, y=h)),
205+
geom_blank(aes(x=.data$x, y=.data$y), data.frame(x=w, y=h)),
189206
annotation_raster(x, w[1],w[2], h[2],h[1], interpolate=FALSE),
190207
scale_color_identity(NULL, guide="legend", breaks=pal, labels=names(pal)),
191208
ggnewscale::new_scale_color())
192209
}
193210

194211
#' @rdname plotImage
195212
#' @export
196-
setMethod("plotImage", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL, cl=NULL) {
213+
setMethod("plotImage", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL, cl=NULL, t=NULL, z=NULL) {
197214
if (is.numeric(i))
198215
i <- imageNames(x)[i]
199216
y <- image(x, i)
200217
if (is.numeric(j))
201218
j <- CTname(y)[j]
202219
y <- transform(y, j)
203-
wh <- .get_wh(y)
204220
if (.is_rgb(y)) {
205221
# RGB: we plot everything by default and we don't normalize
206222
ch <- ch %||% channels(y)
207223
cl <- cl %||% c(0, 1/3)
208224
}
209-
df <- .df_i(y, k, ch, c, cl)
225+
df <- .df_i(y, k, ch, t, c, cl, z)
210226
pal <- c %||% .DEFAULT_COLORS
211227
if (dim(y)[1] > 1 && !.is_rgb(y)) {
212228
nms <- unlist(channels(y))[idx <- .ch_idx(y, ch)]
213229
pal <- pal[seq_along(idx)]; names(pal) <- nms
214230
}
231+
# physical space mapping
232+
wh <- .get_wh(y)
215233
.gg_i(df, wh$w, wh$h, pal)
216234
})
217235

@@ -221,5 +239,5 @@ setMethod("plotImage", "SpatialData", \(x, i=1, j=1, k=NULL, ch=NULL, c=NULL, cl
221239
plotSpatialData <- \() ggplot() + coord_sf(expand=FALSE, reverse="y") + .theme
222240
# `annotation_raster` plots the array the same way it is printed, i.e., with the
223241
# row 1 at the top, which means we need to flip the y-axis to have the correct axis labels.
224-
# We tried flipping the image itself but it means everything gets out of alignement if
242+
# We tried flipping the image itself but it means everything gets out of alignment if
225243
# the user sets `scale_y_reverse()` themselves.

0 commit comments

Comments
 (0)