Skip to content

Commit 3c939cf

Browse files
brandonlukasclaude
andcommitted
Simplify annotation rasterization and document new arguments
Remove the unreachable small-viewport fallback, the redundant size rounding, the duplicated magick availability check, and the unused ht_opt$annotation_use_raster global option. Add roxygen documentation for the six new raster arguments on HeatmapAnnotation() and SingleAnnotation(), and a test covering raster parameter propagation and size stability across raster_quality. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0e5007e commit 3c939cf

5 files changed

Lines changed: 66 additions & 34 deletions

File tree

R/HeatmapAnnotation-class.R

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ HeatmapAnnotation = setClass("HeatmapAnnotation",
7171
# -width Width of the whole heatmap annotations.
7272
# -simple_anno_size Size of the simple annotation.
7373
# -simple_anno_size_adjust Whether also adjust the size of simple annotations when adjusting the whole heatmap annotation.
74+
# -use_raster Whether render the annotations as raster images. It helps to reduce file size when there are a huge number
75+
# of columns (or rows for row annotations). The value is passed to all single annotations.
76+
# -raster_device Graphic device which is used to generate the raster image.
77+
# -raster_quality A value larger than 1.
78+
# -raster_device_param A list of further parameters for the selected graphic device.
79+
# -raster_by_magick Whether to use `magick::image_resize` to scale the image.
80+
# -raster_magick_filter Pass to ``filter`` argument of `magick::image_resize`. A character scalar and all possible values
81+
# are in `magick::filter_types`. The default is ``"Lanczos"``.
7482
#
7583
# == details
7684
# For arguments ``show_legend``, ``border``, ``annotation_name_offset``, ``annotation_name_side``, ``annotation_name_rot``,
@@ -143,10 +151,6 @@ HeatmapAnnotation = function(...,
143151

144152
fun_args = names(as.list(environment()))
145153

146-
if(missing(use_raster) && !is.null(ht_opt$annotation_use_raster)) {
147-
use_raster = ht_opt$annotation_use_raster
148-
}
149-
150154
verbose = ht_opt$verbose
151155

152156
.Object = new("HeatmapAnnotation")
@@ -337,10 +341,10 @@ HeatmapAnnotation = function(...,
337341
i_anno = i_anno + 1
338342
arg_list = list(name = ag, which = which,
339343
label = annotation_label[[i_anno]],
340-
show_name = show_annotation_name[[i_anno]],
341-
name_gp = subset_gp(annotation_name_gp, i_anno),
342-
name_offset = annotation_name_offset[[i_anno]],
343-
name_side = annotation_name_side[i_anno],
344+
show_name = show_annotation_name[[i_anno]],
345+
name_gp = subset_gp(annotation_name_gp, i_anno),
346+
name_offset = annotation_name_offset[[i_anno]],
347+
name_side = annotation_name_side[i_anno],
344348
name_rot = annotation_name_rot[[i_anno]],
345349
border = border[i_anno],
346350
use_raster = use_raster,

R/SingleAnnotation-class.R

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ SingleAnnotation = setClass("SingleAnnotation",
8282
# the width must be an absolute unit.
8383
# -height The height of the plotting region (the viewport) that the annotation is drawn. If it is a column annotation,
8484
# the width must be an absolute unit.
85+
# -use_raster Whether render the annotation as a raster image. It helps to reduce file size when there are a
86+
# huge number of columns (or rows for a row annotation).
87+
# -raster_device Graphic device which is used to generate the raster image.
88+
# -raster_quality A value larger than 1.
89+
# -raster_device_param A list of further parameters for the selected graphic device.
90+
# -raster_by_magick Whether to use `magick::image_resize` to scale the image.
91+
# -raster_magick_filter Pass to ``filter`` argument of `magick::image_resize`. A character scalar and all possible values
92+
# are in `magick::filter_types`. The default is ``"Lanczos"``.
8593
#
8694
# == details
8795
# A single annotation is a basic unit of complex heatmap annotations where the heamtap annotations
@@ -591,11 +599,7 @@ SingleAnnotation = function(name, value, col, fun,
591599
}
592600

593601
if(is.null(raster_device)) {
594-
if(requireNamespace("Cairo", quietly = TRUE)) {
595-
raster_device = "CairoPNG"
596-
} else {
597-
raster_device = "png"
598-
}
602+
raster_device = if(requireNamespace("Cairo", quietly = TRUE)) "CairoPNG" else "png"
599603
}
600604
.Object@raster_param = list(
601605
use_raster = use_raster,
@@ -690,9 +694,8 @@ setMethod(f = "draw",
690694
xscale = data_scale$x, yscale = data_scale$y))
691695

692696
if(verbose) qqcat("execute annotation function\n")
693-
use_raster = isTRUE(object@raster_param$use_raster)
694-
if(use_raster) {
695-
rp = object@raster_param
697+
rp = object@raster_param
698+
if(isTRUE(rp$use_raster)) {
696699
rasterize_in_viewport(
697700
draw_fun = function() draw(object@fun, index = index, k = k, n = n),
698701
raster_device = rp$raster_device,

R/global.R

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,6 @@ ht_opt = setGlobalOptions(
209209
.value = FALSE
210210
),
211211
"validate_names" = TRUE,
212-
annotation_use_raster = list(
213-
.value = NULL,
214-
.class = "logical",
215-
.length = 1
216-
),
217212
raster_temp_image_max_width = 30000,
218213
raster_temp_image_max_height = 30000,
219214
COLOR = c("blue", "#EEEEEE", "red")

R/utils.R

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,12 +1152,6 @@ rasterize_in_viewport = function(draw_fun,
11521152
vp_width_pt = ceiling(vp_width_pt * raster_quality)
11531153
vp_height_pt = ceiling(vp_height_pt * raster_quality)
11541154

1155-
# if viewport is too small, fall back to vector
1156-
if(vp_width_pt < 1 || vp_height_pt < 1) {
1157-
draw_fun()
1158-
return(invisible(NULL))
1159-
}
1160-
11611155
device_info = switch(raster_device,
11621156
png = c("grDevices", "png", "readPNG"),
11631157
jpeg = c("grDevices", "jpeg", "readJPEG"),
@@ -1183,8 +1177,8 @@ rasterize_in_viewport = function(draw_fun,
11831177
}
11841178
}
11851179

1186-
temp_image_width = as.integer(ceiling(max(vp_width_pt, 1)))
1187-
temp_image_height = as.integer(ceiling(max(vp_height_pt, 1)))
1180+
temp_image_width = as.integer(vp_width_pt)
1181+
temp_image_height = as.integer(vp_height_pt)
11881182

11891183
if(!is.na(ht_opt$raster_temp_image_max_width)) {
11901184
temp_image_width = min(temp_image_width, ht_opt$raster_temp_image_max_width)
@@ -1193,8 +1187,7 @@ rasterize_in_viewport = function(draw_fun,
11931187
temp_image_height = min(temp_image_height, ht_opt$raster_temp_image_max_height)
11941188
}
11951189

1196-
temp_dir = tempdir()
1197-
temp_image = tempfile(pattern = ".annotation_raster_", tmpdir = temp_dir,
1190+
temp_image = tempfile(pattern = ".annotation_raster_", tmpdir = tempdir(),
11981191
fileext = paste0(".", device_info[2]))
11991192
device_fun = getFromNamespace(raster_device, ns = device_info[1])
12001193

@@ -1216,9 +1209,6 @@ rasterize_in_viewport = function(draw_fun,
12161209
dev.off2()
12171210

12181211
if(raster_by_magick) {
1219-
if(!requireNamespace("magick", quietly = TRUE)) {
1220-
stop_wrap("'magick' package should be installed.")
1221-
}
12221212
image = magick::image_read(temp_image)
12231213
image = magick::image_resize(image,
12241214
paste0(vp_width_pt, "x", vp_height_pt, "!"),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
context("Test annotation rasterization")
3+
4+
test_that("raster parameters are passed down to single annotations", {
5+
ha = HeatmapAnnotation(foo = 1:10, use_raster = TRUE, raster_quality = 3)
6+
rp = ha@anno_list[["foo"]]@raster_param
7+
expect_true(rp$use_raster)
8+
expect_equal(rp$raster_quality, 3)
9+
10+
ha = HeatmapAnnotation(foo = 1:10)
11+
expect_false(ha@anno_list[["foo"]]@raster_param$use_raster)
12+
})
13+
14+
# the graphics drawn by `draw_fun` are in absolute units, so the rasterized
15+
# result must occupy the same area no matter how large the temporary image is
16+
test_that("rasterized annotation does not shrink when raster_quality increases", {
17+
skip_if_not_installed("png")
18+
19+
filled_fraction = function(quality) {
20+
f = tempfile(fileext = ".png")
21+
png(f, width = 200, height = 200)
22+
pushViewport(viewport(width = unit(1, "npc"), height = unit(1, "npc")))
23+
rasterize_in_viewport(
24+
function() grid.rect(width = unit(10, "mm"), height = unit(10, "mm"),
25+
gp = gpar(fill = "red", col = NA)),
26+
raster_device = "png", raster_quality = quality)
27+
popViewport()
28+
dev.off()
29+
30+
img = png::readPNG(f)
31+
file.remove(f)
32+
mean(img[, , 1] > 0.5 & img[, , 2] < 0.5)
33+
}
34+
35+
q1 = filled_fraction(1)
36+
q3 = filled_fraction(3)
37+
38+
expect_gt(q1, 0.01)
39+
expect_lt(abs(q1 - q3)/q1, 0.1)
40+
})

0 commit comments

Comments
 (0)