From 59675f6d95e4091eab7fcb9d11e8569d545fc6de Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:04:46 -0500 Subject: [PATCH 01/24] wip still needs overlap method --- R/classes-giottoMatrixPoints.R | 256 +++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 R/classes-giottoMatrixPoints.R diff --git a/R/classes-giottoMatrixPoints.R b/R/classes-giottoMatrixPoints.R new file mode 100644 index 00000000..890558e5 --- /dev/null +++ b/R/classes-giottoMatrixPoints.R @@ -0,0 +1,256 @@ +createGiottoBinPoints <- function(expr_values, spatial_locs) { + ids_p <- spatial_locs$cell_ID + spatial_locs[] <- spatial_locs[][, c("sdimx", "sdimy")] # drop point IDs col + sl_points <- as.points(spatial_locs) + + # get counts information as `d` + M <- expr_values[] + if (!inherits(M, "Matrix")) stop("expr_values should be Matrix") + d <- data.table::as.data.table(Matrix::summary(M)) + ids_m <- colnames(M) |> as.character() + + # keep only existing points geoms so it matches `d` content + exist_j <- sort(unique(d$j)) + keep_ids <- ids_m[exist_j] # ids that actually exist (character) + keep_index <- which(ids_p %in% keep_ids) # i integer values for spatial + if (length(keep_index) == 0L) warning("No bin points exist\n") + # remove non-existing from spatial info + ids_p <- ids_p[keep_index] + sl_points <- sl_points[keep_index] + + x <- new("giottoBinPoints", + spatial = sl_points, + counts = d, + bid = ids_m, + pmap = match(ids_p, ids_m), + fid = rownames(M), + compact = TRUE + ) + .gbp_compact(x, ids_p = ids_p, exist_j = exist_j) +} + +setClass("giottoBinPoints", + contains = c("featData", "giottoSubobject"), + slots = list( + spatial = "ANY", # spatvector etc. + counts = "data.frame", + bid = "character", # bin ID (static) + pmap = "integer", # point IDs by mapping onto bid + fid = "character", + compact = "logical" # state of compaction + ), + prototype = list( + compact = TRUE + ) +) + +setMethod("show", signature("giottoBinPoints"), function(object) { + cat(sprintf("An object of class %s\n", class(object))) + .show_feat(object) + plist <- c( + dimensions = toString(dim(object)), + compact = as.character(object@compact) + ) + print_list(plist) + preview <- head(object@counts)[, c("i", "x")] + preview$i <- object@fid[preview$i] + names(preview) <- c("feat_ID", "count") + print(preview) + cat("\n") +}) + +setMethod("dim", signature("giottoBinPoints"), function(x) { + c(nrow(x@counts), 1L) +}) + +setMethod("nrow", signature("giottoBinPoints"), function(x) nrow(x@counts)) + +setMethod("ncol", signature("giottoBinPoints"), function(x) 1L) + +setMethod("[", signature(x = "giottoBinPoints", i = "numeric", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { + i <- as.integer(i) + x@counts <- x@counts[i,] + if (identical(compact, "auto")) compact <- .gbp_compact_auto(x) + if (!compact) { + x@compact <- FALSE + return(x) + } + .gbp_compact(x) +}) + +setMethod("[", signature(x = "giottoBinPoints", i = "logical", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { + if (length(i) != nrow(x)) { + i <- rep(i, length.out = nrow(x)) + } + i <- which(i) + x[i, ..., compact = compact] +}) + +# feature subsetting +setMethod("[", signature(x = "giottoBinPoints", i = "character", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { + keep_feat_idx <- which(x@fid %in% i) # i is feature whitelist + i <- which(x@counts$i %in% keep_feat_idx) + x[i, ..., compact = compact] +}) + +setMethod("plot", signature("giottoBinPoints", "missing"), function(x, + point_size = 0.2, feats = NULL, dens = FALSE, dens_transform = NULL, raster = TRUE, raster_size = 600, ...) { + checkmate::assert_function(dens_transform, null.ok = TRUE) + if (nrow(x@counts) == 0L) { + stop(wrap_txt("No geometries to plot"), call. = FALSE) + } + + # get points to plot + spatial <- .gbp_get_spatial(x, counts = dens) + a <- list(x = spatial, ...) + a$cex <- point_size + cmap <- "white" + if (dens) { + cmap <- NULL # use terra default gradient map + a$y <- "count" + if (!is.null(dens_transform)) { + a$x$count <- dens_transform(a$x$count) + } + } + a$background <- a$background %null% "black" + a$col <- a$col %null% cmap + do.call(terra::plot, a) +}) + +setMethod("relate", signature(x = "giottoSpatial", y = "giottoBinPoints"), + function(x, y, relation, + pairs = TRUE, + na.rm = TRUE, + output = c("data.table", "matrix"), + use_names = TRUE, + ... + ) { + output <- match.arg(output, choices = c("data.table", "matrix")) +}) + + + +# internals + +# pids are mapped 1:1 with spatial row number +.gbp_get_ids_p <- function(x) { + x@bid[x@pmap] +} + +.gbp_get_existing_bid <- function(x, sort = TRUE) { + existing_j <- unique(x@counts$j) + if (sort) { + existing_j <- sort(existing_j) + } + x@bid[existing_j] +} + +# update and remap values for existing bids only +# also drop any nonexisting spatial points +.gbp_compact <- function(x, ids_p = NULL, exist_j = NULL) { + if (is.null(exist_j)) { + exist_j <- sort(unique(x@counts$j)) + } + exist_ids <- x@bid[exist_j] + + if (is.null(ids_p)) { + ids_p <- .gbp_get_ids_p(x) + } + + x@bid <- exist_ids + x@counts$j <- match(x@counts$j, exist_j) + ids_p_mapping <- match(ids_p, x@bid) + spatial_keep_bool <- !is.na(ids_p_mapping) + x@pmap <- ids_p_mapping[spatial_keep_bool] + x@spatial <- x@spatial[spatial_keep_bool] + x@compact <- TRUE + x +} + +# determine whether to compact based on bloat ratio +.gbp_compact_auto <- function(x) { + bloat_ratio <- length(x@bid) / length(unique(x@counts$j)) + bloat_ratio > 10 # arbitrary setting +} + +.gbp_get_spatial <- function(x, counts = FALSE) { + if (counts) return(.gbp_get_spatial_sum_counts(x)) + if (x@compact) { + return(x@spatial) + } + # these are both mappings of @bid, so they can be directly compared + ids_select <- which(x@pmap %in% x@counts$j) + x@spatial[ids_select] +} + +.gbp_get_spatial_sum_counts <- function(x) { + counts <- x@counts[, .("count" = sum(x)), keyby = "j"] + idx <- match(counts$j, x@pmap) + # NA in idx should be impossible. `counts` is a subset of `spatial` + spatial <- x@spatial[idx] # select spatial matching order of counts + spatial$count <- counts$count # append count information + spatial +} + +# `i` is row number(s) of spatial +.gbp_spatial_select_counts <- function(x, i) { + i <- as.integer(i) + if (max(i) > length(x@pmap) || min(i) < 1) { + stop(sprintf("[giottoBinPoints] point %d does not exist", i), + call. = FALSE) + } + selected_j <- x@pmap[i] + counts <- x@counts[j %in% selected_j] + fids <- x@fid + counts[, "i" := fids[i]] + counts <- counts[, .("count" = sum(x)), keyby = "i"] + data.table::setnames(counts, old = "i", new = "feat_ID") + counts +} + + +# .plot_giotto_points_raster <- function(data, feats = NULL, ...) { +# args_list <- list(...) +# +# # raster size +# if (is.null(args_list$size)) { +# args_list$size <- c(600, 600) +# } else if (length(args_list$size) == 1L) { +# # if size provided as single value, replicate to give a square window +# args_list$size <- rep(args_list$size, 2L) +# } +# +# # axis font size +# if (is.null(args_list$cex.axis)) args_list$cex.axis <- 0.7 +# +# args_list$ann <- FALSE +# +# if (is.null(feats)) { +# include_values <- FALSE +# } else { +# include_values <- TRUE +# } +# +# dataDT <- data.table::as.data.table( +# x = data, +# geom = "XY", +# include_values = include_values +# ) +# +# +# if (length(feats) == 0L) { +# do.call(.plot_giotto_points_all, args = c(list(x = data), args_list)) +# } else if (length(feats) == 1L) { +# .plot_giotto_points_one( +# dataDT = dataDT, +# feats = feats, +# args_list = args_list +# ) +# } else { +# .plot_giotto_points_several( +# dataDT = dataDT, +# feats = feats, +# args_list = args_list +# ) +# } +# } \ No newline at end of file From b53e52e083fad2bad4fcdd719ae97b25be80c78a Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:16:50 -0500 Subject: [PATCH 02/24] new: implement calculateOverlap --- R/classes-giottoMatrixPoints.R | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/R/classes-giottoMatrixPoints.R b/R/classes-giottoMatrixPoints.R index 890558e5..2d45ac9a 100644 --- a/R/classes-giottoMatrixPoints.R +++ b/R/classes-giottoMatrixPoints.R @@ -117,18 +117,24 @@ setMethod("plot", signature("giottoBinPoints", "missing"), function(x, do.call(terra::plot, a) }) -setMethod("relate", signature(x = "giottoSpatial", y = "giottoBinPoints"), - function(x, y, relation, - pairs = TRUE, - na.rm = TRUE, - output = c("data.table", "matrix"), - use_names = TRUE, - ... - ) { - output <- match.arg(output, choices = c("data.table", "matrix")) -}) - +setMethod("calculateOverlap", signature("giottoPolygon", "giottoBinPoints"), + function(x, y, name_overlap = NULL, poly_subset_ids = NULL) { + checkmate::assert_character(poly_subset_ids, null.ok = TRUE) + if (!is.null(poly_subset_ids)) { + x <- x[x$poly_ID %in% poly_subset_ids] + } + res <- terra::relate(x[], y@spatial, + relation = "intersects", pairs = TRUE) |> + data.table::as.data.table() + names(res) <- c("poly_ID", "b") + res <- res[, .gbp_spatial_select_counts(y, b), by = "poly_ID"] + res[, "poly_ID" := spatIDs(x)[poly_ID]] + res + }) +setMethod("ext", signature("giottoBinPoints"), function(x, ...) { + ext(x@spatial, ...) +}) # internals From bfa5a6b4780fe55be6a7f4ee5a4e8ccc46591372 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:07:42 -0500 Subject: [PATCH 03/24] chore: code reorganization --- R/classes-images.R | 168 ++++++++++++ R/classes-overlaps.R | 180 +++++++++++++ R/classes-points.R | 112 ++++++++ R/classes-polygons.R | 81 ++++++ R/classes.R | 604 ------------------------------------------- 5 files changed, 541 insertions(+), 604 deletions(-) create mode 100644 R/classes-images.R create mode 100644 R/classes-overlaps.R create mode 100644 R/classes-points.R create mode 100644 R/classes-polygons.R diff --git a/R/classes-images.R b/R/classes-images.R new file mode 100644 index 00000000..ed7d6cc2 --- /dev/null +++ b/R/classes-images.R @@ -0,0 +1,168 @@ +# * definitions #### + +#' @title S4 giottoImage Class +#' @description Framework of giotto object to store and work with spatial +#' expression data +#' @concept giotto image object +#' @slot name name of Giotto image +#' @slot mg_object magick image object +#' @slot minmax minimum and maximum of associated spatial location coordinates +#' @slot boundaries x and y coordinate adjustments (default to 0) +#' @slot scale_factor image scaling relative to spatial locations +#' @slot resolution spatial location units covered per pixel +#' @slot file_path file path to the image if given +#' @slot OS_platform Operating System to run Giotto analysis on +#' @details +#' \[\strong{mg_object}\] Core object is any image that can be read by the +#' magick package +#' +#' \[\strong{boundaries}\] Boundary adjustments can be used to manually or +#' automatically through a script adjust the image with the spatial data. +#' +#' @returns giottoImage +#' @examples +#' giottoImage() +#' @export +giottoImage <- setClass( + Class = "giottoImage", + slots = c( + name = "character", + mg_object = "ANY", + minmax = "ANY", + boundaries = "ANY", + scale_factor = "ANY", + resolution = "ANY", + file_path = "ANY", + OS_platform = "ANY" + ), + prototype = list( + name = "test", + mg_object = NULL, + minmax = NULL, + boundaries = NULL, + scale_factor = NULL, + resolution = NULL, + file_path = NULL, + OS_platform = NULL + ) +) + +#' @title S4 giottoLargeImage Class +#' @description Image class for Giotto that uses \pkg{terra} `SpatRaster` as +#' a backend. If images are loaded from a file on disk then they are worked +#' with lazily, where only the values needed at any moment are loaded/sampled +#' into memory. Since `SpatRaster` objects are C pointers, `giottoLargeImage` +#' and inheriting classes need to run `reconnect()` after loading from a +#' saved object. +#' @concept giotto object image +#' @slot name name of large Giotto image +#' @slot raster_object terra `SpatRaster` object +#' @slot extent tracks the extent of the raster object. Note that most +#' processes should rely on the extent of the raster object instead of this. +#' @slot overall_extent terra extent object covering the original extent of +#' image +#' @slot scale_factor image scaling relative to spatial locations +#' @slot resolution spatial location units covered per pixel +#' @slot max_intensity approximate maximum value +#' @slot min_intensity approximate minimum value +#' @slot max_window value to set as maximum intensity in color scaling +#' @slot colors color mappings in hex codes +#' @slot is_int values are integers +#' @slot file_path file path to the image if given +#' @slot OS_platform Operating System to run Giotto analysis on +#' @returns giottoLargeImage +#' @examples +#' giottoLargeImage() +#' +#' @export giottoLargeImage +#' @exportClass giottoLargeImage +giottoLargeImage <- setClass( + Class = "giottoLargeImage", + contains = "giottoSubobject", + slots = c( + name = "ANY", + raster_object = "ANY", + extent = "ANY", # REMOVE? + overall_extent = "ANY", # REMOVE? New slot px_dims as replacement? + scale_factor = "ANY", + resolution = "ANY", + max_intensity = "numeric", + min_intensity = "numeric", + max_window = "numeric", # NEW + colors = "character", # NEW + is_int = "ANY", + file_path = "ANY", + OS_platform = "ANY" + ), + prototype = list( + name = NULL, + raster_object = NULL, + extent = NULL, + overall_extent = NULL, + scale_factor = NULL, + resolution = NULL, + max_intensity = NA_real_, + min_intensity = NA_real_, + max_window = NA_real_, + colors = grDevices::grey.colors(n = 256, start = 0, end = 1, gamma = 1), + is_int = NULL, + file_path = NULL, + OS_platform = NULL + ) +) + +#' @title S4 giottoAffineImage Class +#' @description +#' Class extending `giottoLargeImage`. When `shear()` or `spin()` operations +#' are performed on a `giottoLargeImage`, this class is instantiated. It +#' provides a way of storing the affine transformation and also lazily +#' performing it when required for a plotting preview. It is possible to force +#' the deferred affine transform using `doDeferred()` and return a processed +#' `giottoLargeImage`. +#' @slot affine contains `affine2d` object allowing lazily performed spatial +#' transforms +#' @slot funs list of functions associated with the object. Primarily to +#' perform the delayed/lazy operation +#' @returns `giottoAffineImage` +setClass( + "giottoAffineImage", + contains = c("giottoLargeImage"), + slots = c( + affine = "affine2d", + funs = "list" + ) +) + + + +# * internals #### + +# function for updating image objects if structure definitions have changed +.update_giotto_image <- function(x) { + if (inherits(x, "giottoLargeImage")) { + # 0.1.2 release adds colors & max_window slots + if (is.null(attr(x, "colors"))) { + attr(x, "colors") <- grDevices::grey.colors( + n = 256, start = 0, + end = 1, gamma = 1 + ) + } + if (is.null(attr(x, "max_window"))) { + # get a max intensity value + if (!is.null(x@max_intensity)) { + x@max_intensity <- .spatraster_intensity_range( + x@raster_object + )[["max"]] + } + + attr(x, "max_window") <- .bitdepth( + x@max_intensity, + return_max = TRUE + ) + } + + # 0.1.x release adds giottoImageStack + # deprecate + } + return(x) +} \ No newline at end of file diff --git a/R/classes-overlaps.R b/R/classes-overlaps.R new file mode 100644 index 00000000..3373bf1d --- /dev/null +++ b/R/classes-overlaps.R @@ -0,0 +1,180 @@ + +# * definitions #### +setClass("overlapInfo", + contains = c("spatFeatData", "VIRTUAL"), + slots = list(data = "ANY") +) +setClass("overlapPoint", contains = c("overlapInfo", "VIRTUAL")) +setClass("overlapIntensity", contains = c("overlapInfo", "VIRTUAL")) + +#' @name overlapPointDT-class +#' @title Polygon and Point Relationships +#' @description +#' Utility class for storing overlaps relationships between polygons and points +#' in a sparse `data.table` format. Retrieve the unique ID index of overlapped +#' points `[i, ]`. Get indices of which polys are overlapping specific feature +#' species using `[, j]`. +#' +#' Subsetting with `ids = FALSE` and `[i, j]` indexing is also supported. +#' +#' Supports `as.matrix` for conversion to `dgCMatrix`. Contained poly and +#' feature names simplify rownames/colnames and empty row/col creation. +#' +#' @slot data data.table. Table containing 3 integer cols: +#' +#' * `poly` - polygon index. Maps to `spat_ids` slot. +#' * `feat` - feat_ID_uniq (unique integer identifier) of a point detection +#' * `feat_id_index` - index of feature name mapping in `@feat_ids` slot. +#' @slot spat_unit character. Spatial unit (usually name of polygons information) +#' @slot feat_type character. Feature type (usually name of points information) +#' @slot provenance character. provenance information +#' @slot spat_ids character. Polygon names +#' @slot feat_ids character. Feature names +#' @slot nfeats integer (optional metadata). How many feature points were +#' used in overlap operation. Gives an idea of sparsity, but has no effect on +#' processing. +#' +#' @param x object +#' @param i numeric, character, logical. Index of or name of poly in overlapping +#' polygons +#' @param j numeric, character, logical. Index of or name of feature being +#' overlapped. +#' @param use_names logical (default = `FALSE`). Whether to return as integer +#' indices or with character ids. +#' @param ids logical (default = `TRUE`). Whether to return the requested +#' integer indices (`TRUE`) or the subset overlap object (`FALSE`). +#' @param drop not used. +#' @param \dots additional params to pass (none implemented) +#' @returns integer or character if only `i` or `j` provided, depending on +#' `use_names`. A subset `overlapPointDT` if both `i` and `j` are used. +#' @examples +#' g <- GiottoData::loadGiottoMini("vizgen") +#' poly <- g[["spatial_info", "z0"]][[1]] +#' ovlp <- overlaps(poly, "rna") +#' ovlp +#' +#' as.matrix(ovlp) +#' +#' dim(ovlp) +#' nrow(ovlp) # number of relationships +#' +#' # get feature unique IDs overlapped by nth poly +#' ovlp[1] # check one (no overlaps returns integer(0)) +#' ovlp[1:5] # check multiple +#' ovlp[1:5, use_names = TRUE] # returns feature names, but no longer unique +#' +#' # get integer index of poly(s) overlapping particular feature species +#' ovlp[, 1] +#' ovlp[, "Mlc1"] # this is the same +#' +#' # get a subset of overlap object +#' ovlp[1:10, ids = FALSE] # subset to first 10 polys +#' ovlp[, 1:10, ids = FALSE] # subset to first 10 feature species +#' ovlp[1:10, 1:10] # subset to first 10 polys and first 10 features species +#' @exportClass overlapPointDT +setClass("overlapPointDT", + contains = "overlapPoint", + slots = list( + spat_ids = "character", # spat_ids are unique, no need to record npoly + feat_ids = "character", + nfeats = "integer" + ) +) +setClass("overlapIntensityDT", + contains = "overlapIntensity", + slots = list( + nfeats = "integer", # skip spat_ids/feat_ids. This data is not sparse + fun = "character" + ) +) + +# * internals #### + +# update old overlaps information to new `overlapInfo` +.update_overlaps <- function(x, ...) { + + if (inherits(x, "giottoPolygon")) { + res <- .update_overlaps(x@overlaps, + poly_ids = x$poly_ID, + spat_unit = spatUnit(x), + ... + ) + x@overlaps <- res + return(x) + } + if (inherits(x, "list")) { + list_names <- names(x) + list_res <- lapply(list_names, function(ovlp_name) { + if (ovlp_name == "intensity") { + # recurse over list of intensity overlaps (can't set feat_type) + intensity_res <- .update_overlaps(x[["intensity"]], ...) + return(intensity_res) + } else { + updated_res <- .update_overlaps(x[[ovlp_name]], + feat_type = ovlp_name, + ... + ) + return(updated_res) + } + }) + names(list_res) <- list_names + return(list_res) + } + if (inherits(x, "SpatVector")) { + return(.update_overlaps_points(x, ...)) + } + if (inherits(x, "data.table")) { + return(.update_overlaps_intensity(x, ...)) + } + x # allow passthrough if not matching either signature +} + +#' @param x (SpatVector) the old overlaps representation to convert +#' @param poly_ids the `spatIDs()` of the giottoPolygon. This is since the +#' ordering of the polygon IDs within the overlaps data usually does not match. +#' @param spat_unit,feat_type spat_unit / feat_type +#' @noRd +.update_overlaps_points <- function(x, poly_ids, + spat_unit = NA_character_, feat_type = NA_character_, ...) { + checkmate::assert_character(poly_ids) + data <- terra::as.data.frame(x) + data.table::setDT(data) + odt <- new("overlapPointDT", + spat_unit = spat_unit, + feat_type = feat_type, + provenance = spat_unit, + nfeats = as.integer(nrow(data)) + ) + odt@feat_ids <- unique(data$feat_ID) + odt@spat_ids <- unique(poly_ids) + data[, feat := as.integer(feat_ID_uniq)] + data[, feat_ID_uniq := NULL] + data.table::setnames(data, + old = c("poly_ID", "feat_ID"), + new = c("poly", "feat_id_index") + ) + data <- data[!is.na(poly) & !is.na(feat),] # drop NAs + # Ensure data is stored as integer-based mapping + data[, poly := match(poly, odt@spat_ids)] + data[, feat_id_index := match(feat_id_index, odt@feat_ids)] + data.table::setkeyv(data, "feat") + data.table::setindexv(data, "poly") + data.table::setcolorder(data, c("poly", "feat", "feat_id_index")) + # add to object + odt@data <- data + odt +} + +.update_overlaps_intensity <- function(x, + spat_unit = NA_character_, feat_type = NA_character_, ...) { + odt <- new("overlapIntensityDT", + spat_unit = spat_unit, + feat_type = feat_type, + provenance = spat_unit, + nfeats = as.integer(ncol(x) - 1) + ) + fids <- setdiff(names(x), "poly_ID") + x <- x[, lapply(.SD, sum), by = "poly_ID", .SDcols = fids] + odt@data <- x + odt +} \ No newline at end of file diff --git a/R/classes-points.R b/R/classes-points.R new file mode 100644 index 00000000..2f142821 --- /dev/null +++ b/R/classes-points.R @@ -0,0 +1,112 @@ +## * definition #### +# giottoPoints class + +#' @title S4 giotto points Class +#' @description Giotto class to store and operate on points data +#' @concept giotto points class +#' @slot feat_type name of feature type +#' @slot spatVector terra spatVector to store point shapes +#' @slot networks feature networks +#' @slot unique_ID_cache cached unique feature IDs that should match the +#' spatVector slot +#' @details Contains vector-type feature data +#' @returns giottoPoints +#' @examples +#' giottoPoints() +#' @export +giottoPoints <- setClass( + Class = "giottoPoints", + contains = c("featData", "terraVectData", "giottoSubobject"), + slots = c( + networks = "ANY", + unique_ID_cache = "character" + ), + prototype = list( + networks = NULL, + unique_ID_cache = NA_character_ + ) +) + + +#' @title S4 giotto feature network Class +#' @description Giotto class to store and operate on feature network +#' @concept giotto points network class +#' @slot name name of feature network +#' @slot network_datatable feature network in data.table format +#' @slot network_lookup_id table mapping numeric network ID to unique +#' feature numerical IDs +#' @slot full fully connected network +#' @details contains feature network information +#' @returns featureNetwork +#' @examples +#' featureNetwork() +#' @export +featureNetwork <- setClass( + Class = "featureNetwork", + contains = c("nameData", "giottoSubobject"), + slots = c( + network_datatable = "ANY", + network_lookup_id = "ANY", + full = "ANY" + ), + prototype = list( + network_datatable = NULL, + network_lookup_id = NULL, + full = NULL + ) +) + +#' @title Update giotto points object +#' @name updateGiottoPointsObject +#' @param gpoints giotto points object +#' @returns GiottoPointsObject +#' @examples +#' g <- GiottoData::loadSubObjectMini("giottoPoints") +#' +#' updateGiottoPointsObject(g) +#' @export +updateGiottoPointsObject <- function(gpoints) { + if (!inherits(gpoints, "giottoPoints")) { + stop("This function is only for giottoPoints") + } + + # 3.2.X adds cacheing of IDs + if (is.null(attr(gpoints, "unique_ID_cache"))) { + attr(gpoints, "unique_ID_cache") <- unique( + as.list(gpoints@spatVector)$feat_ID + ) + } + + gpoints +} + + + + + + + + + + + + + +# * packed classes #### + +# for use with wrap() generic +setClass( + "packedGiottoPoints", + slots = c( + feat_type = "character", + packed_spatVector = "ANY", + networks = "ANY", + unique_ID_cache = "character" + ), + prototype = list( + feat_type = NA_character_, + packed_spatVector = NULL, + networks = NULL, + unique_ID_cache = NA_character_ + ) +) \ No newline at end of file diff --git a/R/classes-polygons.R b/R/classes-polygons.R new file mode 100644 index 00000000..831219ac --- /dev/null +++ b/R/classes-polygons.R @@ -0,0 +1,81 @@ +# * definition #### +# giottoPolygon class + +#' @title S4 giotto polygon Class +#' @description Giotto class to store and operate on polygon-like data +#' @concept giotto polygon class +#' @slot name name of polygon shapes +#' @slot spatVector terra spatVector to store polygon shapes +#' @slot spatVectorCentroids centroids of polygon shapes +#' @slot overlaps information about overlapping points and polygons +#' @slot unique_ID_cache cached unique spatial IDs that should match the +#' spatVector slot +#' @details holds polygon data +#' @returns giottoPolygon +#' @examples +#' giottoPolygon() +#' @export +giottoPolygon <- setClass( + Class = "giottoPolygon", + contains = c("nameData", "terraVectData", "giottoSubobject"), + slots = c( + spatVectorCentroids = "ANY", + overlaps = "ANY", + unique_ID_cache = "character" + ), + prototype = list( + spatVectorCentroids = NULL, + overlaps = NULL, + unique_ID_cache = NA_character_ + ) +) + +#' @title Update giotto polygon object +#' @name updateGiottoPolygonObject +#' @param gpoly giotto polygon object +#' @returns GiottoPolygonObject +#' @examples +#' g <- GiottoData::loadSubObjectMini("giottoPolygon") +#' +#' updateGiottoPolygonObject(g) +#' @export +updateGiottoPolygonObject <- function(gpoly) { + if (!inherits(gpoly, "giottoPolygon")) { + stop("This function is only for giottoPolygon") + } + + # 3.2.X adds cacheing of IDs + if (is.null(attr(gpoly, "unique_ID_cache"))) { + attr(gpoly, "unique_ID_cache") <- unique( + as.list(gpoly@spatVector)$poly_ID + ) + } + + # 0.4.7 changes overlaps representation + # intersection `SpatVector` -> `overlapInfo`-inheriting classes + gpoly <- .update_overlaps(gpoly) + + gpoly +} + + + + +# * packed classes #### + +# for use with wrap() generic +setClass("packedGiottoPolygon", + contains = c("nameData", "giottoSubobject"), + slots = c( + packed_spatVector = "ANY", + packed_spatVectorCentroids = "ANY", + packed_overlaps = "ANY", + unique_ID_cache = "character" + ), + prototype = list( + packed_spatVector = NULL, + packed_spatVectorCentroids = NULL, + packed_overlaps = NULL, + unique_ID_cache = NA_character_ + ) +) \ No newline at end of file diff --git a/R/classes.R b/R/classes.R index af14a9e9..3967ee49 100644 --- a/R/classes.R +++ b/R/classes.R @@ -1399,610 +1399,6 @@ setClass("spatEnrObj", -# SUBCELLULAR #### - -## giottoPolygon class #### - -# * definition #### -# giottoPolygon class - -#' @title S4 giotto polygon Class -#' @description Giotto class to store and operate on polygon-like data -#' @concept giotto polygon class -#' @slot name name of polygon shapes -#' @slot spatVector terra spatVector to store polygon shapes -#' @slot spatVectorCentroids centroids of polygon shapes -#' @slot overlaps information about overlapping points and polygons -#' @slot unique_ID_cache cached unique spatial IDs that should match the -#' spatVector slot -#' @details holds polygon data -#' @returns giottoPolygon -#' @examples -#' giottoPolygon() -#' @export -giottoPolygon <- setClass( - Class = "giottoPolygon", - contains = c("nameData", "terraVectData", "giottoSubobject"), - slots = c( - spatVectorCentroids = "ANY", - overlaps = "ANY", - unique_ID_cache = "character" - ), - prototype = list( - spatVectorCentroids = NULL, - overlaps = NULL, - unique_ID_cache = NA_character_ - ) -) - - - - -#' @title Update giotto polygon object -#' @name updateGiottoPolygonObject -#' @param gpoly giotto polygon object -#' @returns GiottoPolygonObject -#' @examples -#' g <- GiottoData::loadSubObjectMini("giottoPolygon") -#' -#' updateGiottoPolygonObject(g) -#' @export -updateGiottoPolygonObject <- function(gpoly) { - if (!inherits(gpoly, "giottoPolygon")) { - stop("This function is only for giottoPolygon") - } - - # 3.2.X adds cacheing of IDs - if (is.null(attr(gpoly, "unique_ID_cache"))) { - attr(gpoly, "unique_ID_cache") <- unique( - as.list(gpoly@spatVector)$poly_ID - ) - } - - # 0.4.7 changes overlaps representation - # intersection `SpatVector` -> `overlapInfo`-inheriting classes - gpoly <- .update_overlaps(gpoly) - - gpoly -} - - - - - - -# for use with wrap() generic -setClass("packedGiottoPolygon", - contains = c("nameData", "giottoSubobject"), - slots = c( - packed_spatVector = "ANY", - packed_spatVectorCentroids = "ANY", - packed_overlaps = "ANY", - unique_ID_cache = "character" - ), - prototype = list( - packed_spatVector = NULL, - packed_spatVectorCentroids = NULL, - packed_overlaps = NULL, - unique_ID_cache = NA_character_ - ) -) - - - - - - -## giottoPoints class #### - - -## * definition #### -# giottoPoints class - -#' @title S4 giotto points Class -#' @description Giotto class to store and operate on points data -#' @concept giotto points class -#' @slot feat_type name of feature type -#' @slot spatVector terra spatVector to store point shapes -#' @slot networks feature networks -#' @slot unique_ID_cache cached unique feature IDs that should match the -#' spatVector slot -#' @details Contains vector-type feature data -#' @returns giottoPoints -#' @examples -#' giottoPoints() -#' @export -giottoPoints <- setClass( - Class = "giottoPoints", - contains = c("featData", "terraVectData", "giottoSubobject"), - slots = c( - networks = "ANY", - unique_ID_cache = "character" - ), - prototype = list( - networks = NULL, - unique_ID_cache = NA_character_ - ) -) - - - - -#' @title Update giotto points object -#' @name updateGiottoPointsObject -#' @param gpoints giotto points object -#' @returns GiottoPointsObject -#' @examples -#' g <- GiottoData::loadSubObjectMini("giottoPoints") -#' -#' updateGiottoPointsObject(g) -#' @export -updateGiottoPointsObject <- function(gpoints) { - if (!inherits(gpoints, "giottoPoints")) { - stop("This function is only for giottoPoints") - } - - # 3.2.X adds cacheing of IDs - if (is.null(attr(gpoints, "unique_ID_cache"))) { - attr(gpoints, "unique_ID_cache") <- unique( - as.list(gpoints@spatVector)$feat_ID - ) - } - - gpoints -} - - - - - - - - - - - -# for use with wrap() generic -setClass( - "packedGiottoPoints", - slots = c( - feat_type = "character", - packed_spatVector = "ANY", - networks = "ANY", - unique_ID_cache = "character" - ), - prototype = list( - feat_type = NA_character_, - packed_spatVector = NULL, - networks = NULL, - unique_ID_cache = NA_character_ - ) -) - - - - -## overlapInfo #### -setClass("overlapInfo", - contains = c("spatFeatData", "VIRTUAL"), - slots = list(data = "ANY") -) -setClass("overlapPoint", contains = c("overlapInfo", "VIRTUAL")) -setClass("overlapIntensity", contains = c("overlapInfo", "VIRTUAL")) - -#' @name overlapPointDT-class -#' @title Polygon and Point Relationships -#' @description -#' Utility class for storing overlaps relationships between polygons and points -#' in a sparse `data.table` format. Retrieve the unique ID index of overlapped -#' points `[i, ]`. Get indices of which polys are overlapping specific feature -#' species using `[, j]`. -#' -#' Subsetting with `ids = FALSE` and `[i, j]` indexing is also supported. -#' -#' Supports `as.matrix` for conversion to `dgCMatrix`. Contained poly and -#' feature names simplify rownames/colnames and empty row/col creation. -#' -#' @slot data data.table. Table containing 3 integer cols: -#' -#' * `poly` - polygon index. Maps to `spat_ids` slot. -#' * `feat` - feat_ID_uniq (unique integer identifier) of a point detection -#' * `feat_id_index` - index of feature name mapping in `@feat_ids` slot. -#' @slot spat_unit character. Spatial unit (usually name of polygons information) -#' @slot feat_type character. Feature type (usually name of points information) -#' @slot provenance character. provenance information -#' @slot spat_ids character. Polygon names -#' @slot feat_ids character. Feature names -#' @slot nfeats integer (optional metadata). How many feature points were -#' used in overlap operation. Gives an idea of sparsity, but has no effect on -#' processing. -#' -#' @param x object -#' @param i numeric, character, logical. Index of or name of poly in overlapping -#' polygons -#' @param j numeric, character, logical. Index of or name of feature being -#' overlapped. -#' @param use_names logical (default = `FALSE`). Whether to return as integer -#' indices or with character ids. -#' @param ids logical (default = `TRUE`). Whether to return the requested -#' integer indices (`TRUE`) or the subset overlap object (`FALSE`). -#' @param drop not used. -#' @param \dots additional params to pass (none implemented) -#' @returns integer or character if only `i` or `j` provided, depending on -#' `use_names`. A subset `overlapPointDT` if both `i` and `j` are used. -#' @examples -#' g <- GiottoData::loadGiottoMini("vizgen") -#' poly <- g[["spatial_info", "z0"]][[1]] -#' ovlp <- overlaps(poly, "rna") -#' ovlp -#' -#' as.matrix(ovlp) -#' -#' dim(ovlp) -#' nrow(ovlp) # number of relationships -#' -#' # get feature unique IDs overlapped by nth poly -#' ovlp[1] # check one (no overlaps returns integer(0)) -#' ovlp[1:5] # check multiple -#' ovlp[1:5, use_names = TRUE] # returns feature names, but no longer unique -#' -#' # get integer index of poly(s) overlapping particular feature species -#' ovlp[, 1] -#' ovlp[, "Mlc1"] # this is the same -#' -#' # get a subset of overlap object -#' ovlp[1:10, ids = FALSE] # subset to first 10 polys -#' ovlp[, 1:10, ids = FALSE] # subset to first 10 feature species -#' ovlp[1:10, 1:10] # subset to first 10 polys and first 10 features species -#' @exportClass overlapPointDT -setClass("overlapPointDT", - contains = "overlapPoint", - slots = list( - spat_ids = "character", # spat_ids are unique, no need to record npoly - feat_ids = "character", - nfeats = "integer" - ) -) -setClass("overlapIntensityDT", - contains = "overlapIntensity", - slots = list( - nfeats = "integer", # skip spat_ids/feat_ids. This data is not sparse - fun = "character" - ) -) - - - -# update old overlaps information to new `overlapInfo` -.update_overlaps <- function(x, ...) { - - if (inherits(x, "giottoPolygon")) { - res <- .update_overlaps(x@overlaps, - poly_ids = x$poly_ID, - spat_unit = spatUnit(x), - ... - ) - x@overlaps <- res - return(x) - } - if (inherits(x, "list")) { - list_names <- names(x) - list_res <- lapply(list_names, function(ovlp_name) { - if (ovlp_name == "intensity") { - # recurse over list of intensity overlaps (can't set feat_type) - intensity_res <- .update_overlaps(x[["intensity"]], ...) - return(intensity_res) - } else { - updated_res <- .update_overlaps(x[[ovlp_name]], - feat_type = ovlp_name, - ... - ) - return(updated_res) - } - }) - names(list_res) <- list_names - return(list_res) - } - if (inherits(x, "SpatVector")) { - return(.update_overlaps_points(x, ...)) - } - if (inherits(x, "data.table")) { - return(.update_overlaps_intensity(x, ...)) - } - x # allow passthrough if not matching either signature -} - -#' @param x (SpatVector) the old overlaps representation to convert -#' @param poly_ids the `spatIDs()` of the giottoPolygon. This is since the -#' ordering of the polygon IDs within the overlaps data usually does not match. -#' @param spat_unit,feat_type spat_unit / feat_type -#' @noRd -.update_overlaps_points <- function(x, poly_ids, - spat_unit = NA_character_, feat_type = NA_character_, ...) { - checkmate::assert_character(poly_ids) - data <- terra::as.data.frame(x) - data.table::setDT(data) - odt <- new("overlapPointDT", - spat_unit = spat_unit, - feat_type = feat_type, - provenance = spat_unit, - nfeats = as.integer(nrow(data)) - ) - odt@feat_ids <- unique(data$feat_ID) - odt@spat_ids <- unique(poly_ids) - data[, feat := as.integer(feat_ID_uniq)] - data[, feat_ID_uniq := NULL] - data.table::setnames(data, - old = c("poly_ID", "feat_ID"), - new = c("poly", "feat_id_index") - ) - data <- data[!is.na(poly) & !is.na(feat),] # drop NAs - # Ensure data is stored as integer-based mapping - data[, poly := match(poly, odt@spat_ids)] - data[, feat_id_index := match(feat_id_index, odt@feat_ids)] - data.table::setkeyv(data, "feat") - data.table::setindexv(data, "poly") - data.table::setcolorder(data, c("poly", "feat", "feat_id_index")) - # add to object - odt@data <- data - odt -} - -.update_overlaps_intensity <- function(x, - spat_unit = NA_character_, feat_type = NA_character_, ...) { - odt <- new("overlapIntensityDT", - spat_unit = spat_unit, - feat_type = feat_type, - provenance = spat_unit, - nfeats = as.integer(ncol(x) - 1) - ) - fids <- setdiff(names(x), "poly_ID") - x <- x[, lapply(.SD, sum), by = "poly_ID", .SDcols = fids] - odt@data <- x - odt -} - -## featureNetwork class #### - - -## * definition #### -# featureNetwork class - - -#' @title S4 giotto feature network Class -#' @description Giotto class to store and operate on feature network -#' @concept giotto points network class -#' @slot name name of feature network -#' @slot network_datatable feature network in data.table format -#' @slot network_lookup_id table mapping numeric network ID to unique -#' feature numerical IDs -#' @slot full fully connected network -#' @details contains feature network information -#' @returns featureNetwork -#' @examples -#' featureNetwork() -#' @export -featureNetwork <- setClass( - Class = "featureNetwork", - contains = c("nameData", "giottoSubobject"), - slots = c( - network_datatable = "ANY", - network_lookup_id = "ANY", - full = "ANY" - ), - prototype = list( - network_datatable = NULL, - network_lookup_id = NULL, - full = NULL - ) -) - - -# IMAGES #### - -## giottoImage class #### - -# * definition #### -# giottoImage class - -#' @title S4 giottoImage Class -#' @description Framework of giotto object to store and work with spatial -#' expression data -#' @concept giotto image object -#' @slot name name of Giotto image -#' @slot mg_object magick image object -#' @slot minmax minimum and maximum of associated spatial location coordinates -#' @slot boundaries x and y coordinate adjustments (default to 0) -#' @slot scale_factor image scaling relative to spatial locations -#' @slot resolution spatial location units covered per pixel -#' @slot file_path file path to the image if given -#' @slot OS_platform Operating System to run Giotto analysis on -#' @details -#' \[\strong{mg_object}\] Core object is any image that can be read by the -#' magick package -#' -#' \[\strong{boundaries}\] Boundary adjustments can be used to manually or -#' automatically through a script adjust the image with the spatial data. -#' -#' @returns giottoImage -#' @examples -#' giottoImage() -#' @export -giottoImage <- setClass( - Class = "giottoImage", - slots = c( - name = "character", - mg_object = "ANY", - minmax = "ANY", - boundaries = "ANY", - scale_factor = "ANY", - resolution = "ANY", - file_path = "ANY", - OS_platform = "ANY" - ), - prototype = list( - name = "test", - mg_object = NULL, - minmax = NULL, - boundaries = NULL, - scale_factor = NULL, - resolution = NULL, - file_path = NULL, - OS_platform = NULL - ) -) - - - - - - -## giottoLargeImage class #### - - -## * definition #### -# giottoLargeImage class - -#' @title S4 giottoLargeImage Class -#' @description Image class for Giotto that uses \pkg{terra} `SpatRaster` as -#' a backend. If images are loaded from a file on disk then they are worked -#' with lazily, where only the values needed at any moment are loaded/sampled -#' into memory. Since `SpatRaster` objects are C pointers, `giottoLargeImage` -#' and inheriting classes need to run `reconnect()` after loading from a -#' saved object. -#' @concept giotto object image -#' @slot name name of large Giotto image -#' @slot raster_object terra `SpatRaster` object -#' @slot extent tracks the extent of the raster object. Note that most -#' processes should rely on the extent of the raster object instead of this. -#' @slot overall_extent terra extent object covering the original extent of -#' image -#' @slot scale_factor image scaling relative to spatial locations -#' @slot resolution spatial location units covered per pixel -#' @slot max_intensity approximate maximum value -#' @slot min_intensity approximate minimum value -#' @slot max_window value to set as maximum intensity in color scaling -#' @slot colors color mappings in hex codes -#' @slot is_int values are integers -#' @slot file_path file path to the image if given -#' @slot OS_platform Operating System to run Giotto analysis on -#' @returns giottoLargeImage -#' @examples -#' giottoLargeImage() -#' -#' @export giottoLargeImage -#' @exportClass giottoLargeImage -giottoLargeImage <- setClass( - Class = "giottoLargeImage", - contains = "giottoSubobject", - slots = c( - name = "ANY", - raster_object = "ANY", - extent = "ANY", # REMOVE? - overall_extent = "ANY", # REMOVE? New slot px_dims as replacement? - scale_factor = "ANY", - resolution = "ANY", - max_intensity = "numeric", - min_intensity = "numeric", - max_window = "numeric", # NEW - colors = "character", # NEW - is_int = "ANY", - file_path = "ANY", - OS_platform = "ANY" - ), - prototype = list( - name = NULL, - raster_object = NULL, - extent = NULL, - overall_extent = NULL, - scale_factor = NULL, - resolution = NULL, - max_intensity = NA_real_, - min_intensity = NA_real_, - max_window = NA_real_, - colors = grDevices::grey.colors(n = 256, start = 0, end = 1, gamma = 1), - is_int = NULL, - file_path = NULL, - OS_platform = NULL - ) -) - -#' @title S4 giottoAffineImage Class -#' @description -#' Class extending `giottoLargeImage`. When `shear()` or `spin()` operations -#' are performed on a `giottoLargeImage`, this class is instantiated. It -#' provides a way of storing the affine transformation and also lazily -#' performing it when required for a plotting preview. It is possible to force -#' the deferred affine transform using `doDeferred()` and return a processed -#' `giottoLargeImage`. -#' @slot affine contains `affine2d` object allowing lazily performed spatial -#' transforms -#' @slot funs list of functions associated with the object. Primarily to -#' perform the delayed/lazy operation -#' @returns `giottoAffineImage` -setClass( - "giottoAffineImage", - contains = c("giottoLargeImage"), - slots = c( - affine = "affine2d", - funs = "list" - ) -) - - - - - -# function for updating image objects if structure definitions have changed -.update_giotto_image <- function(x) { - if (inherits(x, "giottoLargeImage")) { - # 0.1.2 release adds colors & max_window slots - if (is.null(attr(x, "colors"))) { - attr(x, "colors") <- grDevices::grey.colors( - n = 256, start = 0, - end = 1, gamma = 1 - ) - } - if (is.null(attr(x, "max_window"))) { - # get a max intensity value - if (!is.null(x@max_intensity)) { - x@max_intensity <- .spatraster_intensity_range( - x@raster_object - )[["max"]] - } - - attr(x, "max_window") <- .bitdepth( - x@max_intensity, - return_max = TRUE - ) - } - - # 0.1.x release adds giottoImageStack - # deprecate - } - return(x) -} - - - -## giottoImageStack class #### - -## * definition #### -# giottoImageStack class - -# giottoImageStack <- setClass( -# Class = "giottoImageStack", -# -# slots = c( -# name = 'character', -# images = 'giottoLargeImage', -# weight = 'numeric' -# ) -# ) - - - - # giottoSpatial #### From 792878f686089842e2b9480a64734ef66ff26b9e Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:08:02 -0500 Subject: [PATCH 04/24] chore: rename file --- ...ottoMatrixPoints.R => classes-binpoints.R} | 112 ++++++------------ 1 file changed, 36 insertions(+), 76 deletions(-) rename R/{classes-giottoMatrixPoints.R => classes-binpoints.R} (84%) diff --git a/R/classes-giottoMatrixPoints.R b/R/classes-binpoints.R similarity index 84% rename from R/classes-giottoMatrixPoints.R rename to R/classes-binpoints.R index 2d45ac9a..ff717fd6 100644 --- a/R/classes-giottoMatrixPoints.R +++ b/R/classes-binpoints.R @@ -1,34 +1,5 @@ -createGiottoBinPoints <- function(expr_values, spatial_locs) { - ids_p <- spatial_locs$cell_ID - spatial_locs[] <- spatial_locs[][, c("sdimx", "sdimy")] # drop point IDs col - sl_points <- as.points(spatial_locs) - - # get counts information as `d` - M <- expr_values[] - if (!inherits(M, "Matrix")) stop("expr_values should be Matrix") - d <- data.table::as.data.table(Matrix::summary(M)) - ids_m <- colnames(M) |> as.character() - - # keep only existing points geoms so it matches `d` content - exist_j <- sort(unique(d$j)) - keep_ids <- ids_m[exist_j] # ids that actually exist (character) - keep_index <- which(ids_p %in% keep_ids) # i integer values for spatial - if (length(keep_index) == 0L) warning("No bin points exist\n") - # remove non-existing from spatial info - ids_p <- ids_p[keep_index] - sl_points <- sl_points[keep_index] - - x <- new("giottoBinPoints", - spatial = sl_points, - counts = d, - bid = ids_m, - pmap = match(ids_p, ids_m), - fid = rownames(M), - compact = TRUE - ) - .gbp_compact(x, ids_p = ids_p, exist_j = exist_j) -} +# * definitions #### setClass("giottoBinPoints", contains = c("featData", "giottoSubobject"), slots = list( @@ -44,6 +15,7 @@ setClass("giottoBinPoints", ) ) +# * methods #### setMethod("show", signature("giottoBinPoints"), function(object) { cat(sprintf("An object of class %s\n", class(object))) .show_feat(object) @@ -136,6 +108,40 @@ setMethod("ext", signature("giottoBinPoints"), function(x, ...) { ext(x@spatial, ...) }) + +# * constructor #### + +createGiottoBinPoints <- function(expr_values, spatial_locs) { + ids_p <- spatial_locs$cell_ID + spatial_locs[] <- spatial_locs[][, c("sdimx", "sdimy")] # drop point IDs col + sl_points <- as.points(spatial_locs) + + # get counts information as `d` + M <- expr_values[] + if (!inherits(M, "Matrix")) stop("expr_values should be Matrix") + d <- data.table::as.data.table(Matrix::summary(M)) + ids_m <- colnames(M) |> as.character() + + # keep only existing points geoms so it matches `d` content + exist_j <- sort(unique(d$j)) + keep_ids <- ids_m[exist_j] # ids that actually exist (character) + keep_index <- which(ids_p %in% keep_ids) # i integer values for spatial + if (length(keep_index) == 0L) warning("No bin points exist\n") + # remove non-existing from spatial info + ids_p <- ids_p[keep_index] + sl_points <- sl_points[keep_index] + + x <- new("giottoBinPoints", + spatial = sl_points, + counts = d, + bid = ids_m, + pmap = match(ids_p, ids_m), + fid = rownames(M), + compact = TRUE + ) + .gbp_compact(x, ids_p = ids_p, exist_j = exist_j) +} + # internals # pids are mapped 1:1 with spatial row number @@ -214,49 +220,3 @@ setMethod("ext", signature("giottoBinPoints"), function(x, ...) { counts } - -# .plot_giotto_points_raster <- function(data, feats = NULL, ...) { -# args_list <- list(...) -# -# # raster size -# if (is.null(args_list$size)) { -# args_list$size <- c(600, 600) -# } else if (length(args_list$size) == 1L) { -# # if size provided as single value, replicate to give a square window -# args_list$size <- rep(args_list$size, 2L) -# } -# -# # axis font size -# if (is.null(args_list$cex.axis)) args_list$cex.axis <- 0.7 -# -# args_list$ann <- FALSE -# -# if (is.null(feats)) { -# include_values <- FALSE -# } else { -# include_values <- TRUE -# } -# -# dataDT <- data.table::as.data.table( -# x = data, -# geom = "XY", -# include_values = include_values -# ) -# -# -# if (length(feats) == 0L) { -# do.call(.plot_giotto_points_all, args = c(list(x = data), args_list)) -# } else if (length(feats) == 1L) { -# .plot_giotto_points_one( -# dataDT = dataDT, -# feats = feats, -# args_list = args_list -# ) -# } else { -# .plot_giotto_points_several( -# dataDT = dataDT, -# feats = feats, -# args_list = args_list -# ) -# } -# } \ No newline at end of file From 0125440f282d32f4f2d6c9561e6587283f38f369 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:05:14 -0500 Subject: [PATCH 05/24] more code reorganization --- R/aggregate.R | 82 +--------- R/classes-images.R | 4 + R/classes-overlaps.R | 87 ++++++++++ R/classes-points.R | 3 + R/classes-polygons.R | 3 + R/classes-utils.R | 67 ++++++++ R/classes-virtuals.R | 297 ++++++++++++++++++++++++++++++++++ R/classes.R | 375 +------------------------------------------ 8 files changed, 469 insertions(+), 449 deletions(-) create mode 100644 R/classes-utils.R create mode 100644 R/classes-virtuals.R diff --git a/R/aggregate.R b/R/aggregate.R index 8c286bf6..f0f401e7 100644 --- a/R/aggregate.R +++ b/R/aggregate.R @@ -1,5 +1,6 @@ # collate #' @include generics.R +#' @include classes.R NULL # aggregate expression #### @@ -802,6 +803,7 @@ setMethod( ) ) + # (constructor) see classes-overlaps.R .create_overlap_point_dt(x, y, res, feat_ids = feat_ids) } ) @@ -988,87 +990,7 @@ calculateOverlapRaster <- function( } } -#' @param overlap_data `data.table` of extracted intensity values per poly_ID -#' @noRd -.create_overlap_intensity_dt <- function(overlap_data) { - odt <- new("overlapIntensityDT", data = overlap_data) - odt@nfeats <- ncol(overlap_data) - 1L - odt -} - -#' @param x from data (SpatVector) -#' @param y to data (SpatVector) -#' @param overlap_data relationships (data.frame). Expected to be numeric row -#' indices between x and y -#' @param keep additional col(s) in `y` to keep -#' @noRd -.create_overlap_point_dt <- function(x, y, - overlap_data, keep = NULL, feat_ids) { - poly <- feat_idx <- feat <- feat_id_index <- NULL # NSE vars - # cleanup input overlap_data - checkmate::assert_data_frame(overlap_data) - data.table::setDT(overlap_data) - cnames <- colnames(overlap_data) - data.table::setnames(overlap_data, - old = c(cnames[[2]], cnames[[1]]), - new = c("poly", "feat_idx") - ) - # make relationships table sparse by removing non-overlapped features - # these results are indexed by all features, so no need to filter - # non-overlapped polys - overlap_data <- overlap_data[!is.na(poly)] - - # extract needed info from y - keep <- c("feat_ID", "feat_ID_uniq", keep) - ytab <- terra::as.data.frame(y[overlap_data$feat_idx, keep]) - - # initialize overlap object and needed ids - sids <- x$poly_ID - fids <- unique(ytab$feat_ID) - odt <- new("overlapPointDT", - spat_ids = sids, - feat_ids = feat_ids, - nfeats = as.integer(nrow(y)) - ) - - # Ensure data is stored as integer or integer-based mapping - ## - if poly/feat_idx contents are NOT integer coercible, establish a map # - if (!overlap_data[, checkmate::test_integerish(head(poly, 100))]) { - overlap_data[, poly := match(poly, sids)] - } - if (!overlap_data[, checkmate::test_integerish(head(feat_idx, 100))]) { - overlap_data[, feat_idx := match(feat_idx, fids)] - } - ## -- if still not integer, coerce to integer --------------------------- # - if (!is.integer(overlap_data$poly[1])) { - overlap_data[, poly := as.integer(poly)] - } - if (!is.integer(overlap_data$feat_idx[1])) { - overlap_data[, feat_idx := as.integer(feat_idx)] - } - # append y attribute info - overlap_data <- cbind(overlap_data, ytab) - data.table::setnames(overlap_data, - old = c("feat_ID_uniq", "feat_ID"), - new = c("feat", "feat_id_index") - ) - if (!is.integer(overlap_data$feat[1])) { - overlap_data[, feat := as.integer(feat)] - } - # add feat_ID map - overlap_data[, feat_id_index := match(feat_id_index, odt@feat_ids)] - # remove feat_idx which may not be reliable after feature subsets - overlap_data[, feat_idx := NULL] - # set indices - data.table::setkeyv(overlap_data, "feat") - data.table::setindexv(overlap_data, "poly") - data.table::setcolorder(overlap_data, c("poly", "feat", "feat_id_index")) - # add to object - odt@data <- overlap_data - - odt -} diff --git a/R/classes-images.R b/R/classes-images.R index ed7d6cc2..1850cddc 100644 --- a/R/classes-images.R +++ b/R/classes-images.R @@ -1,3 +1,7 @@ +#' @include classes-virtuals.R +#' @include classes-utils.R +NULL + # * definitions #### #' @title S4 giottoImage Class diff --git a/R/classes-overlaps.R b/R/classes-overlaps.R index 3373bf1d..6abea393 100644 --- a/R/classes-overlaps.R +++ b/R/classes-overlaps.R @@ -1,3 +1,5 @@ +#' @include classes-virtuals.R +NULL # * definitions #### setClass("overlapInfo", @@ -90,6 +92,91 @@ setClass("overlapIntensityDT", # * internals #### +#' @param overlap_data `data.table` of extracted intensity values per poly_ID +#' @noRd +.create_overlap_intensity_dt <- function(overlap_data) { + odt <- new("overlapIntensityDT", data = overlap_data) + odt@nfeats <- ncol(overlap_data) - 1L + odt +} + +#' @param x from data (SpatVector) - need just the poly_ID info +#' @param y to data (SpatVector) - need meta info extracted from cols by overlap info + nrow +#' @param overlap_data relationships (data.frame). Expected to be numeric row +#' indices between x and y +#' @param keep additional col(s) in `y` to keep +#' @examples +#' d <- data.frame(a = sort(rep(1:2, 2)), b = 1:4) +#' +#' @noRd +.create_overlap_point_dt <- function(x, y, + overlap_data, keep = NULL, feat_ids) { + poly <- feat_idx <- feat <- feat_id_index <- NULL # NSE vars + # cleanup input overlap_data + checkmate::assert_data_frame(overlap_data) + data.table::setDT(overlap_data) + cnames <- colnames(overlap_data) + data.table::setnames(overlap_data, + old = c(cnames[[2]], cnames[[1]]), + new = c("poly", "feat_idx") + ) + # make relationships table sparse by removing non-overlapped features + # these results are indexed by all features, so no need to filter + # non-overlapped polys + overlap_data <- overlap_data[!is.na(poly)] + + # extract needed info from y + keep <- c("feat_ID", keep) + ytab <- terra::as.data.frame(y[overlap_data$feat_idx, keep]) + + # initialize overlap object and needed ids + sids <- x$poly_ID + fids <- unique(ytab$feat_ID) + odt <- new("overlapPointDT", + spat_ids = sids, + feat_ids = feat_ids, + nfeats = as.integer(nrow(y)) + ) + + # Ensure data is stored as integer or integer-based mapping + ## - if poly/feat_idx contents are NOT integer coercible, establish a map # + if (!overlap_data[, checkmate::test_integerish(head(poly, 100))]) { + overlap_data[, poly := match(poly, sids)] + } + if (!overlap_data[, checkmate::test_integerish(head(feat_idx, 100))]) { + overlap_data[, feat_idx := match(feat_idx, fids)] + } + ## -- if still not integer, coerce to integer --------------------------- # + if (!is.integer(overlap_data$poly[1])) { + overlap_data[, poly := as.integer(poly)] + } + if (!is.integer(overlap_data$feat_idx[1])) { + overlap_data[, feat_idx := as.integer(feat_idx)] + } + + # append y attribute info + overlap_data <- cbind(overlap_data, ytab) + data.table::setnames(overlap_data, + old = c("feat_ID_uniq", "feat_ID"), + new = c("feat", "feat_id_index") + ) + if (!is.integer(overlap_data$feat[1])) { + overlap_data[, feat := as.integer(feat)] + } + # add feat_ID map + overlap_data[, feat_id_index := match(feat_id_index, odt@feat_ids)] + # remove feat_idx which may not be reliable after feature subsets + overlap_data[, feat_idx := NULL] + # set indices + data.table::setkeyv(overlap_data, "feat") + data.table::setindexv(overlap_data, "poly") + data.table::setcolorder(overlap_data, c("poly", "feat", "feat_id_index")) + # add to object + odt@data <- overlap_data + + odt +} + # update old overlaps information to new `overlapInfo` .update_overlaps <- function(x, ...) { diff --git a/R/classes-points.R b/R/classes-points.R index 2f142821..125c39a0 100644 --- a/R/classes-points.R +++ b/R/classes-points.R @@ -1,3 +1,6 @@ +#' @include classes-virtuals.R +NULL + ## * definition #### # giottoPoints class diff --git a/R/classes-polygons.R b/R/classes-polygons.R index 831219ac..99e4d854 100644 --- a/R/classes-polygons.R +++ b/R/classes-polygons.R @@ -1,3 +1,6 @@ +#' @include classes-virtuals.R +NULL + # * definition #### # giottoPolygon class diff --git a/R/classes-utils.R b/R/classes-utils.R new file mode 100644 index 00000000..919aa44c --- /dev/null +++ b/R/classes-utils.R @@ -0,0 +1,67 @@ +#' @include classes-virtuals.R +NULL + +# ** affine2d #### +setClass( + Class = "affine2d", + slots = list( + anchor = "ANY", + affine = "matrix", + order = "character", + rotate = "numeric", + shear = "numeric", + scale = "numeric", + translate = "numeric" + ), + prototype = list( + anchor = c(-180, 180, -90, 90), + affine = diag(rep(1, 2L)), + order = c("rotate", "shear", "scale", "translate"), + rotate = 0, + shear = c(0, 0), + scale = c(1, 1), + translate = c(0, 0) + ) +) + +# ** processParam #### + +#' @title Parameter Classes for Data Processing Operations +#' @name processParam-class +#' @aliases processParam +#' @description +#' Utility class that defines a data processing procedure and any params used +#' in performing it. Packages defining processing methods will create their own +#' child classes. These parameter objects are intended to be passed alongside +#' the data to process to [processData()]. +#' @slot param list. Named parameters to use with the intended processing +#' operation. These can be accessed and updated using the `$` operator. +#' @exportClass processParam +setClass("processParam", contains = "VIRTUAL", slots = list(param = "list")) + + + +# ** svkey #### + +#' @name svkey-class +#' @title Spatial Value Key +#' @description +#' A metaprogramming object that references a set of information to get +#' from a `giotto` object when used as `svkey@get(gobject)`. +#' Referenced data will be retrieved as a `data.table` via [spatValues()] +#' @keywords internal +setClass("svkey", + slots = list( + feats = "character", + spat_unit = "nullOrChar", + feat_type = "nullOrChar", + expression_values = "nullOrChar", + spat_loc_name = "nullOrChar", + spat_enr_name = "nullOrChar", + poly_info = "nullOrChar", + dim_reduction_to_use = "nullOrChar", + dim_reduction_name = "nullOrChar", + verbose = "nullOrLogical", + get = "function" + ) +) \ No newline at end of file diff --git a/R/classes-virtuals.R b/R/classes-virtuals.R new file mode 100644 index 00000000..1a2c5809 --- /dev/null +++ b/R/classes-virtuals.R @@ -0,0 +1,297 @@ + +# OLDCLASS #### +setOldClass("giottoInstructions") + +# MISC #### +## * Define class unions #### + +setClassUnion("nullOrChar", c("NULL", "character")) +setClassUnion("nullOrList", c("NULL", "list")) +setClassUnion("nullOrInstructions", c("nullOrList", "giottoInstructions")) +setClassUnion("nullOrDatatable", c("NULL", "data.table")) +setClassUnion("nullOrLogical", c("NULL", "logical")) +# see zzz.R for allMatrix + +#' @title gIndex +#' @description +#' class for handling indices similar to `index` class from \pkg{Matrix} +#' simple class union (setClassUnion) of "numeric", "logical" and "character". +#' @keywords internal +#' @noRd +setClassUnion("gIndex", c("numeric", "logical", "character")) + +# ** giottoSubobject Class #### +#' @keywords internal +#' @noRd +setClass( + "giottoSubobject", + contains = "VIRTUAL" +) + +# ** gdtData Class #### +#' @description +#' umbrella class for referring to Giotto's normal data.table-based slots for +#' extraction purposes +#' @keywords internal +#' @noRd +setClass( + "gdtData", + contains = "VIRTUAL" +) + + +# ** nameData Class #### +#' @keywords internal +#' @noRd +setClass("nameData", + contains = "VIRTUAL", + slots = list(name = "character"), + prototype = prototype(name = NA_character_) +) + +# ** exprData Class #### +#' Basic class for classes with expression information +#' @keywords internal +#' @noRd +setClass("exprData", + contains = "VIRTUAL", + slots = list(exprMat = "ANY"), + prototype = prototype(exprMat = NULL) +) + + + +# ** coordData Class #### +#' Basic class for classes with coordinate information +#' +#' @description +#' coordDataDT is the specific flavor that deals with objects where the +#' coordinate information is stored within data.table objects and should work +#' similarly to data.table when interacting with some basic generic operators +#' for data retreival and setting. +#' +#' @keywords internal +#' @noRd +setClass("coordDataDT", + contains = c("VIRTUAL", "gdtData"), + slots = list(coordinates = "data.table"), + prototype = prototype(coordinates = data.table::data.table()) +) + + + + + +# setClass('coordDataMT', +# slots = list(coordinates = 'matrix'), +# prototype = prototype(coordinates = matrix())) + + +# ** metaData Class #### +#' Basic class for classes with metadata information +#' +#' @description +#' Classes that inherit from this class will contain a metadata slot that +#' stores information in a data.table and should work similarly to data.table +#' when interacting with some basic generic operators for data retrieval and +#' setting +#' @keywords internal +#' @noRd +setClass("metaData", + contains = c("VIRTUAL", "gdtData"), + slots = list( + metaDT = "data.table", + col_desc = "character" + ), + prototype = methods::prototype( + metaDT = data.table::data.table(), + col_desc = NA_character_ + ) +) + + + + + +# ** enrData #### +#' enrData +#' @keywords internal +#' @noRd +setClass("enrData", + contains = c("VIRTUAL", "gdtData"), + slots = list( + method = "character", + enrichDT = "nullOrDatatable" + ), + prototype = methods::prototype( + method = NA_character_, + enrichDT = NULL + ) +) + + + + + +# ** nnData #### +#' @keywords internal +#' @noRd +setClass("nnData", + contains = "VIRTUAL", + slots = list( + nn_type = "character", + igraph = "ANY" + ), + prototype = methods::prototype( + nn_type = NA_character_, + igraph = NULL + ) +) + + +# ** spatNetData #### +#' @keywords internal +#' @noRd +setClass("spatNetData", + contains = "VIRTUAL", + slots = list( + method = "character", + parameters = "ANY", + outputObj = "ANY", + networkDT = "nullOrDatatable", + networkDT_before_filter = "nullOrDatatable", + cellShapeObj = "ANY" + ), + prototype = methods::prototype( + method = NA_character_, + parameters = NULL, + outputObj = NULL, + networkDT = NULL, + networkDT_before_filter = NULL, + cellShapeObj = NULL + ) +) + + + + +# ** spatGridData #### +#' @keywords internal +#' @noRd +setClass("spatGridData", + contains = "VIRTUAL", + slots = list( + method = "character", + parameters = "ANY", + gridDT = "nullOrDatatable" + ), + prototype = prototype( + method = NA_character_, + parameters = NULL, + gridDT = NULL + ) +) + + + + + +# ** provData Class #### +#' Basic class for classes with provenance information. +#' +#' @description +#' This kind of information is necessary when generating data that is +#' aggregated from multiple original sources of raw information. This could +#' refer to situations such as when producing cellxfeature expression matrices +#' from subcellular transcript information and polygons that are provided as +#' multiple z layers. Provenance is Giotto's method of mapping this aggregated +#' information back to the original z layers that were used in its generation. +#' +#' @keywords internal +#' @noRd +setClass("provData", + contains = "VIRTUAL", + slots = list(provenance = "ANY"), + prototype = prototype(provenance = NULL) +) + + + +# ** spatData Class #### +#' Basic class for classes with spatial information +#' +#' @description +#' Classes that inherit from this class will contain a spat_unit slot that +#' describes which spatial unit the data belongs to. This is most relevant +#' to aggregated information. Subcellular information such as poly data +#' in \code{spatial_info} slot essentially define their own spatial units. +#' Within slots that deal with classes that contain spatData, +#' there is a nesting structure that first nests by spatial unit. +#' @keywords internal +#' @noRd +setClass("spatData", + contains = c("provData", "VIRTUAL"), + slots = list(spat_unit = "character"), # not allowed to be NULL + prototype = prototype(spat_unit = NA_character_) +) + + + +# ** featData Class #### +#' @title Basic class for classes with feature information +#' +#' @description +#' Features in Giotto are a blanket term for any features that are detected, +#' covering modalities such as, but not limited to rna, protein, ATAC, and +#' even QC probes. Classes that inherit from this class will contain a +#' feat_type slot that describes which feature type the data is. Within slots +#' that deal with classes that contain featData, there is a nesting structure +#' that usually first nests by spatial unit and then by feature type. +#' @keywords internal +#' @noRd +setClass("featData", + contains = "VIRTUAL", + slots = list(feat_type = "character"), # not allowed to be NULL + prototype = prototype(feat_type = NA_character_) +) + +# ** spatFeatData #### +#' @description Superclass for classes that contain both spatial and feature +#' data +#' @keywords internal +#' @noRd +setClass("spatFeatData", + contains = c("spatData", "featData", "VIRTUAL") +) + +# ** miscData Class #### +#' @title Basic class for additional miscellaneous information +#' +#' @description +#' Classes (such as dimObj) that can hold information from multiple types of +#' methods use the misc slot to hold additional information specific to each +#' method. Information may be stored within as S3 structures. +#' @returns slot for miscellaneous information +#' @examples +#' g <- GiottoData::loadSubObjectMini("dimObj") +#' +#' slot(g, "misc") +setClass("miscData", + contains = "VIRTUAL", + slots = list(misc = "ANY"), + prototype = prototype(misc = NULL) +) + + +# ** terraVectData Class #### +#' @title Basic class for terra SpatVector-based objects +#' @description +#' Classes that inherit from this class will contain a spatVector slot meant to +#' hold and work with terra SpatVector objects +#' @returns object with spatVector slot +terraVectData <- setClass( + "terraVectData", + contains = "VIRTUAL", + slots = list(spatVector = "ANY"), + prototype = prototype(spatVector = NULL) +) diff --git a/R/classes.R b/R/classes.R index 3967ee49..9cf4d7f7 100644 --- a/R/classes.R +++ b/R/classes.R @@ -1,378 +1,15 @@ #' @include package_imports.R +#' @include classes-virtuals.R +#' @include classes-polygons.R +#' @include classes-points.R +#' @include classes-overlaps.R +#' @include classes-images.R +#' @include classes-utils.R NULL -# OLDCLASS #### -setOldClass("giottoInstructions") -# MISC #### -## * Define class unions #### -setClassUnion("nullOrChar", c("NULL", "character")) -setClassUnion("nullOrList", c("NULL", "list")) -setClassUnion("nullOrInstructions", c("nullOrList", "giottoInstructions")) -setClassUnion("nullOrDatatable", c("NULL", "data.table")) -setClassUnion("nullOrLogical", c("NULL", "logical")) -# see zzz.R for allMatrix -#' @title gIndex -#' @description -#' class for handling indices similar to `index` class from \pkg{Matrix} -#' simple class union (setClassUnion) of "numeric", "logical" and "character". -#' @keywords internal -#' @noRd -setClassUnion("gIndex", c("numeric", "logical", "character")) - -# VIRTUAL CLASSES #### - - -# ** giottoSubobject Class #### -#' @keywords internal -#' @noRd -setClass( - "giottoSubobject", - contains = "VIRTUAL" -) - -# ** gdtData Class #### -#' @description -#' umbrella class for referring to Giotto's normal data.table-based slots for -#' extraction purposes -#' @keywords internal -#' @noRd -setClass( - "gdtData", - contains = "VIRTUAL" -) - - -# ** nameData Class #### -#' @keywords internal -#' @noRd -setClass("nameData", - contains = "VIRTUAL", - slots = list(name = "character"), - prototype = prototype(name = NA_character_) -) - -# ** exprData Class #### -#' Basic class for classes with expression information -#' @keywords internal -#' @noRd -setClass("exprData", - contains = "VIRTUAL", - slots = list(exprMat = "ANY"), - prototype = prototype(exprMat = NULL) -) - - - -# ** coordData Class #### -#' Basic class for classes with coordinate information -#' -#' @description -#' coordDataDT is the specific flavor that deals with objects where the -#' coordinate information is stored within data.table objects and should work -#' similarly to data.table when interacting with some basic generic operators -#' for data retreival and setting. -#' -#' @keywords internal -#' @noRd -setClass("coordDataDT", - contains = c("VIRTUAL", "gdtData"), - slots = list(coordinates = "data.table"), - prototype = prototype(coordinates = data.table::data.table()) -) - - - - - -# setClass('coordDataMT', -# slots = list(coordinates = 'matrix'), -# prototype = prototype(coordinates = matrix())) - - -# ** metaData Class #### -#' Basic class for classes with metadata information -#' -#' @description -#' Classes that inherit from this class will contain a metadata slot that -#' stores information in a data.table and should work similarly to data.table -#' when interacting with some basic generic operators for data retrieval and -#' setting -#' @keywords internal -#' @noRd -setClass("metaData", - contains = c("VIRTUAL", "gdtData"), - slots = list( - metaDT = "data.table", - col_desc = "character" - ), - prototype = methods::prototype( - metaDT = data.table::data.table(), - col_desc = NA_character_ - ) -) - - - - - -# ** enrData #### -#' enrData -#' @keywords internal -#' @noRd -setClass("enrData", - contains = c("VIRTUAL", "gdtData"), - slots = list( - method = "character", - enrichDT = "nullOrDatatable" - ), - prototype = methods::prototype( - method = NA_character_, - enrichDT = NULL - ) -) - - - - - -# ** nnData #### -#' @keywords internal -#' @noRd -setClass("nnData", - contains = "VIRTUAL", - slots = list( - nn_type = "character", - igraph = "ANY" - ), - prototype = methods::prototype( - nn_type = NA_character_, - igraph = NULL - ) -) - - -# ** spatNetData #### -#' @keywords internal -#' @noRd -setClass("spatNetData", - contains = "VIRTUAL", - slots = list( - method = "character", - parameters = "ANY", - outputObj = "ANY", - networkDT = "nullOrDatatable", - networkDT_before_filter = "nullOrDatatable", - cellShapeObj = "ANY" - ), - prototype = methods::prototype( - method = NA_character_, - parameters = NULL, - outputObj = NULL, - networkDT = NULL, - networkDT_before_filter = NULL, - cellShapeObj = NULL - ) -) - - - - -# ** spatGridData #### -#' @keywords internal -#' @noRd -setClass("spatGridData", - contains = "VIRTUAL", - slots = list( - method = "character", - parameters = "ANY", - gridDT = "nullOrDatatable" - ), - prototype = prototype( - method = NA_character_, - parameters = NULL, - gridDT = NULL - ) -) - - - - - -# ** provData Class #### -#' Basic class for classes with provenance information. -#' -#' @description -#' This kind of information is necessary when generating data that is -#' aggregated from multiple original sources of raw information. This could -#' refer to situations such as when producing cellxfeature expression matrices -#' from subcellular transcript information and polygons that are provided as -#' multiple z layers. Provenance is Giotto's method of mapping this aggregated -#' information back to the original z layers that were used in its generation. -#' -#' @keywords internal -#' @noRd -setClass("provData", - contains = "VIRTUAL", - slots = list(provenance = "ANY"), - prototype = prototype(provenance = NULL) -) - - - -# ** spatData Class #### -#' Basic class for classes with spatial information -#' -#' @description -#' Classes that inherit from this class will contain a spat_unit slot that -#' describes which spatial unit the data belongs to. This is most relevant -#' to aggregated information. Subcellular information such as poly data -#' in \code{spatial_info} slot essentially define their own spatial units. -#' Within slots that deal with classes that contain spatData, -#' there is a nesting structure that first nests by spatial unit. -#' @keywords internal -#' @noRd -setClass("spatData", - contains = c("provData", "VIRTUAL"), - slots = list(spat_unit = "character"), # not allowed to be NULL - prototype = prototype(spat_unit = NA_character_) -) - - - -# ** featData Class #### -#' @title Basic class for classes with feature information -#' -#' @description -#' Features in Giotto are a blanket term for any features that are detected, -#' covering modalities such as, but not limited to rna, protein, ATAC, and -#' even QC probes. Classes that inherit from this class will contain a -#' feat_type slot that describes which feature type the data is. Within slots -#' that deal with classes that contain featData, there is a nesting structure -#' that usually first nests by spatial unit and then by feature type. -#' @keywords internal -#' @noRd -setClass("featData", - contains = "VIRTUAL", - slots = list(feat_type = "character"), # not allowed to be NULL - prototype = prototype(feat_type = NA_character_) -) - - - -# ** miscData Class #### -#' @title Basic class for additional miscellaneous information -#' -#' @description -#' Classes (such as dimObj) that can hold information from multiple types of -#' methods use the misc slot to hold additional information specific to each -#' method. Information may be stored within as S3 structures. -#' @returns slot for miscellaneous information -#' @examples -#' g <- GiottoData::loadSubObjectMini("dimObj") -#' -#' slot(g, "misc") -setClass("miscData", - contains = "VIRTUAL", - slots = list(misc = "ANY"), - prototype = prototype(misc = NULL) -) - - -# ** terraVectData Class #### -#' @title Basic class for terra SpatVector-based objects -#' @description -#' Classes that inherit from this class will contain a spatVector slot meant to -#' hold and work with terra SpatVector objects -#' @returns object with spatVector slot -terraVectData <- setClass( - "terraVectData", - contains = "VIRTUAL", - slots = list(spatVector = "ANY"), - prototype = prototype(spatVector = NULL) -) - - - -# UTILITY #### - -# ** affine2d #### -setClass( - Class = "affine2d", - slots = list( - anchor = "ANY", - affine = "matrix", - order = "character", - rotate = "numeric", - shear = "numeric", - scale = "numeric", - translate = "numeric" - ), - prototype = list( - anchor = c(-180, 180, -90, 90), - affine = diag(rep(1, 2L)), - order = c("rotate", "shear", "scale", "translate"), - rotate = 0, - shear = c(0, 0), - scale = c(1, 1), - translate = c(0, 0) - ) -) - -# ** processParam #### - -#' @title Parameter Classes for Data Processing Operations -#' @name processParam-class -#' @aliases processParam -#' @description -#' Utility class that defines a data processing procedure and any params used -#' in performing it. Packages defining processing methods will create their own -#' child classes. These parameter objects are intended to be passed alongside -#' the data to process to [processData()]. -#' @slot param list. Named parameters to use with the intended processing -#' operation. These can be accessed and updated using the `$` operator. -#' @exportClass processParam -setClass("processParam", contains = "VIRTUAL", slots = list(param = "list")) - - - -# ** svkey #### - -#' @name svkey-class -#' @title Spatial Value Key -#' @description -#' A metaprogramming object that references a set of information to get -#' from a `giotto` object when used as `svkey@get(gobject)`. -#' Referenced data will be retrieved as a `data.table` via [spatValues()] -#' @keywords internal -setClass("svkey", - slots = list( - feats = "character", - spat_unit = "nullOrChar", - feat_type = "nullOrChar", - expression_values = "nullOrChar", - spat_loc_name = "nullOrChar", - spat_enr_name = "nullOrChar", - poly_info = "nullOrChar", - dim_reduction_to_use = "nullOrChar", - dim_reduction_name = "nullOrChar", - verbose = "nullOrLogical", - get = "function" - ) -) - -# SUBCLASSES #### - -# ** spatFeatData #### -#' @description Superclass for classes that contain both spatial and feature -#' data -#' @keywords internal -#' @noRd -setClass("spatFeatData", - contains = c("spatData", "featData", "VIRTUAL") -) From f3856e15e5b6db2a31d57174a3dcff686f1d5f20 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:05:46 -0500 Subject: [PATCH 06/24] cleanup some imports add head/tail method imports for terra --- R/package_imports.R | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/R/package_imports.R b/R/package_imports.R index 6b8f3e7b..200655f9 100644 --- a/R/package_imports.R +++ b/R/package_imports.R @@ -14,11 +14,7 @@ #' @importMethodsFrom Matrix t dim %*% as.matrix coerce #' @importMethodsFrom Matrix colSums rowSums colMeans rowMeans diag summary #' @importMethodsFrom terra ext ext<- hull -#' @importMethodsFrom terra plot -#' @importMethodsFrom terra wrap -#' @importMethodsFrom terra zoom -#' @importMethodsFrom terra crop -#' @importMethodsFrom terra vect buffer +#' @importMethodsFrom terra plot wrap zoom crop vect buffer head tail #' @importMethodsFrom terra relate #' @importMethodsFrom terra union erase intersect symdif snap #' @importMethodsFrom terra as.data.frame as.polygons as.points From e8b0f2951114de7c923b177934d76fb195b649ae Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:06:50 -0500 Subject: [PATCH 07/24] enh: head/tail/data.table methods for binpoints --- R/classes-binpoints.R | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index ff717fd6..6f37a393 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -4,7 +4,7 @@ setClass("giottoBinPoints", contains = c("featData", "giottoSubobject"), slots = list( spatial = "ANY", # spatvector etc. - counts = "data.frame", + counts = "data.table", bid = "character", # bin ID (static) pmap = "integer", # point IDs by mapping onto bid fid = "character", @@ -24,10 +24,7 @@ setMethod("show", signature("giottoBinPoints"), function(object) { compact = as.character(object@compact) ) print_list(plist) - preview <- head(object@counts)[, c("i", "x")] - preview$i <- object@fid[preview$i] - names(preview) <- c("feat_ID", "count") - print(preview) + print(as.data.table(head(object))) cat("\n") }) @@ -108,6 +105,28 @@ setMethod("ext", signature("giottoBinPoints"), function(x, ...) { ext(x@spatial, ...) }) +setMethod("head", signature("giottoBinPoints"), function(x, n = 6L, ...) { + n <- min(nrow(x), n) + x[seq_len(n)] +}) + +setMethod("tail", signature("giottoBinPoints"), function(x, n = 6L, ...) { + nr <- nrow(x) + begin <- nr - n + 1L + begin <- max(1, begin) + x[begin:nr] +}) + +#' @rdname as.data.table +#' @method as.data.table giottoBinPoints +#' @export +as.data.table.giottoBinPoints <- function(x, ...) { + d <- x@counts[, c("i", "x")] + d$i <- x@fid[d$i] + names(d) <- c("feat_ID", "count") + d +} + # * constructor #### @@ -185,12 +204,13 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { bloat_ratio > 10 # arbitrary setting } +# get spatial points from GBP (only existing ones) .gbp_get_spatial <- function(x, counts = FALSE) { if (counts) return(.gbp_get_spatial_sum_counts(x)) if (x@compact) { return(x@spatial) } - # these are both mappings of @bid, so they can be directly compared + # these are both mappings of IDs in @bid, so they can be directly compared ids_select <- which(x@pmap %in% x@counts$j) x@spatial[ids_select] } @@ -204,7 +224,9 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { spatial } -# `i` is row number(s) of spatial +# For the ith spatial point(s), get the contained feature counts as a 2 column +# `data.table` of "feat_ID" and "count" +#' @param i is row number(s) of spatial .gbp_spatial_select_counts <- function(x, i) { i <- as.integer(i) if (max(i) > length(x@pmap) || min(i) < 1) { From 7b436174454e11cdf7a1ee763542d862adf73ac2 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:07:35 -0500 Subject: [PATCH 08/24] chore: collate --- DESCRIPTION | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index ca319efa..414f2f51 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -80,6 +80,12 @@ Suggests: Remotes: drieslab/GiottoUtils Config/testthat/edition: 3 Collate: + 'classes-virtuals.R' + 'classes-utils.R' + 'classes-images.R' + 'classes-overlaps.R' + 'classes-points.R' + 'classes-polygons.R' 'package_imports.R' 'classes.R' 'generics.R' @@ -87,6 +93,7 @@ Collate: 'aggregate.R' 'auxilliary.R' 'buffer.R' + 'classes-binpoints.R' 'combine_metadata.R' 'slot_accessors.R' 'create.R' From c5005865ef285392306887d8ef5273adea788d4c Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:02:43 -0500 Subject: [PATCH 09/24] Update NAMESPACE --- NAMESPACE | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 52e58540..e0d174ff 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,6 +11,7 @@ S3method(.DollarNames,terraVectData) S3method(as.data.frame,overlapIntensityDT) S3method(as.data.frame,overlapPointDT) S3method(as.data.table,SpatVector) +S3method(as.data.table,giottoBinPoints) S3method(as.data.table,giottoPoints) S3method(as.data.table,giottoPolygon) S3method(print,ghistory) @@ -429,6 +430,7 @@ importMethodsFrom(terra,density) importMethodsFrom(terra,erase) importMethodsFrom(terra,ext) importMethodsFrom(terra,flip) +importMethodsFrom(terra,head) importMethodsFrom(terra,hist) importMethodsFrom(terra,hull) importMethodsFrom(terra,intersect) @@ -441,6 +443,7 @@ importMethodsFrom(terra,snap) importMethodsFrom(terra,spin) importMethodsFrom(terra,symdif) importMethodsFrom(terra,t) +importMethodsFrom(terra,tail) importMethodsFrom(terra,union) importMethodsFrom(terra,vect) importMethodsFrom(terra,wrap) From 6215257cdd14305e4406913a401b3c6a97f5420a Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:03:06 -0500 Subject: [PATCH 10/24] Update classes-overlaps.R --- R/classes-overlaps.R | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/R/classes-overlaps.R b/R/classes-overlaps.R index 6abea393..11ee1d15 100644 --- a/R/classes-overlaps.R +++ b/R/classes-overlaps.R @@ -100,14 +100,19 @@ setClass("overlapIntensityDT", odt } + +#' @description +#' Internal constructor for point overlap objects backed by `data.table`. +#' Contains all information needed to construct a matrix or [exprObj]. +#' Internally represented as a 2+ column `data.table` of `integers` mapped to +#' poly_ID and feat_ID lookup vectors for efficiency. #' @param x from data (SpatVector) - need just the poly_ID info #' @param y to data (SpatVector) - need meta info extracted from cols by overlap info + nrow #' @param overlap_data relationships (data.frame). Expected to be numeric row #' indices between x and y #' @param keep additional col(s) in `y` to keep -#' @examples -#' d <- data.frame(a = sort(rep(1:2, 2)), b = 1:4) -#' +#' @param feat_ids character. Set of unique features that were involved in the +#' overlap. This is needed for matrix row generation #' @noRd .create_overlap_point_dt <- function(x, y, overlap_data, keep = NULL, feat_ids) { @@ -126,33 +131,25 @@ setClass("overlapIntensityDT", overlap_data <- overlap_data[!is.na(poly)] # extract needed info from y - keep <- c("feat_ID", keep) + keep <- unique(c("feat_ID", "feat_ID_uniq", keep)) ytab <- terra::as.data.frame(y[overlap_data$feat_idx, keep]) # initialize overlap object and needed ids - sids <- x$poly_ID - fids <- unique(ytab$feat_ID) odt <- new("overlapPointDT", - spat_ids = sids, + spat_ids = x$poly_ID, feat_ids = feat_ids, - nfeats = as.integer(nrow(y)) + nfeats = nrow(y) ) # Ensure data is stored as integer or integer-based mapping - ## - if poly/feat_idx contents are NOT integer coercible, establish a map # + ## - if poly col contents are NOT integer coercible, establish a map # if (!overlap_data[, checkmate::test_integerish(head(poly, 100))]) { - overlap_data[, poly := match(poly, sids)] - } - if (!overlap_data[, checkmate::test_integerish(head(feat_idx, 100))]) { - overlap_data[, feat_idx := match(feat_idx, fids)] + overlap_data[, poly := match(poly, odt@spat_ids)] } ## -- if still not integer, coerce to integer --------------------------- # if (!is.integer(overlap_data$poly[1])) { overlap_data[, poly := as.integer(poly)] } - if (!is.integer(overlap_data$feat_idx[1])) { - overlap_data[, feat_idx := as.integer(feat_idx)] - } # append y attribute info overlap_data <- cbind(overlap_data, ytab) From 7c4dbed024f3b86cc3238605deeaf6291a1eda1b Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:38:25 -0500 Subject: [PATCH 11/24] Update classes-overlaps.R --- R/classes-overlaps.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/classes-overlaps.R b/R/classes-overlaps.R index 11ee1d15..f09c532d 100644 --- a/R/classes-overlaps.R +++ b/R/classes-overlaps.R @@ -138,7 +138,7 @@ setClass("overlapIntensityDT", odt <- new("overlapPointDT", spat_ids = x$poly_ID, feat_ids = feat_ids, - nfeats = nrow(y) + nfeats = as.integer(nrow(y)) ) # Ensure data is stored as integer or integer-based mapping From fce5dd4dfc38e1550f68c3c027e3df96d3b69de5 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:38:40 -0500 Subject: [PATCH 12/24] new: overlap for gbp --- R/classes-binpoints.R | 138 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 13 deletions(-) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index 6f37a393..07cd3353 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -62,6 +62,13 @@ setMethod("[", signature(x = "giottoBinPoints", i = "character", j = "missing", x[i, ..., compact = compact] }) +setMethod("objName", signature("giottoBinPoints"), function(x) x@feat_type) +setMethod("objName<-", signature("giottoBinPoints", "character"), + function(x, value) { + x@feat_type <- value + x + }) + setMethod("plot", signature("giottoBinPoints", "missing"), function(x, point_size = 0.2, feats = NULL, dens = FALSE, dens_transform = NULL, raster = TRUE, raster_size = 600, ...) { checkmate::assert_function(dens_transform, null.ok = TRUE) @@ -87,18 +94,42 @@ setMethod("plot", signature("giottoBinPoints", "missing"), function(x, }) setMethod("calculateOverlap", signature("giottoPolygon", "giottoBinPoints"), - function(x, y, name_overlap = NULL, poly_subset_ids = NULL) { + function(x, y, + name_overlap = NULL, + poly_subset_ids = NULL, + return_gpolygon = TRUE, + verbose = NULL, + ...) { checkmate::assert_character(poly_subset_ids, null.ok = TRUE) if (!is.null(poly_subset_ids)) { x <- x[x$poly_ID %in% poly_subset_ids] } - res <- terra::relate(x[], y@spatial, - relation = "intersects", pairs = TRUE) |> - data.table::as.data.table() - names(res) <- c("poly_ID", "b") - res <- res[, .gbp_spatial_select_counts(y, b), by = "poly_ID"] - res[, "poly_ID" := spatIDs(x)[poly_ID]] - res + overlap_data <- terra::extract(x[], y@spatial) + # res <- terra::relate(x[], y@spatial, + # relation = "intersects", pairs = TRUE) |> + # data.table::as.data.table() + # names(res) <- c("poly_ID", "b") + # res <- res[, .gbp_spatial_select_counts(y, b), by = "poly_ID"] + + res <- .gbp_create_overlap_point_dt(x, y, overlap_data) + + if (isTRUE(return_gpolygon)) { + # update schema metadata in overlap object + if (is.null(name_overlap)) name_overlap <- objName(y) + prov(res) <- spatUnit(x) + spatUnit(res) <- spatUnit(x) + featType(res) <- name_overlap + + # ensure centroids calculated + if (is.null(centroids(x))) { + x <- centroids(x, append_gpolygon = TRUE) + } + + x@overlaps[[name_overlap]] <- res + return(x) + } else { + return(res) + } }) setMethod("ext", signature("giottoBinPoints"), function(x, ...) { @@ -121,16 +152,19 @@ setMethod("tail", signature("giottoBinPoints"), function(x, n = 6L, ...) { #' @method as.data.table giottoBinPoints #' @export as.data.table.giottoBinPoints <- function(x, ...) { - d <- x@counts[, c("i", "x")] + cn <- colnames(x@counts) + get_cols <- cn[!cn == "j"] + d <- x@counts[, get_cols, with = FALSE] d$i <- x@fid[d$i] - names(d) <- c("feat_ID", "count") + names(d)[1:2] <- c("feat_ID", "count") d } # * constructor #### -createGiottoBinPoints <- function(expr_values, spatial_locs) { +createGiottoBinPoints <- function(expr_values, spatial_locs, + feat_type = "rna") { ids_p <- spatial_locs$cell_ID spatial_locs[] <- spatial_locs[][, c("sdimx", "sdimy")] # drop point IDs col sl_points <- as.points(spatial_locs) @@ -156,6 +190,7 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { bid = ids_m, pmap = match(ids_p, ids_m), fid = rownames(M), + feat_type = feat_type, compact = TRUE ) .gbp_compact(x, ids_p = ids_p, exist_j = exist_j) @@ -214,7 +249,7 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { ids_select <- which(x@pmap %in% x@counts$j) x@spatial[ids_select] } - +# get spatial + count col with sum of all features per point .gbp_get_spatial_sum_counts <- function(x) { counts <- x@counts[, .("count" = sum(x)), keyby = "j"] idx <- match(counts$j, x@pmap) @@ -226,7 +261,7 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { # For the ith spatial point(s), get the contained feature counts as a 2 column # `data.table` of "feat_ID" and "count" -#' @param i is row number(s) of spatial +# param i is row number(s) of spatial .gbp_spatial_select_counts <- function(x, i) { i <- as.integer(i) if (max(i) > length(x@pmap) || min(i) < 1) { @@ -242,3 +277,80 @@ createGiottoBinPoints <- function(expr_values, spatial_locs) { counts } +#' @description +#' Internal constructor for point overlap objects backed by `data.table`. +#' Contains all information needed to construct a matrix or [exprObj]. +#' Internally represented as a 2+ column `data.table` of `integers` mapped to +#' poly_ID and feat_ID lookup vectors for efficiency. +#' @param x from data (SpatVector) - need just the poly_ID info +#' @param y `giottoBinPoints` object +#' @param overlap_data relationships (data.frame). Expected to be numeric row +#' indices between x and y +#' @param keep additional col(s) in `y` to keep +#' @examples +#' # load test data +#' sl <- GiottoData::loadSubObjectMini("spatLocsObj") +#' m <- GiottoData::loadSubObjectMini("exprObj") +#' gpoly <- GiottoData::loadSubObjectMini("giottoPolygon") +#' +#' # create a giottoBinPoints object +#' gbp <- createGiottoBinPoints(m, sl) +#' # find overlapped points +#' o <- terra::extract(gpoly[], gbp@spatial) +#' odt <- .gbp_create_overlap_point_dt(gpoly, gbp, o) +#' +#' overlapToMatrix(odt, feat_count_column = "count") +#' @noRd +.gbp_create_overlap_point_dt <- function(x, y, + overlap_data, keep = NULL) { + poly <- feat_idx <- feat <- feat_id_index <- NULL # NSE vars + # cleanup input overlap_data + checkmate::assert_data_frame(overlap_data) + data.table::setDT(overlap_data) + cnames <- colnames(overlap_data) + data.table::setnames(overlap_data, + old = c(cnames[[2]], cnames[[1]]), + new = c("poly", "feat_idx") # feat_idx is the idx of gbp@spatial + ) + + # initialize overlap object and needed ids + odt <- new("overlapPointDT", + spat_ids = x$poly_ID, + feat_ids = as.character(y@fid), + nfeats = nrow(y) + ) + + # make relationships table sparse by removing non-overlapped features + # these results are indexed by all features, so no need to filter + # non-overlapped polys + overlap_data <- overlap_data[!is.na(poly)] + # change poly to map against spat_ids + overlap_data[, poly := match(poly, odt@spat_ids)] + # append a feature index col to counts info in gbp + y@counts$feat_ID_uniq <- seq_len(nrow(y)) + # add col matched to gbp counts j col in overlap info + overlap_data[, count_j := y@pmap[feat_idx]] + # left join counts info into overlap_data. + overlap_data <- y@counts[overlap_data, on = .(j = count_j)] + overlap_data[, feat_idx := NULL] # no longer needed. + overlap_data[, j := NULL] # j is from y@spatial mapping. No longer needed. + # current expected cols: + # i (feat name map), x (count), feat_ID_uniq, poly + data.table::setnames(overlap_data, + old = c("i", "x", "feat_ID_uniq"), + new = c("feat_id_index", "count", "feat") + ) + + # extract needed info from y + keep <- unique(c("feat_id_index", "count", "feat", "poly", keep)) + overlap_data <- overlap_data[, keep, with = FALSE] + + # set indices + data.table::setkeyv(overlap_data, "feat") + data.table::setindexv(overlap_data, "poly") + data.table::setcolorder(overlap_data, c("poly", "feat", "feat_id_index")) + # add to object + odt@data <- overlap_data + + odt +} From 26501943989d8dbd6982f1f77a70dccb095dc378 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:41:50 -0500 Subject: [PATCH 13/24] Update classes-binpoints.R --- R/classes-binpoints.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index 07cd3353..d12977e0 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -63,11 +63,11 @@ setMethod("[", signature(x = "giottoBinPoints", i = "character", j = "missing", }) setMethod("objName", signature("giottoBinPoints"), function(x) x@feat_type) -setMethod("objName<-", signature("giottoBinPoints", "character"), +setMethod("objName<-", signature("giottoBinPoints", "ANY"), function(x, value) { - x@feat_type <- value - x - }) + x@feat_type <- as.character(value) + x +}) setMethod("plot", signature("giottoBinPoints", "missing"), function(x, point_size = 0.2, feats = NULL, dens = FALSE, dens_transform = NULL, raster = TRUE, raster_size = 600, ...) { From b37a5934d6aa045ddf427b49c35b88f45eee2cb7 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:42:02 -0500 Subject: [PATCH 14/24] chore: docs --- man/as.data.table.Rd | 11 +++++++---- man/as.matrix.Rd | 2 +- man/as.points.Rd | 2 +- man/as.polygons.Rd | 2 +- man/featureNetwork-class.Rd | 2 +- man/giottoAffineImage-class.Rd | 2 +- man/giottoImage-class.Rd | 2 +- man/giottoLargeImage-class.Rd | 2 +- man/giottoPoints-class.Rd | 2 +- man/giottoPolygon-class.Rd | 2 +- man/miscData-class.Rd | 2 +- man/overlapPointDT-class.Rd | 2 +- man/processParam-class.Rd | 2 +- man/r_spatial_conversions.Rd | 2 +- man/svkey-class.Rd | 2 +- man/terraVectData-class.Rd | 2 +- man/updateGiottoPointsObject.Rd | 2 +- man/updateGiottoPolygonObject.Rd | 2 +- 18 files changed, 24 insertions(+), 21 deletions(-) diff --git a/man/as.data.table.Rd b/man/as.data.table.Rd index 3b9e0169..7da82723 100644 --- a/man/as.data.table.Rd +++ b/man/as.data.table.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-coerce.R -\name{as.data.table} +% Please edit documentation in R/classes-binpoints.R, R/methods-coerce.R +\name{as.data.table.giottoBinPoints} +\alias{as.data.table.giottoBinPoints} \alias{as.data.table} \alias{as.data.table.SpatVector} \alias{as.data.table.giottoPolygon} @@ -9,6 +10,8 @@ \alias{as.data.frame.overlapIntensityDT} \title{Coerce to data.table} \usage{ +\method{as.data.table}{giottoBinPoints}(x, ...) + \method{as.data.table}{SpatVector}( x, keep.rownames = FALSE, @@ -29,6 +32,8 @@ \arguments{ \item{x}{The object to coerce} +\item{\dots}{additional arguments to pass} + \item{keep.rownames}{This argument is ignored} \item{geom}{character or NULL. If not NULL, either "XY", "WKT", or "HEX", to @@ -42,8 +47,6 @@ is 'XY'} Fallback geomtype used when it is not possible for \{terra\} to determine the type of geometry an object is. (commonly seen when nrow of the object = 0)} - -\item{\dots}{additional arguments to pass} } \value{ data.table diff --git a/man/as.matrix.Rd b/man/as.matrix.Rd index ef1f22b6..a4a76908 100644 --- a/man/as.matrix.Rd +++ b/man/as.matrix.Rd @@ -43,7 +43,7 @@ m <- as.matrix(sl) } \seealso{ Other As coercion functions: -\code{\link{as.data.table}()}, +\code{\link{as.data.table.giottoBinPoints}()}, \code{\link{as.points}()}, \code{\link{as.polygons}()}, \code{\link{r_spatial_conversions}} diff --git a/man/as.points.Rd b/man/as.points.Rd index 8f305f0f..65849f63 100644 --- a/man/as.points.Rd +++ b/man/as.points.Rd @@ -35,7 +35,7 @@ as.points(slot(g, "spatVector")) \code{\link[terra:as.points]{terra::as.points()}} Other As coercion functions: -\code{\link{as.data.table}()}, +\code{\link{as.data.table.giottoBinPoints}()}, \code{\link{as.matrix}()}, \code{\link{as.polygons}()}, \code{\link{r_spatial_conversions}} diff --git a/man/as.polygons.Rd b/man/as.polygons.Rd index a68922ed..78560a69 100644 --- a/man/as.polygons.Rd +++ b/man/as.polygons.Rd @@ -40,7 +40,7 @@ as.polygons(slot(g, "spatVector")) \code{\link[terra:as.polygons]{terra::as.polygons()}} Other As coercion functions: -\code{\link{as.data.table}()}, +\code{\link{as.data.table.giottoBinPoints}()}, \code{\link{as.matrix}()}, \code{\link{as.points}()}, \code{\link{r_spatial_conversions}} diff --git a/man/featureNetwork-class.Rd b/man/featureNetwork-class.Rd index 0b77fc98..7d3fab76 100644 --- a/man/featureNetwork-class.Rd +++ b/man/featureNetwork-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-points.R \docType{class} \name{featureNetwork-class} \alias{featureNetwork-class} diff --git a/man/giottoAffineImage-class.Rd b/man/giottoAffineImage-class.Rd index 7955b7ff..4774a0a9 100644 --- a/man/giottoAffineImage-class.Rd +++ b/man/giottoAffineImage-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-images.R \docType{class} \name{giottoAffineImage-class} \alias{giottoAffineImage-class} diff --git a/man/giottoImage-class.Rd b/man/giottoImage-class.Rd index a999abd3..b9413177 100644 --- a/man/giottoImage-class.Rd +++ b/man/giottoImage-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-images.R \docType{class} \name{giottoImage-class} \alias{giottoImage-class} diff --git a/man/giottoLargeImage-class.Rd b/man/giottoLargeImage-class.Rd index 83c7014a..e197a2ce 100644 --- a/man/giottoLargeImage-class.Rd +++ b/man/giottoLargeImage-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-images.R \docType{class} \name{giottoLargeImage-class} \alias{giottoLargeImage-class} diff --git a/man/giottoPoints-class.Rd b/man/giottoPoints-class.Rd index 780d8052..9ff77896 100644 --- a/man/giottoPoints-class.Rd +++ b/man/giottoPoints-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-points.R \docType{class} \name{giottoPoints-class} \alias{giottoPoints-class} diff --git a/man/giottoPolygon-class.Rd b/man/giottoPolygon-class.Rd index 6fd300e3..7586e33f 100644 --- a/man/giottoPolygon-class.Rd +++ b/man/giottoPolygon-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-polygons.R \docType{class} \name{giottoPolygon-class} \alias{giottoPolygon-class} diff --git a/man/miscData-class.Rd b/man/miscData-class.Rd index 4f88be62..1dd91398 100644 --- a/man/miscData-class.Rd +++ b/man/miscData-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-virtuals.R \docType{class} \name{miscData-class} \alias{miscData-class} diff --git a/man/overlapPointDT-class.Rd b/man/overlapPointDT-class.Rd index 57e8ceee..cbc9cb54 100644 --- a/man/overlapPointDT-class.Rd +++ b/man/overlapPointDT-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R, R/methods-extract.R +% Please edit documentation in R/classes-overlaps.R, R/methods-extract.R \docType{class} \name{overlapPointDT-class} \alias{overlapPointDT-class} diff --git a/man/processParam-class.Rd b/man/processParam-class.Rd index 9944dcfe..48903662 100644 --- a/man/processParam-class.Rd +++ b/man/processParam-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-utils.R \docType{class} \name{processParam-class} \alias{processParam-class} diff --git a/man/r_spatial_conversions.Rd b/man/r_spatial_conversions.Rd index ee6034fe..a6aacb07 100644 --- a/man/r_spatial_conversions.Rd +++ b/man/r_spatial_conversions.Rd @@ -101,7 +101,7 @@ as.sf(g) } \seealso{ Other As coercion functions: -\code{\link{as.data.table}()}, +\code{\link{as.data.table.giottoBinPoints}()}, \code{\link{as.matrix}()}, \code{\link{as.points}()}, \code{\link{as.polygons}()} diff --git a/man/svkey-class.Rd b/man/svkey-class.Rd index 07111833..df83ad2c 100644 --- a/man/svkey-class.Rd +++ b/man/svkey-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-utils.R \docType{class} \name{svkey-class} \alias{svkey-class} diff --git a/man/terraVectData-class.Rd b/man/terraVectData-class.Rd index 36a2ba88..098b7648 100644 --- a/man/terraVectData-class.Rd +++ b/man/terraVectData-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-virtuals.R \docType{class} \name{terraVectData-class} \alias{terraVectData-class} diff --git a/man/updateGiottoPointsObject.Rd b/man/updateGiottoPointsObject.Rd index 9b50a719..c84a6751 100644 --- a/man/updateGiottoPointsObject.Rd +++ b/man/updateGiottoPointsObject.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-points.R \name{updateGiottoPointsObject} \alias{updateGiottoPointsObject} \title{Update giotto points object} diff --git a/man/updateGiottoPolygonObject.Rd b/man/updateGiottoPolygonObject.Rd index 7ab7515c..487040f9 100644 --- a/man/updateGiottoPolygonObject.Rd +++ b/man/updateGiottoPolygonObject.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classes.R +% Please edit documentation in R/classes-polygons.R \name{updateGiottoPolygonObject} \alias{updateGiottoPolygonObject} \title{Update giotto polygon object} From b972c7cb61444ebdb30cdf17df68ec803429ec8a Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 9 Jan 2026 00:01:55 -0500 Subject: [PATCH 15/24] fix version needed for object updates --- R/classes.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/classes.R b/R/classes.R index 9cf4d7f7..fc2e3488 100644 --- a/R/classes.R +++ b/R/classes.R @@ -152,7 +152,7 @@ updateGiottoObject <- function(gobject) { info_list <- lapply(info_list, function(info) { try_val <- try(validObject(info), silent = TRUE) if (inherits(try_val, "try-error") || - .gversion(gobject) <= "0.4.7") { + .gversion(gobject) < "0.5.0") { info <- updateGiottoPolygonObject(info) } return(info) From 6e6e1c203867703cd0338c13a441e9ffd649fb4e Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:36:05 -0500 Subject: [PATCH 16/24] chore: make code clearer on save function --- R/save_load.R | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/R/save_load.R b/R/save_load.R index 05e217e1..17ed6623 100644 --- a/R/save_load.R +++ b/R/save_load.R @@ -56,16 +56,18 @@ saveGiotto <- function( gobject@feat_info <- NULL } - overwriting <- FALSE + # change name of `overwrite` var to avoid confusion with underlying + # function call params + do_overwrite <- FALSE if (dir.exists(final_dir)) { if (!overwrite) { stop(wrap_txt( - "Folder already exist and overwrite = FALSE abort saving" + "Folder already exists and overwrite = FALSE abort saving" )) } else { wrap_msg("Folder already exist and overwrite = TRUE, overwrite folder") - overwriting <- TRUE + do_overwrite <- TRUE use_dir <- file.path(dir, ".giotto_scratch") dir.create(use_dir, recursive = TRUE, showWarnings = FALSE) } @@ -248,7 +250,7 @@ saveGiotto <- function( ) # effect overwrite - if (overwrite && overwriting) { + if (do_overwrite) { unlink(x = final_dir, recursive = TRUE) file.rename(from = use_dir, to = final_dir) } From 3d7297bffd1573bcd9287f0241b729640208bcb4 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:23:03 -0500 Subject: [PATCH 17/24] feat: crop for binpoints also additional docs --- R/classes-binpoints.R | 120 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index d12977e0..e9c5ab0a 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -1,5 +1,32 @@ # * definitions #### + +#' @title Binned point class +#' @name giottoBinPoints-class +#' @exportClass giottoBinPoints +#' @description +#' S4 class allowing point detection-like access patterns for binned spatial +#' values. Implemented more efficiently by only representing the spatial points +#' once and mapping the sparse values information against the points. +#' @slot spatial ANY (currently `SpatVector` only). Row-indexed spatial points +#' @slot counts `data.table` with integer cols `i` and `j` mapping to `@fid` +#' and `@bid` respectively. `x` is a numeric col standing for count or value of +#' a feature for this bin point. Row indexing of this object is based on this +#' slot. +#' @slot bid `character`. Bin IDs to map against +#' @slot pmap `integer`. For each spatial point, gives the index into `@bid`. +#' Length equals `length(spatial)`. Forms a bridge between spatial and counts: +#' both `pmap` and `counts$j` are indices into `@bid`. **Invariant**: every bin +#' ID in `counts$j` must appear in `pmap` (i.e., counts is always a subset of +#' spatial). Allows subsetting `counts` without modifying the more expensive +#' `spatial` representation until compaction. +#' @slot fid `character`. Feature IDs to map against +#' @slot compact `logical`. State of compaction. When `TRUE`, `@bid`, `@pmap`, +#' and `@spatial` contain only bins that appear in `@counts` (bidirectional +#' relationship). When `FALSE`, they may contain bins not present in `@counts` +#' (unidirectional: every bin in counts has spatial, but not every spatial has +#' counts). +#' @export setClass("giottoBinPoints", contains = c("featData", "giottoSubobject"), slots = list( @@ -7,7 +34,7 @@ setClass("giottoBinPoints", counts = "data.table", bid = "character", # bin ID (static) pmap = "integer", # point IDs by mapping onto bid - fid = "character", + fid = "character", # feature ID compact = "logical" # state of compaction ), prototype = list( @@ -29,14 +56,14 @@ setMethod("show", signature("giottoBinPoints"), function(object) { }) setMethod("dim", signature("giottoBinPoints"), function(x) { - c(nrow(x@counts), 1L) + c(nrow(x@counts), 2L) }) setMethod("nrow", signature("giottoBinPoints"), function(x) nrow(x@counts)) setMethod("ncol", signature("giottoBinPoints"), function(x) 1L) -setMethod("[", signature(x = "giottoBinPoints", i = "numeric", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { +setMethod("[", signature(x = "giottoBinPoints", i = "numeric", j = "missing", drop = "missing"), function(x, i, j, compact = "auto", ..., drop) { i <- as.integer(i) x@counts <- x@counts[i,] if (identical(compact, "auto")) compact <- .gbp_compact_auto(x) @@ -47,7 +74,13 @@ setMethod("[", signature(x = "giottoBinPoints", i = "numeric", j = "missing", dr .gbp_compact(x) }) -setMethod("[", signature(x = "giottoBinPoints", i = "logical", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { +#' @rdname subset_bracket +#' @param compact `character` or `logical` (default = "auto"). Whether to +#' compact object. See [giottoBinPoints-class]. `"auto"` will perform a +#' compaction when number of spatial points referenced in `@counts` is 1/10 of +#' that existing in `@spatial` +#' @export +setMethod("[", signature(x = "giottoBinPoints", i = "logical", j = "missing", drop = "missing"), function(x, i, j, compact = "auto", ..., drop) { if (length(i) != nrow(x)) { i <- rep(i, length.out = nrow(x)) } @@ -56,7 +89,7 @@ setMethod("[", signature(x = "giottoBinPoints", i = "logical", j = "missing", dr }) # feature subsetting -setMethod("[", signature(x = "giottoBinPoints", i = "character", j = "missing", drop = "missing"), function(x, i, j, ..., compact = "auto", drop) { +setMethod("[", signature(x = "giottoBinPoints", i = "character", j = "missing", drop = "missing"), function(x, i, j, compact = "auto", ..., drop) { keep_feat_idx <- which(x@fid %in% i) # i is feature whitelist i <- which(x@counts$i %in% keep_feat_idx) x[i, ..., compact = compact] @@ -136,11 +169,38 @@ setMethod("ext", signature("giottoBinPoints"), function(x, ...) { ext(x@spatial, ...) }) +#' @rdname crop +#' @param ext `logical`. When `TRUE`, the extent of y will be used instead of y +#' @param compact `character` or `logical` (default = "auto"). Whether to +#' compact object. See [giottoBinPoints-class]. `"auto"` will perform a +#' compaction when number of spatial points referenced in `@counts` is 1/10 of +#' that existing in `@spatial` +#' @export +setMethod("crop", signature("giottoBinPoints", "giottoPolygon"), function(x, y, + ext = FALSE, compact = "auto") { + if (isTRUE(ext)) y <- ext(y) + else y <- y[] + keep_spat_idx <- which(terra::relate(x@spatial, y, relation = "intersects")) + keep_j <- x@pmap[keep_spat_idx] + keep_count_idx <- which(x@counts$j %in% keep_j) + x[keep_count_idx, compact = compact] +}) + +#' @name headtail +#' @description +#' Get the head (first values) or tail (last values) of an object +#' @param x object +#' @param n `integerlike` how many to get +#' @param ... additional arguments to pass to other methods +#' @returns the same class as `x` +#' @export setMethod("head", signature("giottoBinPoints"), function(x, n = 6L, ...) { n <- min(nrow(x), n) x[seq_len(n)] }) +#' @rdname headtail +#' @export setMethod("tail", signature("giottoBinPoints"), function(x, n = 6L, ...) { nr <- nrow(x) begin <- nr - n + 1L @@ -163,6 +223,56 @@ as.data.table.giottoBinPoints <- function(x, ...) { # * constructor #### +#' @describeIn giottoBinPoints constructor function +#' @param expr_values `exprObj` Bin counts/values +#' @param spatial_locs `spatLocsObj` Spatial locations of bins +#' @param feat_type `character` (default = "rna"). Feature type of the data +#' @examples +#' ids <- sprintf("bin_%d", 1:50) +#' sl <- createSpatLocsObj(rnorm(100)) +#' sl$cell_ID <- ids +#' m <- matrix(floor(runif(500) * 3), +#' ncol = 50, +#' dimnames = list(letters[1:10], ids) +#' ) +#' ex <- createExprObj(m) +#' gbp <- createGiottoBinPoints(ex, sl) +#' +#' # basics -------------------------------------------------------- # +#' force(gbp) +#' nrow(gbp) +#' dim(gbp) +#' data.table::as.data.table(gbp) +#' head(gbp) +#' tail(gbp) +#' objName(gbp) +#' featType(gbp) +#' +#' # subsetting ---------------------------------------------------- # +#' gbp[50:100] +#' gbp["a"] # get only points for feature "a" +#' gbp[letters[1:4]] # get only points for features "a", "b", "c", "d" +#' +#' # plotting ------------------------------------------------------ # +#' plot(gbp, dens = TRUE) # will take a long time on large datasets +#' plot(gbp["a"]) # plot feature "a" only +#' plot(gbp[c("a", "d")]) # plot features "a" and "d" together +#' +#' # spatial ------------------------------------------------------- # +#' ext(gbp) # spatial extent +#' +#' d <- Giotto::hexVertices(1) +#' d$poly_ID <- "a" +#' hex <- createGiottoPolygon(d) +#' plot(gbp, col = "blue") +#' plot(hex, add = TRUE, border = "red") +#' plot(crop(gbp, hex), add = TRUE, col = "green") # cropping +#' +#' hex2 <- tessellate(ext(gbp), shape_size = 1) +#' res <- calculateOverlap(hex2, gbp) # overlapped feature calculation +#' m <- overlapToMatrix(res) # overlap info to expression matrix +#' force(m) +#' @export createGiottoBinPoints <- function(expr_values, spatial_locs, feat_type = "rna") { ids_p <- spatial_locs$cell_ID From 890e61a21dac90ff08b6f6f4e2d279467c8e9d9b Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:23:25 -0500 Subject: [PATCH 18/24] chore: docs --- NAMESPACE | 4 ++ man/crop.Rd | 38 ++++++++----- man/giottoBinPoints-class.Rd | 101 +++++++++++++++++++++++++++++++++++ man/subset_bracket.Rd | 12 ++++- 4 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 man/giottoBinPoints-class.Rd diff --git a/NAMESPACE b/NAMESPACE index e0d174ff..c70a6823 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -76,6 +76,7 @@ export(createCellMetaObj) export(createDimObj) export(createExprObj) export(createFeatMetaObj) +export(createGiottoBinPoints) export(createGiottoImage) export(createGiottoInstructions) export(createGiottoLargeImage) @@ -296,6 +297,7 @@ exportClasses(exprObj) exportClasses(featMetaObj) exportClasses(featureNetwork) exportClasses(giotto) +exportClasses(giottoBinPoints) exportClasses(giottoImage) exportClasses(giottoLargeImage) exportClasses(giottoPoints) @@ -354,6 +356,7 @@ exportMethods(ext) exportMethods(featIDs) exportMethods(featType) exportMethods(flip) +exportMethods(head) exportMethods(hist) exportMethods(hull) exportMethods(instructions) @@ -382,6 +385,7 @@ exportMethods(splitGeom) exportMethods(subset) exportMethods(symdif) exportMethods(t) +exportMethods(tail) exportMethods(union) exportMethods(vect) exportMethods(wrap) diff --git a/man/crop.Rd b/man/crop.Rd index 6dae9f8f..bc6a239e 100644 --- a/man/crop.Rd +++ b/man/crop.Rd @@ -1,32 +1,42 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-crop.R -\name{crop} +% Please edit documentation in R/classes-binpoints.R, R/methods-crop.R +\name{crop,giottoBinPoints,giottoPolygon-method} +\alias{crop,giottoBinPoints,giottoPolygon-method} \alias{crop} -\alias{crop,giottoLargeImage-method} -\alias{crop,giottoAffineImage-method} -\alias{crop,spatLocsObj-method} -\alias{crop,spatialNetworkObj-method} -\alias{crop,giottoPoints-method} -\alias{crop,giottoPolygon-method} +\alias{crop,giottoLargeImage,ANY-method} +\alias{crop,giottoAffineImage,ANY-method} +\alias{crop,spatLocsObj,ANY-method} +\alias{crop,spatialNetworkObj,ANY-method} +\alias{crop,giottoPoints,ANY-method} +\alias{crop,giottoPolygon,ANY-method} \title{Crop to a spatial subset} \usage{ -\S4method{crop}{giottoLargeImage}(x, y, ...) +\S4method{crop}{giottoBinPoints,giottoPolygon}(x, y, ext = FALSE, compact = "auto") -\S4method{crop}{giottoAffineImage}(x, y, ...) +\S4method{crop}{giottoLargeImage,ANY}(x, y, ...) -\S4method{crop}{spatLocsObj}(x, y, ...) +\S4method{crop}{giottoAffineImage,ANY}(x, y, ...) -\S4method{crop}{spatialNetworkObj}(x, y, ...) +\S4method{crop}{spatLocsObj,ANY}(x, y, ...) -\S4method{crop}{giottoPoints}(x, y, DT = TRUE, xmin = NULL, xmax = NULL, ymin = NULL, ymax = NULL, ...) +\S4method{crop}{spatialNetworkObj,ANY}(x, y, ...) -\S4method{crop}{giottoPolygon}(x, y, DT = TRUE, xmin = NULL, xmax = NULL, ymin = NULL, ymax = NULL, ...) +\S4method{crop}{giottoPoints,ANY}(x, y, DT = TRUE, xmin = NULL, xmax = NULL, ymin = NULL, ymax = NULL, ...) + +\S4method{crop}{giottoPolygon,ANY}(x, y, DT = TRUE, xmin = NULL, xmax = NULL, ymin = NULL, ymax = NULL, ...) } \arguments{ \item{x}{object} \item{y}{any object that has a SpatExtent or returns a SpatExtent} +\item{ext}{\code{logical}. When \code{TRUE}, the extent of y will be used instead of y} + +\item{compact}{\code{character} or \code{logical} (default = "auto"). Whether to +compact object. See \linkS4class{giottoBinPoints}. \code{"auto"} will perform a +compaction when number of spatial points referenced in \verb{@counts} is 1/10 of +that existing in \verb{@spatial}} + \item{\dots}{additional params to pass to terra::crop} \item{DT}{logical. Use alternative DT subsetting for crop operation} diff --git a/man/giottoBinPoints-class.Rd b/man/giottoBinPoints-class.Rd new file mode 100644 index 00000000..c60bd716 --- /dev/null +++ b/man/giottoBinPoints-class.Rd @@ -0,0 +1,101 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/classes-binpoints.R +\docType{class} +\name{giottoBinPoints-class} +\alias{giottoBinPoints-class} +\alias{createGiottoBinPoints} +\title{Binned point class} +\usage{ +createGiottoBinPoints(expr_values, spatial_locs, feat_type = "rna") +} +\arguments{ +\item{expr_values}{\code{exprObj} Bin counts/values} + +\item{spatial_locs}{\code{spatLocsObj} Spatial locations of bins} + +\item{feat_type}{\code{character} (default = "rna"). Feature type of the data} +} +\description{ +S4 class allowing point detection-like access patterns for binned spatial +values. Implemented more efficiently by only representing the spatial points +once and mapping the sparse values information against the points. +} +\section{Functions}{ +\itemize{ +\item \code{createGiottoBinPoints()}: constructor function + +}} +\section{Slots}{ + +\describe{ +\item{\code{spatial}}{ANY (currently \code{SpatVector} only). Row-indexed spatial points} + +\item{\code{counts}}{\code{data.table} with integer cols \code{i} and \code{j} mapping to \verb{@fid} +and \verb{@bid} respectively. \code{x} is a numeric col standing for count or value of +a feature for this bin point. Row indexing of this object is based on this +slot.} + +\item{\code{bid}}{\code{character}. Bin IDs to map against} + +\item{\code{pmap}}{\code{integer}. For each spatial point, gives the index into \verb{@bid}. +Length equals \code{length(spatial)}. Forms a bridge between spatial and counts: +both \code{pmap} and \code{counts$j} are indices into \verb{@bid}. \strong{Invariant}: every bin +ID in \code{counts$j} must appear in \code{pmap} (i.e., counts is always a subset of +spatial). Allows subsetting \code{counts} without modifying the more expensive +\code{spatial} representation until compaction.} + +\item{\code{fid}}{\code{character}. Feature IDs to map against} + +\item{\code{compact}}{\code{logical}. State of compaction. When \code{TRUE}, \verb{@bid}, \verb{@pmap}, +and \verb{@spatial} contain only bins that appear in \verb{@counts} (bidirectional +relationship). When \code{FALSE}, they may contain bins not present in \verb{@counts} +(unidirectional: every bin in counts has spatial, but not every spatial has +counts).} +}} + +\examples{ +ids <- sprintf("bin_\%d", 1:50) +sl <- createSpatLocsObj(rnorm(100)) +sl$cell_ID <- ids +m <- matrix(floor(runif(500) * 3), + ncol = 50, + dimnames = list(letters[1:10], ids) +) +ex <- createExprObj(m) +gbp <- createGiottoBinPoints(ex, sl) + +# basics -------------------------------------------------------- # +force(gbp) +nrow(gbp) +dim(gbp) +data.table::as.data.table(gbp) +head(gbp) +tail(gbp) +objName(gbp) +featType(gbp) + +# subsetting ---------------------------------------------------- # +gbp[50:100] +gbp["a"] # get only points for feature "a" +gbp[letters[1:4]] # get only points for features "a", "b", "c", "d" + +# plotting ------------------------------------------------------ # +plot(gbp, dens = TRUE) # will take a long time on large datasets +plot(gbp["a"]) # plot feature "a" only +plot(gbp[c("a", "d")]) # plot features "a" and "d" together + +# spatial ------------------------------------------------------- # +ext(gbp) # spatial extent + +d <- Giotto::hexVertices(1) +d$poly_ID <- "a" +hex <- createGiottoPolygon(d) +plot(gbp, col = "blue") +plot(hex, add = TRUE, border = "red") +plot(crop(gbp, hex), add = TRUE, col = "green") # cropping + +hex2 <- tessellate(ext(gbp), shape_size = 1) +res <- calculateOverlap(hex2, gbp) # overlapped feature calculation +m <- overlapToMatrix(res) # overlap info to expression matrix +force(m) +} diff --git a/man/subset_bracket.Rd b/man/subset_bracket.Rd index 848e0653..4cfdce21 100644 --- a/man/subset_bracket.Rd +++ b/man/subset_bracket.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-extract.R -\name{subset_bracket} +% Please edit documentation in R/classes-binpoints.R, R/methods-extract.R +\name{[,giottoBinPoints,logical,missing,missing-method} +\alias{[,giottoBinPoints,logical,missing,missing-method} \alias{subset_bracket} \alias{`[`,} \alias{`[[`} @@ -47,6 +48,8 @@ \alias{[,processParam,missing,missing,missing-method} \title{Subset part of an object with \code{[} or \code{[[}} \usage{ +\S4method{[}{giottoBinPoints,logical,missing,missing}(x, i, j, compact = "auto", ..., drop) + \S4method{[}{gdtData,gIndex,gIndex,missing}(x, i, j) \S4method{[}{gdtData,logical,missing,missing}(x, i, j) @@ -137,6 +140,11 @@ \item{i, j}{indices specifying elements to extract. Indices are numeric or character vectors, or empty} +\item{compact}{\code{character} or \code{logical} (default = "auto"). Whether to +compact object. See \linkS4class{giottoBinPoints}. \code{"auto"} will perform a +compaction when number of spatial points referenced in \verb{@counts} is 1/10 of +that existing in \verb{@spatial}} + \item{\dots}{additional arguments} } \value{ From 138da4b7688fbbcaed27f18239411158820fc8a1 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:56:10 -0500 Subject: [PATCH 19/24] chore: docs for head/tail --- R/classes-binpoints.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index e9c5ab0a..56b7f439 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -186,6 +186,7 @@ setMethod("crop", signature("giottoBinPoints", "giottoPolygon"), function(x, y, x[keep_count_idx, compact = compact] }) +#' @title Head and tail #' @name headtail #' @description #' Get the head (first values) or tail (last values) of an object From b381596e981075a9f21082ee0d0787dd8041774d Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:57:01 -0500 Subject: [PATCH 20/24] fix: .gversion for untracked gobjects --- R/classes.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/classes.R b/R/classes.R index fc2e3488..7d8413dc 100644 --- a/R/classes.R +++ b/R/classes.R @@ -30,6 +30,9 @@ NULL } .gversion <- function(gobject) { + if (is.null(attr(gobject, "versions"))) { # apply default version 0.0.0 + return(as.package_version("0.0.0")) # untracked + } gobject@versions$gclass } From d3aca63bde067f1eb7c201713da3f6e952581f06 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:43:48 -0500 Subject: [PATCH 21/24] refactor: gobject save/load - reworked using internal S4 generic - separation of concerns for subobject and backing files - save/load for giottoBinPoints subobjects resolved --- R/save_load.R | 503 ++++++++++++++++---------------------------------- 1 file changed, 164 insertions(+), 339 deletions(-) diff --git a/R/save_load.R b/R/save_load.R index 17ed6623..6fcb0ab1 100644 --- a/R/save_load.R +++ b/R/save_load.R @@ -1,4 +1,4 @@ -## I/O helpers #### + #' @title saveGiotto #' @name saveGiotto @@ -81,109 +81,27 @@ saveGiotto <- function( feat_info_names <- list_feature_info_names(gobject) if (!is.null(feat_info_names)) { - feat_dir <- paste0(use_dir, "/", "Features") - dir.create(feat_dir) for (feat in feat_info_names) { - if (verbose) wrap_msg("For feature: ", feat, "\n") - - # original spatvector - if (!is.null(gobject@feat_info[[feat]]@spatVector)) { - # write names of spatvector - spatvecnames <- names(gobject@feat_info[[feat]]@spatVector) - filename_names <- paste0( - feat_dir, "/", feat, "_feature_spatVector_names.txt" - ) - write.table( - x = spatvecnames, - file = filename_names, - col.names = FALSE, - row.names = FALSE - ) - - # write spatvector - filename <- paste0( - feat_dir, "/", feat, "_feature_spatVector.shp" - ) - terra::writeVector( - x = gobject@feat_info[[feat]]@spatVector, - filename = filename, overwrite = TRUE - ) - } - - # network - # ? data.table object + .save_external(gobject@feat_info[[feat]], + dir = use_dir, + verbose = verbose + ) } } - ## save spatVector objects related to spatial information if (verbose) wrap_msg("2. Start writing spatial information \n") spat_info_names <- list_spatial_info_names(gobject) if (!is.null(spat_info_names)) { - spatinfo_dir <- paste0(use_dir, "/", "SpatialInfo") - dir.create(spatinfo_dir) for (spatinfo in spat_info_names) { - vmsg(.v = verbose, "For spatial information: ", spatinfo) - - # original spatVectors - if (!is.null(gobject@spatial_info[[spatinfo]]@spatVector)) { - # write names of spatvector - spatvecnames <- names( - gobject@spatial_info[[spatinfo]]@spatVector - ) - filename_names <- paste0( - spatinfo_dir, "/", spatinfo, - "_spatInfo_spatVector_names.txt" - ) - write.table( - x = spatvecnames, file = filename_names, - col.names = FALSE, row.names = FALSE - ) - - # write spatvector - filename <- paste0( - spatinfo_dir, "/", spatinfo, - "_spatInfo_spatVector.shp" - ) - terra::writeVector( - gobject@spatial_info[[spatinfo]]@spatVector, - filename = filename, overwrite = TRUE - ) - } - - # spatVectorCentroids - if (!is.null( - gobject@spatial_info[[spatinfo]]@spatVectorCentroids - )) { - # write names of spatvector - spatvecnames <- names( - gobject@spatial_info[[spatinfo]]@spatVectorCentroids - ) - filename_names <- paste0( - spatinfo_dir, "/", spatinfo, - "_spatInfo_spatVectorCentroids_names.txt" - ) - write.table( - x = spatvecnames, file = filename_names, - col.names = FALSE, row.names = FALSE - ) - - # write spatvector - filename <- paste0( - spatinfo_dir, "/", spatinfo, - "_spatInfo_spatVectorCentroids.shp" - ) - terra::writeVector( - gobject@spatial_info[[spatinfo]]@spatVectorCentroids, - filename = filename, overwrite = TRUE - ) - } + .save_external(gobject@spatial_info[[spatinfo]], + dir = use_dir, + verbose = verbose + ) } } - - ## save images vmsg(.v = verbose, "3. Start writing image information") # only `giottoLargeImages` need to be saved separately @@ -202,10 +120,10 @@ saveGiotto <- function( # save extent info (needed for non-COG outputs) img@extent <- terra::ext(r)[] # update filepath + filename <- file.path(image_dir, paste0(image, "_spatRaster")) img@file_path <- filename # save raster - filename <- file.path(image_dir, paste0(image, "_spatRaster")) terra::writeRaster( x = r, filename = filename, @@ -381,6 +299,145 @@ loadGiotto <- function(path_to_folder, # internals #### +# * internal generics #### + +setGeneric(".save_external", function(x, ...) standardGeneric(".save_external")) +setGeneric(".load_external", function(x, ...) standardGeneric(".load_external")) + +## * save methods #### +setMethod(".save_external", signature("ANY"), function(x, ...) return()) +# dir - save directory +setMethod(".save_external", signature("giottoPolygon"), function(x, dir, + verbose = NULL, ...) { + dir <- file.path(dir, "SpatialInfo") + if (!dir.exists(dir)) dir.create(dir) + vmsg(.v = verbose, "For spatial information: ", objName(x)) + if (!is.null(x@spatVector)) { + name_fmt1 <- paste0(objName(x), "_spatInfo_%s") + .save_external(x@spatVector, + name_fmt = name_fmt1, + dir = dir, + ... + ) + } + if (!is.null(x@spatVectorCentroids)) { + name_fmt2 <- paste0(objName(x), "_spatInfo_%sCentroids") + .save_external(x@spatVectorCentroids, + name_fmt = name_fmt2, + dir = dir, + ... + ) + } +}) +setMethod(".save_external", signature("giottoPoints"), function(x, dir, + verbose = NULL, ...) { + dir <- file.path(dir, "Features") + if (!dir.exists(dir)) dir.create(dir) + vmsg(.v = verbose, "For feature: ", objName(x)) + if (is.null(x@spatVector)) return() + name_fmt <- paste0(objName(x), "_feature_%s") + .save_external(x@spatVector, name_fmt = name_fmt, dir = dir, ...) +}) +setMethod(".save_external", signature("giottoBinPoints"), function(x, dir, + verbose = NULL, ...) { + dir <- file.path(dir, "Features") + if (!dir.exists(dir)) dir.create(dir) + vmsg(.v = verbose, "For feature: ", objName(x)) + name_fmt <- paste0(objName(x), "_feature_%s") + .save_external(x@spatial, name_fmt = name_fmt, dir = dir, + write_colnames = FALSE, ... + ) +}) +# name - character. Used in naming for files +setMethod(".save_external", signature("SpatVector"), function(x, name_fmt, dir, + write_colnames = TRUE, ...) { + name <- sprintf(name_fmt, "spatVector") + fname_txt <- file.path(dir, paste0(name, "_names.txt")) + fname_shp <- file.path(dir, paste0(name, ".shp")) + if (write_colnames) { + sv_names <- names(x) + write.table(sv_names, + file = fname_txt, + col.names = FALSE, + row.names = FALSE + ) + } + terra::writeVector(x, + filename = fname_shp, + overwrite = TRUE + ) +}) + +## * load methods #### + +setMethod(".load_external", signature("ANY"), function(x, ...) return()) +setMethod(".load_external", signature("giottoPolygon"), function(x, dir, version, ...) { + dir <- file.path(dir, "SpatialInfo") + if (!dir.exists(dir)) return(x) + name_fmt1 <- paste0(objName(x), "_spatInfo_%s") + x@spatVector <- .load_external(x@spatVector, + name_fmt = name_fmt1, dir = dir, read_colnames = TRUE, oname = objName(x), ... + ) + name_fmt2 <- paste0(objName(x), "_spatInfo_%sCentroids") + x@spatVectorCentroids <- .load_external(x@spatVectorCentroids, + name_fmt = name_fmt2, dir = dir, read_colnames = TRUE, oname = objName(x), ... + ) + if (version >= "0.5.0" || is.null(x@overlaps)) return(x) + + # catch legacy overlap outputs + name_fmt3 <- paste0(objName(x), "_spatInfo_%sOverlaps") + overlap_names <- names(x@overlaps) + names(overlap_names) <- overlap_names # ensure lapply outputs are named + x@overlaps <- lapply(overlap_names, function(overlap_feat) { + .load_external(x@overlaps[[overlap_feat]], + name_fmt = name_fmt3, + dir = dir, + read_colnames = TRUE, + oname = objName(x), + prefix = overlap_feat, + ... + ) + }) + x +}) +setMethod(".load_external", signature("giottoPoints"), function(x, dir, ...) { + dir <- file.path(dir, "Features") + if (!dir.exists(dir)) return(x) + name_fmt <- paste0(objName(x), "_feature_%s") + x@spatVector <- .load_external(x@spatVector, + name_fmt = name_fmt, dir = dir, read_colnames = TRUE, oname = objName(x), ... + ) + x +}) +setMethod(".load_external", signature("giottoBinPoints"), function(x, dir, ...) { + dir <- file.path(dir, "Features") + if (!dir.exists(dir)) return(x) + name_fmt <- paste0(objName(x), "_feature_%s") + x@spatial <- .load_external(x@spatial, + name_fmt = name_fmt, dir = dir, read_colnames = FALSE, oname = objName(x), ... + ) + x +}) +setMethod(".load_external", signature("SpatVector"), function(x, name_fmt, dir, + read_colnames = TRUE, verbose = NULL, oname, prefix = NULL, ...) { + if (!is.null(prefix)) name_fmt <- paste(prefix, name_fmt, sep = "_") + name <- sprintf(name_fmt, "spatVector") + fname_txt <- file.path(dir, paste0(name, "_names.txt")) + fname_shp <- file.path(dir, paste0(name, ".shp")) + + if (!file.exists(fname_shp)) return(x) + vmsg(.v = verbose, .is_debug = TRUE, .initial = " ", + sprintf("[%s] %s", oname, basename(fname_shp)) + ) + sv <- terra::vect(fname_shp) + if (read_colnames) { + sv_names <- data.table::fread(input = fname_txt, header = FALSE)[["V1"]] + names(sv) <- sv_names + } + sv +}) + +# * helpers #### # load in the gobject S4 object. # the contained point-based information will need to be regenerated/reconnected @@ -430,57 +487,14 @@ loadGiotto <- function(path_to_folder, # load and append spatial feature information .load_giotto_feature_info <- function(gobject, path_to_folder, verbose = NULL) { vmsg(.v = verbose, "2. read Giotto feature information") - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - box_chars()$l, "subdir: /Features/", sep = "" - ) - - feats_dir <- file.path(path_to_folder, "Features") - manifest <- dir_manifest(feats_dir) - basenames <- names(manifest) + if (is.null(gobject@feat_info)) return(gobject) - # basenames of .shp files to load - shp_files <- basenames[grepl(".shp", basenames)] - - # return early if none, also catches when dir does not exist - if (length(shp_files) == 0) { - return(gobject) - } - - # parse the feature type(s) to load from the .shp basenames - feats <- gsub(shp_files, - pattern = "_feature_spatVector.shp", replacement = "" - ) - - # basenames of .txt files to load - # These have attribute info names (e.g. feat_ID, feat_ID_uniq) - # this is done since serialized SpatVectors may have clipped names. - txt_files <- paste0(feats, "_feature_spatVector_names.txt") - - # ordering of files follow feats. - # Apply name to make indexing simple and unique - names(shp_files) <- names(txt_files) <- feats - - # iterate through features discovered and load/regenerate each - # then append the information to the gobject - for (feat in feats) { - load_shp <- manifest[[shp_files[[feat]]]] - load_txt <- manifest[[txt_files[[feat]]]] - - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - sprintf("[%s] %s", feat, basename(load_shp)) + gobject@feat_info <- lapply(gobject@feat_info, function(data) { + .load_external(data, + dir = path_to_folder, + verbose = verbose ) - spatVector <- terra::vect(x = load_shp) - - # read in original column names and assign to SpatVector - spatVector_names <- data.table::fread( - input = load_txt, header = FALSE - )[["V1"]] - names(spatVector) <- spatVector_names - - gobject@feat_info[[feat]]@spatVector <- spatVector - } + }) return(gobject) } @@ -488,184 +502,16 @@ loadGiotto <- function(path_to_folder, # load and append to gobject the spatial polygon information .load_giotto_spatial_info <- function(gobject, path_to_folder, verbose = NULL) { vmsg(.v = verbose, "3. read Giotto spatial information") - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - box_chars()$l, "subdir: /SpatialInfo/", sep = "" - ) - - spat_dir <- file.path(path_to_folder, "SpatialInfo") - manifest <- dir_manifest(spat_dir) - basenames <- names(manifest) - - # basenames of .shp files to load - # there are other .shp files for centroids and overlaps in this dir - # so the search term is more specific - shp_files <- basenames[grepl("spatVector.shp", basenames)] - - # return early if none, also catches when dir does not exist - if (length(shp_files) == 0) { - return(gobject) - } - - ## 3.1. shapes - vmsg(.v = verbose, "3.1 read Giotto spatial shape information") - - # parse the spatial unit(s) to load from the .shp basenames - spats <- gsub(shp_files, - pattern = "_spatInfo_spatVector.shp", replacement = "" - ) - - # basenames of .txt files to load - # .shp files may clip these normally, so we load them separately - txt_files <- paste0(spats, "_spatInfo_spatVector_names.txt") - - # ordering of files follow spats. - # Apply name to make indexing simple and unique - names(shp_files) <- names(txt_files) <- spats - - # iterate through spat units discovered and load/regen each - # then append the info to the gobject - for (spat in spats) { - load_shp <- manifest[[shp_files[[spat]]]] - load_txt <- manifest[[txt_files[[spat]]]] - - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - sprintf("[%s] %s", spat, basename(load_shp)) + if (is.null(gobject@spatial_info)) return(gobject) + + gversion <- .gversion(gobject) + gobject@spatial_info <- lapply(gobject@spatial_info, function(data) { + .load_external(data, + dir = path_to_folder, + version = gversion, + verbose = verbose ) - spatVector <- terra::vect(x = load_shp) - - # read in original column names and assign to spatVector - spatVector_names <- data.table::fread( - input = load_txt, header = FALSE - )[["V1"]] - names(spatVector) <- spatVector_names - - gobject@spatial_info[[spat]]@spatVector <- spatVector - } - - - # load centroids of gpoly - gobject <- .load_giotto_spatial_info_centroids( - gobject = gobject, - manifest = manifest, - basenames = basenames, - spats = spats, - verbose = verbose - ) - - # load overlaps of gpoly - gobject <- .load_giotto_spatial_info_overlaps( - gobject = gobject, - manifest = manifest, - verbose = verbose - ) - - return(gobject) -} - -# load and append to gobject the polygons centroids information -.load_giotto_spatial_info_centroids <- function(gobject, manifest, basenames, spats, verbose = NULL) { - ## 3.2. centroids - vmsg(.v = verbose, "3.2 read Giotto spatial centroid information \n") - - # these files are optional, depending on if they have been calculated. - # They may not exist - - # build expected filenames as file search terms - shp_search <- paste0(spats, "_spatInfo_spatVectorCentroids.shp") - txt_search <- paste0(spats, "_spatInfo_spatVectorCentroids_names.txt") - # detect existing centroids files - shp_files <- basenames[basenames %in% shp_search] - - # return early if none exist - if (length(shp_files) == 0) { - return(gobject) - } - - # apply name on search terms for simple and unique indexing - names(shp_search) <- names(txt_search) <- spats - - # iterate through spat_units for data load - # skip the spat_unit if file not found - for (spat in spats) { - load_shp <- manifest[[shp_search[[spat]]]] - load_txt <- manifest[[txt_search[[spat]]]] - - if (is.null(load_shp)) next # skip to next spat_unit if none - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - sprintf("[%s] %s", spat, basename(load_shp)) - ) - missing_nametxt <- FALSE - if (is.null(load_txt)) { - warning(sprintf("[%s] missing centroid attribute names.txt", spat), - call. = FALSE) - missing_nametxt <- TRUE - } - - # read in centroids - spatVector <- terra::vect(load_shp) - # read in original column names and assign to spatVector - if (!missing_nametxt) { - spatVector_names <- data.table::fread( - input = load_txt, header = FALSE - )[["V1"]] - names(spatVector) <- spatVector_names - } - - gobject@spatial_info[[spat]]@spatVectorCentroids <- spatVector - } - return(gobject) -} - -# load and append to gobject the polygons overlaps information -.load_giotto_spatial_info_overlaps <- function(gobject, manifest, verbose = NULL) { - # objects from GiottoClass v0.5 and onwards do not need this for overlaps - if ("versions" %in% names(attributes(gobject))) { - if (.gversion(gobject) >= "0.5.0") return(gobject) - } - - ## 3.3. overlaps - vmsg(.v = verbose, "3.3 read Giotto spatial overlap information \n") - - si <- get_polygon_info_list(gobject) # none case taken care of in 3.1 - spats <- names(si) - - # These files are optional, depending on if they have been calculated. - # They may not exist - # They are named in "feattype_spatunit_postfix.extension" convention - - for (spat in spats) { - feats <- .gpoly_overlap_names(si[[spat]], type = "point") - if (is.null(feats)) next # goto next spat_unit if no overlaps - - for (feat in feats) { - # format: feattype_spatunit - comb <- paste(feat, spat, sep = "_") - - # format: feattype_spatunit_postfix.extension - shp_file <- paste0(comb, "_spatInfo_spatVectorOverlaps.shp") - txt_file <- paste0(comb, "_spatInfo_spatVectorOverlaps_names.txt") - load_shp <- manifest[[shp_file]] - load_txt <- manifest[[txt_file]] - - vmsg( - .v = verbose, .is_debug = TRUE, .initial = " ", - sprintf("[%s and %s] %s", spat, feat, basename(load_shp)) - ) - spatVector <- terra::vect(load_shp) - - # read in original column names - spatVector_names <- data.table::fread( - input = load_txt, header = FALSE - )[["V1"]] - names(spatVector) <- spatVector_names - - # append - gobject@spatial_info[[spat]]@overlaps[[feat]] <- spatVector - } - } + }) return(gobject) } @@ -718,24 +564,3 @@ loadGiotto <- function(path_to_folder, return(gobject) } - -.gpoly_overlap_names <- function(x, type = c("point", "intensity")) { - type <- match.arg(type, choices = c("point", "intensity")) - ovlps <- overlaps(x) - if (is.null(ovlps)) { - return(NULL) - } - - switch(type, - "point" = { - res <- names(ovlps) - res <- res[res != "intensity"] - if (length(res) == 0) res <- NULL - return(res) - }, - "intensity" = { - res <- names(ovlps$intensity) - } - ) - return(res) -} From 53320cb32f9e8f7b4a05a2d49b632d5c02ff7d44 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:45:50 -0500 Subject: [PATCH 22/24] feat: setGiotto and featIDs compat for binpoints featFeatureInfo for binpoints does work, but param `return_giottoPoints` must be `TRUE` --- R/classes-binpoints.R | 17 +++++++++++++++++ R/slot_accessors.R | 19 ++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/R/classes-binpoints.R b/R/classes-binpoints.R index 56b7f439..674e79ef 100644 --- a/R/classes-binpoints.R +++ b/R/classes-binpoints.R @@ -209,6 +209,23 @@ setMethod("tail", signature("giottoBinPoints"), function(x, n = 6L, ...) { x[begin:nr] }) +#' @rdname setGiotto +#' @export +setMethod("setGiotto", signature("giotto", "giottoBinPoints"), + function(gobject, x, ...) { + gobject <- setFeatureInfo(gobject = gobject, x = x, ...) + gobject +}) + +#' @rdname spatIDs-generic +#' @export +setMethod("featIDs", signature(x = "giottoBinPoints"), function(x, uniques = TRUE, ...) { + if (uniques) { + return(x@fid[unique(x@counts$i)]) + } + x@fid[x@counts$i] +}) + #' @rdname as.data.table #' @method as.data.table giottoBinPoints #' @export diff --git a/R/slot_accessors.R b/R/slot_accessors.R index 889589b7..07bc4c67 100644 --- a/R/slot_accessors.R +++ b/R/slot_accessors.R @@ -4995,7 +4995,7 @@ setFeatureInfo <- function(gobject, # NATIVE INPUT TYPES # 2. if input is giottoPoints or NULL, pass to internal - if (is.null(x) || inherits(x, "giottoPoints")) { + if (is.null(x) || inherits(x, c("giottoPoints", "giottoBinPoints"))) { # pass to internal gobject <- set_feature_info( gobject = gobject, @@ -5009,7 +5009,10 @@ setFeatureInfo <- function(gobject, } else if (inherits(x, "list")) { # check list items are native if (all( - vapply(x, inherits, "giottoPoints", FUN.VALUE = logical(1L)) + vapply(x, + inherits, c("giottoPoints", "giottoBinPoints"), + FUN.VALUE = logical(1L) + ) )) { # MULTIPLE INPUT # 3. iteratively set @@ -5031,7 +5034,8 @@ setFeatureInfo <- function(gobject, # catch stop(wrap_txt("Only giottoPoints or lists of giottoPoints accepted. - For raw or external data, please first use readFeatureInfo()")) + For raw or external data, please first use readFeatureInfo()") + ) } @@ -5071,7 +5075,10 @@ set_feature_info <- function(gobject, # 0. stop if not native formats if (inherits(gpoints, "list")) { if (!all( - vapply(gpoints, inherits, "giottoPoints", FUN.VALUE = logical(1L)) + vapply(gpoints, + inherits, c("giottoPoints", "giottoBinPoints"), + FUN.VALUE = logical(1L) + ) )) { stop(wrap_txt("If providing a list to internal setter, only lists of", "giottoPoints objects are permitted", @@ -5079,7 +5086,9 @@ set_feature_info <- function(gobject, )) } } - if (!inherits(gpoints, c("giottoPoints", "NULL", "list"))) { + if (!inherits(gpoints, + c("giottoPoints", "giottoBinPoints", "NULL", "list")) + ) { stop(wrap_txt(deparse(substitute(gpoints)), "is not giottoPoints (set), list of giottoPoints (set), or NULL (remove)")) } From 73161daed09de5d8b74f713968a4bc646beb36d3 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:46:10 -0500 Subject: [PATCH 23/24] chore: docs --- man/headtail.Rd | 24 ++++++++++++++++++++++++ man/setCellMetadata.Rd | 2 +- man/setDimReduction.Rd | 2 +- man/setExpression.Rd | 2 +- man/setFeatureInfo.Rd | 2 +- man/setFeatureMetadata.Rd | 2 +- man/setGiotto.Rd | 11 +++++++---- man/setGiottoImage.Rd | 2 +- man/setMultiomics.Rd | 2 +- man/setNearestNetwork.Rd | 2 +- man/setPolygonInfo.Rd | 2 +- man/setSpatialEnrichment.Rd | 2 +- man/setSpatialGrid.Rd | 2 +- man/setSpatialLocations.Rd | 2 +- man/setSpatialNetwork.Rd | 2 +- man/set_multiomics.Rd | 2 +- man/spatIDs-generic.Rd | 21 ++++++++++++--------- 17 files changed, 57 insertions(+), 27 deletions(-) create mode 100644 man/headtail.Rd diff --git a/man/headtail.Rd b/man/headtail.Rd new file mode 100644 index 00000000..292ed258 --- /dev/null +++ b/man/headtail.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/classes-binpoints.R +\name{headtail} +\alias{headtail} +\alias{tail,giottoBinPoints-method} +\title{Head and tail} +\usage{ +\S4method{head}{giottoBinPoints}(x, n = 6L, ...) + +\S4method{tail}{giottoBinPoints}(x, n = 6L, ...) +} +\arguments{ +\item{x}{object} + +\item{n}{\code{integerlike} how many to get} + +\item{...}{additional arguments to pass to other methods} +} +\value{ +the same class as \code{x} +} +\description{ +Get the head (first values) or tail (last values) of an object +} diff --git a/man/setCellMetadata.Rd b/man/setCellMetadata.Rd index e534933d..0e4a1f3b 100644 --- a/man/setCellMetadata.Rd +++ b/man/setCellMetadata.Rd @@ -56,7 +56,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setDimReduction.Rd b/man/setDimReduction.Rd index 37818c2a..5912555d 100644 --- a/man/setDimReduction.Rd +++ b/man/setDimReduction.Rd @@ -65,7 +65,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setExpression.Rd b/man/setExpression.Rd index cc7be0ea..f88fd92d 100644 --- a/man/setExpression.Rd +++ b/man/setExpression.Rd @@ -62,7 +62,7 @@ Other functions to set data in giotto object: \code{\link{setDimReduction}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setFeatureInfo.Rd b/man/setFeatureInfo.Rd index b19037ac..4d5029e7 100644 --- a/man/setFeatureInfo.Rd +++ b/man/setFeatureInfo.Rd @@ -49,7 +49,7 @@ Other functions to set data in giotto object: \code{\link{setDimReduction}()}, \code{\link{setExpression}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setFeatureMetadata.Rd b/man/setFeatureMetadata.Rd index a7f8bf91..f49657cd 100644 --- a/man/setFeatureMetadata.Rd +++ b/man/setFeatureMetadata.Rd @@ -56,7 +56,7 @@ Other functions to set data in giotto object: \code{\link{setDimReduction}()}, \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setGiotto.Rd b/man/setGiotto.Rd index 67caab60..55e4992c 100644 --- a/man/setGiotto.Rd +++ b/man/setGiotto.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-setGiotto.R -\name{setGiotto} +% Please edit documentation in R/classes-binpoints.R, R/methods-setGiotto.R +\name{setGiotto,giotto,giottoBinPoints-method} +\alias{setGiotto,giotto,giottoBinPoints-method} \alias{setGiotto} \alias{setGiotto,giotto,list-method} \alias{setGiotto,giotto,cellMetaObj-method} @@ -17,6 +18,8 @@ \alias{setGiotto,giotto,giottoImage-method} \title{Set giotto subobjects into giotto object} \usage{ +\S4method{setGiotto}{giotto,giottoBinPoints}(gobject, x, ...) + \S4method{setGiotto}{giotto,list}(gobject, x, verbose = TRUE, ...) \S4method{setGiotto}{giotto,cellMetaObj}(gobject, x, ...) @@ -48,9 +51,9 @@ \item{x}{giottoSubobject to set} -\item{verbose}{be verbose} - \item{\dots}{additional params to pass to specific Giotto setter functions} + +\item{verbose}{be verbose} } \value{ giottoSubobject diff --git a/man/setGiottoImage.Rd b/man/setGiottoImage.Rd index 913883bd..50482549 100644 --- a/man/setGiottoImage.Rd +++ b/man/setGiottoImage.Rd @@ -61,7 +61,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, \code{\link{setPolygonInfo}()}, diff --git a/man/setMultiomics.Rd b/man/setMultiomics.Rd index 78d9a352..d4d2bc5f 100644 --- a/man/setMultiomics.Rd +++ b/man/setMultiomics.Rd @@ -59,7 +59,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setNearestNetwork}()}, \code{\link{setPolygonInfo}()}, diff --git a/man/setNearestNetwork.Rd b/man/setNearestNetwork.Rd index 2ee260f4..6663d0b9 100644 --- a/man/setNearestNetwork.Rd +++ b/man/setNearestNetwork.Rd @@ -63,7 +63,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setPolygonInfo}()}, diff --git a/man/setPolygonInfo.Rd b/man/setPolygonInfo.Rd index 20aeb3cd..b62f6401 100644 --- a/man/setPolygonInfo.Rd +++ b/man/setPolygonInfo.Rd @@ -65,7 +65,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setSpatialEnrichment.Rd b/man/setSpatialEnrichment.Rd index 8b9ed631..9f4f9a39 100644 --- a/man/setSpatialEnrichment.Rd +++ b/man/setSpatialEnrichment.Rd @@ -59,7 +59,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setSpatialGrid.Rd b/man/setSpatialGrid.Rd index 6d91c9fc..f1bd96b5 100644 --- a/man/setSpatialGrid.Rd +++ b/man/setSpatialGrid.Rd @@ -57,7 +57,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setSpatialLocations.Rd b/man/setSpatialLocations.Rd index 44cf7c62..18e32ad8 100644 --- a/man/setSpatialLocations.Rd +++ b/man/setSpatialLocations.Rd @@ -62,7 +62,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/setSpatialNetwork.Rd b/man/setSpatialNetwork.Rd index f1ddfd74..24b35309 100644 --- a/man/setSpatialNetwork.Rd +++ b/man/setSpatialNetwork.Rd @@ -56,7 +56,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/set_multiomics.Rd b/man/set_multiomics.Rd index 3d3c04f4..c8a4c3e5 100644 --- a/man/set_multiomics.Rd +++ b/man/set_multiomics.Rd @@ -56,7 +56,7 @@ Other functions to set data in giotto object: \code{\link{setExpression}()}, \code{\link{setFeatureInfo}()}, \code{\link{setFeatureMetadata}()}, -\code{\link{setGiotto}()}, +\code{\link{setGiotto,giotto,giottoBinPoints-method}}, \code{\link{setGiottoImage}()}, \code{\link{setMultiomics}()}, \code{\link{setNearestNetwork}()}, diff --git a/man/spatIDs-generic.Rd b/man/spatIDs-generic.Rd index 8f82c92c..fa546335 100644 --- a/man/spatIDs-generic.Rd +++ b/man/spatIDs-generic.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/methods-IDs.R -\name{spatIDs-generic} +% Please edit documentation in R/classes-binpoints.R, R/methods-IDs.R +\name{featIDs,giottoBinPoints-method} +\alias{featIDs,giottoBinPoints-method} \alias{spatIDs-generic} \alias{spatIDs<-} \alias{spatIDs} @@ -22,6 +23,8 @@ \alias{featIDs,spatEnrObj-method} \title{Spatial and feature IDs} \usage{ +\S4method{featIDs}{giottoBinPoints}(x, uniques = TRUE, ...) + \S4method{spatIDs}{giotto}(x, spat_unit = NULL, subset, negate = FALSE, quote = TRUE, ...) \S4method{spatIDs}{exprObj}(x, ...) @@ -55,6 +58,13 @@ \arguments{ \item{x}{an object} +\item{uniques}{return unique ID values +only (currently gpoly and gpoints only)} + +\item{\dots}{additional params to pass when used with the \code{subset} param. +For \code{spatID()}, these pass to \code{\link[=spatValues]{spatValues()}}. For \code{featID()}, these +currently only pass to \code{fDataDT()}.} + \item{spat_unit}{(optional) specify which spatial unit} \item{subset}{logical expression to find a subset of features.} @@ -67,15 +77,8 @@ are selected} that may not be recommended since NSE output can be unexpected when not used interactively.} -\item{\dots}{additional params to pass when used with the \code{subset} param. -For \code{spatID()}, these pass to \code{\link[=spatValues]{spatValues()}}. For \code{featID()}, these -currently only pass to \code{fDataDT()}.} - \item{use_cache}{use cached IDs if available (gpoly and gpoints only)} -\item{uniques}{return unique ID values -only (currently gpoly and gpoints only)} - \item{old}{character. IDs to match against to replace} \item{value}{character. IDs to replace with} From bb28bd06fc0379fdebf7352956ae477aa4935c99 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:19:51 -0500 Subject: [PATCH 24/24] chore: news --- NEWS.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7e342708..d6024bb5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,13 +2,18 @@ ## changes - `calculateOverlap()` and `overlapToMatrix()` param harmonization +- refactor of `saveGiotto()` and `loadGiotto()` +- code reorganization for `classes.R` ## new -- `aggregateFeatures()` wrapper for running `calculateOverlap()` and `overlapToMatrix()` +- `aggregateFeatures()` giotto object wrapper function for running `calculateOverlap()` and `overlapToMatrix()` - `overlapPointDT()` and `overlapIntensityDT()` classes to store overlaps relationships efficiently and help with aggregation pipeline +- `giottoBinPoints` class for efficient binned spatial points ## bug fixes - `overlaps()` will now properly find image overlaps +- fix a naming bug when exporting images during save + # GiottoClass 0.4.12 (2025/12/12)