From 1ae472776b5cf930d76d7815cef270d02a5dda36 Mon Sep 17 00:00:00 2001 From: josschavezf Date: Sun, 5 May 2024 23:08:16 -0400 Subject: [PATCH 01/15] update R version --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 69ea85a..4e6a866 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,9 +23,9 @@ BugReports: https://github.com/drieslab/Giotto/issues Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 Depends: - base (>= 4.3.0), - utils (>= 4.3.0), - R (>= 4.3.0), + base (>= 4.4.0), + utils (>= 4.4.0), + R (>= 4.4.0) Imports: checkmate, colorRamp2, From 955ef30f7b6cf305c7d128e102736dd7db5b4f18 Mon Sep 17 00:00:00 2001 From: josschavezf Date: Fri, 10 May 2024 16:28:16 -0400 Subject: [PATCH 02/15] run devtools document --- man/auto_image_resample.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/auto_image_resample.Rd b/man/auto_image_resample.Rd index 6ecf76e..f77e13f 100644 --- a/man/auto_image_resample.Rd +++ b/man/auto_image_resample.Rd @@ -7,7 +7,7 @@ \usage{ .auto_resample_gimage( img, - plot_ext, + plot_ext = NULL, img_border = 0.125, flex_resample = TRUE, max_sample = getOption("giotto.plot_img_max_sample", 5e+05), From aa1efd3d43e16ec7a813b1a26f9ca7f30ee128cc Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 28 May 2024 15:41:41 -0400 Subject: [PATCH 03/15] fix: typo --- R/vis_spatial.R | 2 +- man/spatPlot.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/vis_spatial.R b/R/vis_spatial.R index c2c1a78..38fe2f8 100644 --- a/R/vis_spatial.R +++ b/R/vis_spatial.R @@ -774,7 +774,7 @@ spatPlot2D <- function( #' #' #' # load another dataset with 3D data -#' starmap <- GiottoData::loadGiottoData("starmap", verbose = FALSE) +#' starmap <- GiottoData::loadGiottoMini("starmap", verbose = FALSE) #' #' # default is to rescale plot as a 3D cube #' spatPlot3D(starmap, cell_color = "leiden_clus") diff --git a/man/spatPlot.Rd b/man/spatPlot.Rd index f453ba0..a165bc2 100644 --- a/man/spatPlot.Rd +++ b/man/spatPlot.Rd @@ -309,7 +309,7 @@ spatPlot2D(g, # load another dataset with 3D data -starmap <- GiottoData::loadGiottoData("starmap", verbose = FALSE) +starmap <- GiottoData::loadGiottoMini("starmap", verbose = FALSE) # default is to rescale plot as a 3D cube spatPlot3D(starmap, cell_color = "leiden_clus") From d3baa51f2902e813eac72c0b12cdfa397de04578 Mon Sep 17 00:00:00 2001 From: Ruben Dries Date: Tue, 4 Jun 2024 15:18:20 -0400 Subject: [PATCH 04/15] add xlim and ylim to spatInSituPlotPoints --- R/vis_spatial_in_situ.R | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/R/vis_spatial_in_situ.R b/R/vis_spatial_in_situ.R index 36f3987..94573fe 100644 --- a/R/vis_spatial_in_situ.R +++ b/R/vis_spatial_in_situ.R @@ -24,6 +24,8 @@ #' @param feat_type feature types of the feats #' @param sdimx spatial dimension x #' @param sdimy spatial dimension y +#' @param xlim limits of x-scale (min/max vector) +#' @param ylim limits of y-scale (min/max vector) #' @param point_size size of the points #' @param stroke stroke to apply to feature points #' @param expand_counts expand feature coordinate counts (see details) @@ -69,6 +71,8 @@ spatInSituPlotPoints <- function(gobject, feat_shape_code = NULL, sdimx = 'x', sdimy = 'y', + xlim = NULL, + ylim = NULL, point_size = 1.5, stroke = 0.5, expand_counts = FALSE, @@ -342,11 +346,20 @@ spatInSituPlotPoints <- function(gobject, panel.background = element_rect(fill = background_color)) - + # subset data based on x and y limits + if(!is.null(xlim)) { + plot <- plot + ggplot2::xlim(xlim) + } + if(!is.null(ylim)) { + plot <- plot + ggplot2::ylim(ylim) + } + + # fix coordinates if(!is.null(coord_fix_ratio)) { plot = plot + ggplot2::coord_fixed(ratio = coord_fix_ratio) } + return(plot_output_handler( gobject = gobject, plot_object = plot, From d07d176178de0d1dc536a92021745437da375d9f Mon Sep 17 00:00:00 2001 From: josschavezf Date: Tue, 11 Jun 2024 14:37:37 -0400 Subject: [PATCH 05/15] fix example in dimFeatPlot3D --- R/vis_spatial.R | 85 ++++++++++++++++++++++---------------------- man/dimFeatPlot3D.Rd | 2 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/R/vis_spatial.R b/R/vis_spatial.R index 38fe2f8..4df1d1a 100644 --- a/R/vis_spatial.R +++ b/R/vis_spatial.R @@ -7514,40 +7514,41 @@ spatGenePlot3D <- function(...) { #' @returns plotly #' @examples #' g <- GiottoData::loadGiottoMini("starmap") -#' dimFeatPlot3D(g, genes = "Slc17a7") -#' +#' +#' dimFeatPlot3D(g, genes = "Slc17a7", dim_reduction_name = "3D_umap") #' @export -dimFeatPlot3D <- function(gobject, - feat_type = NULL, - spat_unit = NULL, - expression_values = c("normalized", "scaled", "custom"), - genes = NULL, - dim_reduction_to_use = "umap", - dim_reduction_name = "umap", - dim1_to_use = 1, - dim2_to_use = 2, - dim3_to_use = 3, - show_NN_network = FALSE, - nn_network_to_use = "sNN", - network_name = "sNN.pca", - network_color = "lightgray", - cluster_column = NULL, - select_cell_groups = NULL, - select_cells = NULL, - show_other_cells = TRUE, - other_cell_color = "lightgrey", - other_point_size = 1, - edge_alpha = NULL, - point_size = 2, - genes_high_color = NULL, - genes_mid_color = "white", - genes_low_color = "blue", - show_legend = TRUE, - show_plot = NULL, - return_plot = NULL, - save_plot = NULL, - save_param = list(), - default_save_name = "dimFeatPlot3D") { +dimFeatPlot3D <- function( + gobject, + feat_type = NULL, + spat_unit = NULL, + expression_values = c("normalized", "scaled", "custom"), + genes = NULL, + dim_reduction_to_use = "umap", + dim_reduction_name = "umap", + dim1_to_use = 1, + dim2_to_use = 2, + dim3_to_use = 3, + show_NN_network = FALSE, + nn_network_to_use = "sNN", + network_name = "sNN.pca", + network_color = "lightgray", + cluster_column = NULL, + select_cell_groups = NULL, + select_cells = NULL, + show_other_cells = TRUE, + other_cell_color = "lightgrey", + other_point_size = 1, + edge_alpha = NULL, + point_size = 2, + genes_high_color = NULL, + genes_mid_color = "white", + genes_low_color = "blue", + show_legend = TRUE, + show_plot = NULL, + return_plot = NULL, + save_plot = NULL, + save_param = list(), + default_save_name = "dimFeatPlot3D") { # Set feat_type and spat_unit spat_unit <- set_default_spat_unit( gobject = gobject, @@ -7562,7 +7563,7 @@ dimFeatPlot3D <- function(gobject, ## select genes ## selected_genes <- genes values <- match.arg(expression_values, c("normalized", "scaled", "custom")) - expr_values <- get_expression_values( + expr_values <- getExpression( gobject = gobject, spat_unit = spat_unit, feat_type = feat_type, @@ -7597,11 +7598,11 @@ dimFeatPlot3D <- function(gobject, ## dimension reduction ## - dim_dfr <- get_dimReduction(gobject, - reduction = "cells", - reduction_method = dim_reduction_to_use, - name = dim_reduction_name, - output = "data.table" + dim_dfr <- getDimReduction(gobject, + reduction = "cells", + reduction_method = dim_reduction_to_use, + name = dim_reduction_name, + output = "data.table" ) dim_dfr <- dim_dfr[, c(dim1_to_use, dim2_to_use, dim3_to_use)] dim_names <- colnames(dim_dfr) @@ -7620,12 +7621,12 @@ dimFeatPlot3D <- function(gobject, # create input for network if (show_NN_network == TRUE) { # nn_network - selected_nn_network <- get_NearestNetwork( + selected_nn_network <- getNearestNetwork( gobject = gobject, feat_type = feat_type, spat_unit = spat_unit, - nn_network_to_use = nn_network_to_use, - network_name = network_name, + nn_type = nn_network_to_use, + name = network_name, output = "igraph" ) network_DT <- data.table::as.data.table(igraph::as_data_frame( diff --git a/man/dimFeatPlot3D.Rd b/man/dimFeatPlot3D.Rd index 5e7fc73..e55ee71 100644 --- a/man/dimFeatPlot3D.Rd +++ b/man/dimFeatPlot3D.Rd @@ -126,7 +126,7 @@ Description of parameters. }} \examples{ g <- GiottoData::loadGiottoMini("starmap") -dimFeatPlot3D(g, genes = "Slc17a7") +dimFeatPlot3D(g, genes = "Slc17a7", dim_reduction_name = "3D_umap") } \concept{dimension reduction gene expression visualizations} From f30a4455e6d8014c1f06cd2cbd89ae294fb02a1d Mon Sep 17 00:00:00 2001 From: josschavezf Date: Tue, 11 Jun 2024 14:37:49 -0400 Subject: [PATCH 06/15] run devtools::document --- man/spatInSituPlotPoints.Rd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/spatInSituPlotPoints.Rd b/man/spatInSituPlotPoints.Rd index aa05353..0fcd97e 100644 --- a/man/spatInSituPlotPoints.Rd +++ b/man/spatInSituPlotPoints.Rd @@ -18,6 +18,8 @@ spatInSituPlotPoints( feat_shape_code = NULL, sdimx = "x", sdimy = "y", + xlim = NULL, + ylim = NULL, spat_enr_names = NULL, point_size = 1.5, stroke = 0.5, @@ -81,6 +83,10 @@ spatInSituPlotPoints( \item{sdimy}{spatial dimension y} +\item{xlim}{limits of x-scale (min/max vector)} + +\item{ylim}{limits of y-scale (min/max vector)} + \item{spat_enr_names}{character. names of spatial enrichment results to include} From e98f98ba3cba89aa09dd80ba6250cba3f8be617e Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:28:01 -0400 Subject: [PATCH 07/15] feat: `.ext_to_dummy_df()` as refactored internal --- R/gg_info_layers.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/R/gg_info_layers.R b/R/gg_info_layers.R index 63ae4c7..b7f1b0e 100644 --- a/R/gg_info_layers.R +++ b/R/gg_info_layers.R @@ -1412,10 +1412,7 @@ plot_spat_image_layer_ggplot <- function( ... ) - bounds_dt <- data.table::data.table( - sdimx = e[][c(1, 2)], - sdimy = e[][c(3, 4)] - ) + bounds_dt <- .ext_to_dummy_df(e) # Assign region to plot gg_obj <- gg_obj + geom_blank(data = bounds_dt, aes(sdimx, sdimy)) @@ -1436,7 +1433,15 @@ plot_spat_image_layer_ggplot <- function( return(gg_obj) } - +# internal to convert a SpatExtent into a data.frame with x and y values that +# ggplot2 can use to determine bounds of placement +.ext_to_dummy_df <- function(x) { + data.frame( + sdimx = x[][c(1, 2)], + sdimy = x[][c(3, 4)], + row.names = NULL + ) +} From 9f771a0d59f2186727d45789406d2711568a2a78 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:36:45 -0400 Subject: [PATCH 08/15] feat: reexport ggrepel functions --- NAMESPACE | 4 ++++ R/suite_reexports.R | 5 +++++ man/reexports.Rd | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 13d0472..ddbbf63 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,8 @@ export(dimPlot) export(dimPlot2D) export(dimPlot3D) export(expand_feature_info) +export(geom_label_repel) +export(geom_text_repel) export(getColors) export(getDistinctColors) export(getRainbowColors) @@ -107,6 +109,8 @@ importFrom(GiottoUtils,getRainbowColors) importFrom(colorRamp2,colorRamp2) importFrom(data.table,dcast) importFrom(data.table,dcast.data.table) +importFrom(ggrepel,geom_label_repel) +importFrom(ggrepel,geom_text_repel) importFrom(igraph,as_data_frame) importFrom(methods,new) importFrom(methods,setGeneric) diff --git a/R/suite_reexports.R b/R/suite_reexports.R index 92d685d..b8cef91 100644 --- a/R/suite_reexports.R +++ b/R/suite_reexports.R @@ -2,3 +2,8 @@ GiottoUtils::getRainbowColors #' @export GiottoUtils::getDistinctColors + +#' @export +ggrepel::geom_text_repel +#' @export +ggrepel::geom_label_repel diff --git a/man/reexports.Rd b/man/reexports.Rd index 6259261..1096a60 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -6,6 +6,8 @@ \alias{colorRamp2} \alias{getRainbowColors} \alias{getDistinctColors} +\alias{geom_text_repel} +\alias{geom_label_repel} \title{Objects exported from other packages} \value{ a function to create continous colors @@ -22,6 +24,8 @@ below to see their documentation. \describe{ \item{colorRamp2}{\code{\link[colorRamp2]{colorRamp2}}} + \item{ggrepel}{\code{\link[ggrepel:geom_text_repel]{geom_label_repel}}, \code{\link[ggrepel]{geom_text_repel}}} + \item{GiottoUtils}{\code{\link[GiottoUtils]{getDistinctColors}}, \code{\link[GiottoUtils]{getRainbowColors}}} }} From 2c6273aef64d0bcfa1649e198d8a52576489aceb Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:30:57 -0400 Subject: [PATCH 09/15] feat: max_window and colorize support - rework image array processing - add more efficient single channel code - refactor image array processing to `.gg_process_img_array()` - add `giottoLargeImage` color and max_window support --- R/gg_annotation_raster.R | 108 +++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/R/gg_annotation_raster.R b/R/gg_annotation_raster.R index 0e0d318..29c3087 100644 --- a/R/gg_annotation_raster.R +++ b/R/gg_annotation_raster.R @@ -73,57 +73,26 @@ setMethod( } # get plotting minmax - extent <- terra::ext(gimage@raster_object)[seq_len(4)] + extent <- terra::ext(gimage)[seq_len(4)] xmin <- extent[["xmin"]] xmax <- extent[["xmax"]] ymin <- extent[["ymin"]] ymax <- extent[["ymax"]] - # convert raster object into array with 3 channels - img_array <- terra::as.array(gimage@raster_object) - - # TODO: check if required, fixes NaN values - # replacing NA's by zero or another value directly in raster object? - # raster[is.na(raster[])] <- 0 - if (is.nan(max(img_array[, , 1]))) { - img_array[, , 1][is.nan(img_array[, , 1])] <- max(img_array[, , 1], - na.rm = TRUE + # convert raster object into array with 3 channels + alpha + img_array_rgb <- terra::as.array(gimage@raster_object) %>% + .gg_process_img_array( + maxval = gimage@max_window, + col = gimage@colors ) - } - - if (dim(img_array)[3] > 1) { - if (is.nan(max(img_array[, , 2]))) { - img_array[, , 2][is.nan(img_array[, , 2])] <- - max(img_array[, , 2], na.rm = TRUE) - } - } - - if (dim(img_array)[3] > 2) { - if (is.nan(max(img_array[, , 3]))) { - img_array[, , 3][is.nan(img_array[, , 3])] <- - max(img_array[, , 3], na.rm = TRUE) - } - } - - img_array <- img_array / max(img_array, na.rm = TRUE) - if (dim(img_array)[3] == 1) { - img_array_RGB <- array(NA, dim = c(dim(img_array)[seq_len(2)], 3)) - img_array_RGB[, , seq_len(3)] <- img_array - } else { - img_array_RGB <- img_array - } - - # handle NA values - img_array_RGB[is.na(img_array_RGB)] <- 0 # append to ggobj ggobj <- ggobj + annotation_raster( - img_array_RGB, + img_array_rgb, xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax ) - # TODO geom_raster to accommodate single-channel return(ggobj) } ) @@ -322,4 +291,67 @@ setMethod( return(img) } +# make an image array compatible with ggplot::annotation_raster() +# maxval is the cutoff after which everything is max intensity +# returns: +# - if rgb, a properly scaled and cleaned array +# - if single channel, a native raster +.gg_process_img_array <- function(x, maxval = NULL, col = NULL) { + nlyr <- dim(x)[3L] # number of channels/layers + if (is.na(nlyr)) nlyr <- 1L + # NOTE: 4 layers allowed (rgba), but may conflict with actual 4 info + # layer cases which SHOULD be converted to 3 layer + # + # more than 4 layers -> directly ignore layers past the 3rd + if (nlyr > 4L) { + nlyr <- 3L + x <- x[, , seq_len(3)] + } + + # handle NaN values -- set as max value of that layer + # these may arise due to save artefacting when values are larger than + # expected + for (lyr in seq_len(nlyr)) { + if (is.nan(max(x[, , lyr]))) { + x[, , lyr][is.nan(x[, , lyr])] <- + max(x[, , lyr], na.rm = TRUE) + } + } + + # handle NA values -- set as 0 + x[is.na(x)] <- 0 + + if (nlyr == 1L) { + # SINGLE CHANNEL # + # max window cutoff + if (!is.null(maxval)) x[x > maxval] <- maxval + # colorize + if (is.null(col)) { + col <- getMonochromeColors("white", n = 256) + } + r <- .colorize_single_channel_raster(x, col = col) + } else { + # RGB EXPECTED # + # convert to range 0:1 (needed for as.raster()) + x <- scales::rescale(x, to = c(0, 1)) + r <- as.raster(x) + } + + return(r) +} + +# `x` is array to use +# `col` is character vector of colors to use +.colorize_single_channel_raster <- function(x, col) { + if (!is.na(dim(x)[3L]))x <- x[,, 1L] # convert to matrix + r <- range(x, na.rm = TRUE) + x <- (x - r[1])/(r[2] - r[1]) + x <- round(x * (length(col) - 1) + 1) + x[] <- col[x] + as.raster(x) +} + + + + From 64a7af78064aef54649ee96c04674ef7581611ec Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:20:08 -0400 Subject: [PATCH 10/15] chore: update news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 87e8b87..38e0b36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,8 @@ # GiottoVisuals 0.2.4 +## enhancements +- `giottoLargeImage` `max_window` and `colors` slot info is now followed during ggplot plotting # GiottoVisuals 0.2.3 (2024/05/28) From 87d13f2842a91d23a8309947a74ce803b2d39ce3 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:21:40 -0400 Subject: [PATCH 11/15] chore: update news --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 38e0b36..83a1723 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ ## enhancements - `giottoLargeImage` `max_window` and `colors` slot info is now followed during ggplot plotting +## new +- `geom_text_repel()` and `geom_label_repel()` from `ggplot2` are now re-exported + # GiottoVisuals 0.2.3 (2024/05/28) ## bug fixes From ee22d11c6eece9cbaa2d203bdebb51561368704e Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:21:01 -0400 Subject: [PATCH 12/15] chore: formatting and docs --- R/gg_annotation_raster.R | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/R/gg_annotation_raster.R b/R/gg_annotation_raster.R index 29c3087..4886a00 100644 --- a/R/gg_annotation_raster.R +++ b/R/gg_annotation_raster.R @@ -63,14 +63,13 @@ setMethod( signature(ggobj = "gg", gimage = "giottoLargeImage"), function(ggobj, gimage, ext = NULL, ...) { - # apply plot ext - if (!is.null(ext)) { - gimage <- .auto_resample_gimage( - img = gimage, - plot_ext = ext, - ... - ) - } + # resample from extent + if (is.null(ext)) ext <- ext(gimage) + gimage <- .auto_resample_gimage( + img = gimage, + plot_ext = ext, + ... + ) # get plotting minmax extent <- terra::ext(gimage)[seq_len(4)] @@ -213,16 +212,17 @@ setMethod( ) ) { + # 1. determine image and cropping extents img_ext <- terra::ext(img) if (is.null(plot_ext)) crop_ext <- img_ext # default else crop_ext <- ext(plot_ext) bound_poly <- as.polygons(crop_ext) - # override max_crop if needed + # 1.5. override max_crop if needed if (max_sample > max_crop) max_crop <- max_sample - # apply img border - # - cropping with extent larger than the image extent works + # 2. apply img border expansion + # - note: cropping with extent larger than the image extent works if (img_border > 0) { crop_ext <- bound_poly %>% @@ -233,19 +233,22 @@ setMethod( crop_ext <- ext(crop(bound_poly, crop_ext)) } - # determine ratio of crop vs original + # 3. determine ratio of crop vs original original_dims <- dim(img)[c(2L, 1L)] # x, y ordering ratios <- range(crop_ext) / range(img_ext) # x, y ordering crop_dims <- original_dims * ratios crop_area_px <- prod(crop_dims) + # 4. perform flexible resample/crop if (!isTRUE(flex_resample) || crop_area_px <= max_crop) { # [METHOD A]: # 1. Crop if needed # 2. resample to final image if (!isTRUE(flex_resample) && crop_area_px > max_crop) { - warning("Plotting large regions with flex_resample == FALSE will - increase time and may require scratch space.") + warning( + "Plotting large regions with flex_resample == FALSE will\n ", + "increase time and may require scratch space." + ) } vmsg(.is_debug = TRUE, @@ -291,11 +294,14 @@ setMethod( return(img) } + + + + + # make an image array compatible with ggplot::annotation_raster() # maxval is the cutoff after which everything is max intensity -# returns: -# - if rgb, a properly scaled and cleaned array -# - if single channel, a native raster +# returns: raster .gg_process_img_array <- function(x, maxval = NULL, col = NULL) { nlyr <- dim(x)[3L] # number of channels/layers if (is.na(nlyr)) nlyr <- 1L From 820903c599c4fbd96b806ccf0a642fbd7907feaa Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:32:48 -0400 Subject: [PATCH 13/15] feat: ggplot `giottoAffineImage` compatibility - refactor `gg_annotation_raster()` internals - add compatibility for plotting with `giottoAffineImage` --- R/gg_annotation_raster.R | 161 ++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 55 deletions(-) diff --git a/R/gg_annotation_raster.R b/R/gg_annotation_raster.R index 4886a00..6766327 100644 --- a/R/gg_annotation_raster.R +++ b/R/gg_annotation_raster.R @@ -62,41 +62,42 @@ setMethod( "gg_annotation_raster", signature(ggobj = "gg", gimage = "giottoLargeImage"), function(ggobj, gimage, ext = NULL, ...) { - # resample from extent if (is.null(ext)) ext <- ext(gimage) gimage <- .auto_resample_gimage( img = gimage, plot_ext = ext, + crop_ratio_fun = .img_to_crop_ratio_gimage, + sample_fun = .sample_gimage, ... ) - # get plotting minmax - extent <- terra::ext(gimage)[seq_len(4)] - xmin <- extent[["xmin"]] - xmax <- extent[["xmax"]] - ymin <- extent[["ymin"]] - ymax <- extent[["ymax"]] - - # convert raster object into array with 3 channels + alpha - img_array_rgb <- terra::as.array(gimage@raster_object) %>% - .gg_process_img_array( - maxval = gimage@max_window, - col = gimage@colors - ) - - # append to ggobj - ggobj <- ggobj + annotation_raster( - img_array_rgb, - xmin = xmin, xmax = xmax, - ymin = ymin, ymax = ymax - ) + ggobj <- .gg_append_image(ggobj = ggobj, gimage = gimage) return(ggobj) } ) +#' @rdname gg_annotation_raster +setMethod( + "gg_annotation_raster", + signature(ggobj = "gg", gimage = "giottoAffineImage"), + function(ggobj, gimage, ext, ...) { + # resample from extent + if (is.null(ext)) ext <- ext(gimage) + gimage <- .auto_resample_gimage( + img = gimage, + plot_ext = ext, + crop_ratio_fun = .img_to_crop_ratio_gaffimage, + sample_fun = .sample_gaffimage, + ... + ) + ggobj <- .gg_append_image(ggobj = ggobj, gimage = gimage) + + return(ggobj) + } +) @@ -170,7 +171,7 @@ setMethod( #' determines if this switching behavior happens. #' When set to \code{FALSE}, only method A is used. #' @param img giotto image to plot -#' @param plot_ext extent of plot (required) +#' @param plot_ext extent of plot (defaults to the image extent) #' @param img_border if not 0 or FALSE, expand plot_ext by this percentage on #' each side before applying crop on image. See details #' @param flex_resample logical. Default = TRUE. Forces usage of method A when @@ -204,6 +205,8 @@ setMethod( img, plot_ext = NULL, img_border = 0.125, + crop_ratio_fun = .img_to_crop_ratio_gimage, + sample_fun = .sample_gimage, flex_resample = TRUE, max_sample = getOption("giotto.plot_img_max_sample", 5e5), max_crop = getOption("giotto.plot_img_max_crop", 1e8), @@ -212,17 +215,16 @@ setMethod( ) ) { - # 1. determine image and cropping extents - img_ext <- terra::ext(img) - if (is.null(plot_ext)) crop_ext <- img_ext # default + # 1. determine source image and cropping extents + if (is.null(plot_ext)) crop_ext <- ext(img) # default to img extent else crop_ext <- ext(plot_ext) bound_poly <- as.polygons(crop_ext) - # 1.5. override max_crop if needed + # 1.1. override max_crop if needed if (max_sample > max_crop) max_crop <- max_sample - # 2. apply img border expansion - # - note: cropping with extent larger than the image extent works + # 1.2. apply img border expansion + # - note: cropping with extent larger than the image extent is supported if (img_border > 0) { crop_ext <- bound_poly %>% @@ -233,13 +235,13 @@ setMethod( crop_ext <- ext(crop(bound_poly, crop_ext)) } - # 3. determine ratio of crop vs original + # 2. determine cropping area original_dims <- dim(img)[c(2L, 1L)] # x, y ordering - ratios <- range(crop_ext) / range(img_ext) # x, y ordering + ratios <- crop_ratio_fun(img = img, crop_ext = crop_ext) # x, y ordering crop_dims <- original_dims * ratios crop_area_px <- prod(crop_dims) - # 4. perform flexible resample/crop + # 3. perform flexible resample/crop based on cropping area if (!isTRUE(flex_resample) || crop_area_px <= max_crop) { # [METHOD A]: # 1. Crop if needed @@ -255,16 +257,8 @@ setMethod( sprintf("img auto_res: [A] | area: %f | max: %f", crop_area_px, max_crop)) - crop_img <- terra::crop( - x = img@raster_object, - y = crop_ext - ) - img@raster_object <- terra::spatSample( - crop_img, - size = max_sample, - method = "regular", - as.raster = TRUE - ) + crop_img <- terra::crop(img, crop_ext) + res <- sample_fun(crop_img, size = max_sample) } else { # [METHOD B]: # 1. Oversample @@ -280,21 +274,60 @@ setMethod( sprintf("img auto_res: [B] | scalef: %f | max_scale: %f", scalef, max_resample_scale)) - oversample_img <- terra::spatSample( - img@raster_object, - size = round(max_sample * scalef), - method = "regular", - as.raster = TRUE - ) - img@raster_object <- terra::crop( - x = oversample_img, - y = crop_ext - ) + oversample_img <- sample_fun(img, size = round(max_sample * scalef)) + res <- terra::crop(oversample_img, crop_ext) } - return(img) + return(res) +} + + + + +# determine ratio of crop vs full image extent +.img_to_crop_ratio_gimage <- function(img, crop_ext) { + img_ext <- ext(img) + ratio <- range(crop_ext) / range(img_ext) + # crops larger than the image are possible, but meaningless for this + # calculate. so the ratios are capped at 1. + ratio[ratio > 1] <- 1 + return(ratio) } +.img_to_crop_ratio_gaffimage <- function(img, crop_ext) { + # Do not use the ext() method for giottoAffineImage + # Instead use the mapping applied to the underlying SpatRaster. + # For giottoAffineImage, these two values are usually different. + img_ext <- ext(img@raster_object) + # find the extent needed in the source (untransformed) image + crop_bound <- terra::as.polygons(crop_ext) + crop_bound$id <- "bound" # affine() requires ID values + crop_ext <- ext(affine(crop_bound, img@affine, inv = TRUE)) + ratio <- range(crop_ext) / range(img_ext) + # crops larger than the image are possible, but meaningless for this + # calculate. so the ratios are capped at 1. + ratio[ratio > 1] <- 1 + return(ratio) +} + + + + +# pull sampled values from original image into target spatial mapping +# should return a giottoLargeImage +.sample_gimage <- function(x, size) { + x@raster_object <- terra::spatSample( + x = x@raster_object, + size = size, + method = "regular", + as.raster = TRUE + ) + return(x) +} +.sample_gaffimage <- function(x, size) { + res <- x@funs$realize_magick(size = size) + return(res) +} @@ -302,7 +335,7 @@ setMethod( # make an image array compatible with ggplot::annotation_raster() # maxval is the cutoff after which everything is max intensity # returns: raster -.gg_process_img_array <- function(x, maxval = NULL, col = NULL) { +.gg_imgarray_2_raster <- function(x, maxval = NULL, col = NULL) { nlyr <- dim(x)[3L] # number of channels/layers if (is.na(nlyr)) nlyr <- 1L # NOTE: 4 layers allowed (rgba), but may conflict with actual 4 info @@ -346,6 +379,9 @@ setMethod( return(r) } + + + # `x` is array to use # `col` is character vector of colors to use .colorize_single_channel_raster <- function(x, col) { @@ -357,7 +393,22 @@ setMethod( as.raster(x) } +# append a giotto image object containing a SpatRaster that has already been +# resampled/pulled into memory. Output is a `gg` object +.gg_append_image <- function(ggobj, gimage) { + # convert gimage to a raster + r <- terra::as.array(gimage@raster_object) %>% + .gg_imgarray_2_raster( + maxval = gimage@max_window, + col = gimage@colors + ) + # append to ggobj + extent <- ext(gimage)[seq_len(4L)] + ggobj <- ggobj + annotation_raster(r, + xmin = extent[["xmin"]], xmax = extent[["xmax"]], + ymin = extent[["ymin"]], ymax = extent[["ymax"]] + ) - - + return(ggobj) +} From 599949d5007149a24de03c76aec7660e4a150a56 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:35:11 -0400 Subject: [PATCH 14/15] chore: document --- man/auto_image_resample.Rd | 4 +++- man/gg_annotation_raster.Rd | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/man/auto_image_resample.Rd b/man/auto_image_resample.Rd index f77e13f..bb49060 100644 --- a/man/auto_image_resample.Rd +++ b/man/auto_image_resample.Rd @@ -9,6 +9,8 @@ img, plot_ext = NULL, img_border = 0.125, + crop_ratio_fun = .img_to_crop_ratio_gimage, + sample_fun = .sample_gimage, flex_resample = TRUE, max_sample = getOption("giotto.plot_img_max_sample", 5e+05), max_crop = getOption("giotto.plot_img_max_crop", 1e+08), @@ -18,7 +20,7 @@ \arguments{ \item{img}{giotto image to plot} -\item{plot_ext}{extent of plot (required)} +\item{plot_ext}{extent of plot (defaults to the image extent)} \item{img_border}{if not 0 or FALSE, expand plot_ext by this percentage on each side before applying crop on image. See details} diff --git a/man/gg_annotation_raster.Rd b/man/gg_annotation_raster.Rd index 2b81d22..18e4bc3 100644 --- a/man/gg_annotation_raster.Rd +++ b/man/gg_annotation_raster.Rd @@ -5,6 +5,7 @@ \alias{gg_annotation_raster,gg,list-method} \alias{gg_annotation_raster,gg,giottoImage-method} \alias{gg_annotation_raster,gg,giottoLargeImage-method} +\alias{gg_annotation_raster,gg,giottoAffineImage-method} \title{Append image to ggplot as annotation_raster} \usage{ \S4method{gg_annotation_raster}{gg,list}(ggobj, gimage, ...) @@ -12,6 +13,8 @@ \S4method{gg_annotation_raster}{gg,giottoImage}(ggobj, gimage, ...) \S4method{gg_annotation_raster}{gg,giottoLargeImage}(ggobj, gimage, ext = NULL, ...) + +\S4method{gg_annotation_raster}{gg,giottoAffineImage}(ggobj, gimage, ext, ...) } \arguments{ \item{ggobj}{ggplot2 \code{gg} object} From ad8f20cb0c1d836a74b0e8a873ba38c7ba46fa97 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:35:19 -0400 Subject: [PATCH 15/15] chore: update news --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 83a1723..d1076ef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ ## enhancements - `giottoLargeImage` `max_window` and `colors` slot info is now followed during ggplot plotting +- `giottoAffineImage` compatibility for giotto ggplot2 plotting functions ## new - `geom_text_repel()` and `geom_label_repel()` from `ggplot2` are now re-exported