From c23ff68e4982d1cf5787271871afdf9d7463460a Mon Sep 17 00:00:00 2001 From: josschavezf Date: Sun, 5 Oct 2025 18:02:35 -0400 Subject: [PATCH 01/17] fix outdated argument for seurat object and fix typo --- R/interoperability.R | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/R/interoperability.R b/R/interoperability.R index 10f98869..1391fa3a 100644 --- a/R/interoperability.R +++ b/R/interoperability.R @@ -1572,13 +1572,13 @@ giottoToSeuratV5 <- function(gobject, gobject = gobject, spat_unit = spat_unit ) - assay_names <- names(gobject@expression$cell) + assay_names <- names(gobject@expression[[spat_unit]]) # Identify assays with spaces and replace with underscores new_assay_names <- gsub(" ", "_", assay_names) # Apply the new names to the gobject expression slot - names(gobject@expression$cell) <- new_assay_names + names(gobject@expression[[spat_unit]]) <- new_assay_names # verify if optional package is installed package_check(pkg_name = "Seurat", repository = "CRAN") @@ -1622,15 +1622,17 @@ giottoToSeuratV5 <- function(gobject, assay = assay_use ) if ("normalized" %in% slot_use) { - sobj <- Seurat::SetAssayData(sobj, - slot = "data", + sobj <- Seurat::SetAssayData( + sobj, + layer = "data", new.data = expr_use[["normalized"]][], assay = assay_use ) } if ("scaled" %in% slot_use) { - sobj <- Seurat::SetAssayData(sobj, - slot = "scale.data", + sobj <- Seurat::SetAssayData( + sobj, + layer = "scale.data", # does not accept 'dgeMatrix' new.data = as.matrix(expr_use[["scaled"]][]), assay = assay_use @@ -1662,8 +1664,9 @@ giottoToSeuratV5 <- function(gobject, sobj[[assay_use]] <- assay_obj if ("scaled" %in% slot_use) { data_scale <- as.matrix(expr_use[["scaled"]][]) - sobj <- Seurat::SetAssayData(sobj, - slot = "scale.data", + sobj <- Seurat::SetAssayData( + sobj, + layer = "scale.data", new.data = data_scale, assay = assay_use ) @@ -1671,7 +1674,8 @@ giottoToSeuratV5 <- function(gobject, } # add cell metadata - names(gobject@cell_metadata$cell) <- gsub(" ", "_", names(gobject@cell_metadata$cell)) + names(gobject@cell_metadata$cell) <- gsub( + " ", "_", names(gobject@cell_metadata$cell)) meta_cells <- data.table::setDF( getCellMetadata( gobject = gobject, @@ -1682,7 +1686,8 @@ giottoToSeuratV5 <- function(gobject, ) ) rownames(meta_cells) <- meta_cells$cell_ID - meta_cells <- meta_cells[, -which(colnames(meta_cells) == "cell_ID"), drop = FALSE] + meta_cells <- meta_cells[ + , -which(colnames(meta_cells) == "cell_ID"), drop = FALSE] if (ncol(meta_cells) > 0) { colnames(meta_cells) <- paste0( assay_use, "_", @@ -1695,7 +1700,8 @@ giottoToSeuratV5 <- function(gobject, } # add feature metadata - names(gobject@feat_metadata$cell) <- gsub(" ", "_", names(gobject@feat_metadata$cell)) + names(gobject@feat_metadata$cell) <- gsub( + " ", "_", names(gobject@feat_metadata$cell)) meta_genes <- data.table::setDF( getFeatureMetadata( gobject = gobject, @@ -1829,7 +1835,7 @@ giottoToSeuratV5 <- function(gobject, # spatial network avail_sn <- list_spatial_networks(gobject = gobject, spat_unit = spat_unit) - if (!is.null(avail_nn)) { + if (!is.null(avail_sn)) { if (nrow(avail_sn) > 0) { sn_all <- avail_sn[, name] for (i in sn_all) { From 181bc695eed7ec897921cb02dde61a5895f16dd8 Mon Sep 17 00:00:00 2001 From: Joselyn Chavez Date: Wed, 8 Oct 2025 15:36:13 -0400 Subject: [PATCH 02/17] upgrade R version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 04fb3ff3..6048f273 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,7 +26,7 @@ Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 Depends: - R (>= 4.4.0) + R (>= 4.5.0) Imports: checkmate, data.table (>= 1.12.2), From 5e1da1ef53b35daa4568863d4aafbe0e2b936c51 Mon Sep 17 00:00:00 2001 From: Joselyn Chavez Date: Wed, 8 Oct 2025 15:38:46 -0400 Subject: [PATCH 03/17] replace = -> assingment symbol --- R/combine_metadata.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/combine_metadata.R b/R/combine_metadata.R index 424d246d..bd40b029 100644 --- a/R/combine_metadata.R +++ b/R/combine_metadata.R @@ -695,10 +695,10 @@ calculateLabelProportions <- function(gobject, labels, ), call. = FALSE) } - spat_unit = set_default_spat_unit( + spat_unit <- set_default_spat_unit( gobject = gobject, spat_unit = spat_unit ) - feat_type = set_default_feat_type( + feat_type <- set_default_feat_type( gobject = gobject, spat_unit = spat_unit, feat_type = feat_type ) @@ -881,7 +881,7 @@ calculateLabelProportions <- function(gobject, labels, sn <- sn[, c("source", "target")] # drop weights info if any } else if (!"weight" %in% colnames(sn) || isFALSE(weights)) { warning(wrap_txt("No 'weight' information present in spatial network. - Using adjacency instead."), call. = FALSE) + Using adjacency instead."), call. = FALSE) sn[, "weight" := 1] # fallback if no weight info exists } # ensure unique From 200183b8f8a69626f7d37423b739a3dedbf74c02 Mon Sep 17 00:00:00 2001 From: Joselyn Chavez Date: Wed, 8 Oct 2025 15:39:15 -0400 Subject: [PATCH 04/17] fix indentation --- R/data_evaluation.R | 4 ++-- R/images.R | 2 +- R/spatial_query.R | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/data_evaluation.R b/R/data_evaluation.R index 15e5b473..2ab44bbf 100644 --- a/R/data_evaluation.R +++ b/R/data_evaluation.R @@ -152,7 +152,7 @@ evaluate_input <- function(type, x, ...) { .gstop( "expression input needs to be a path to matrix-like data or an", "object of class 'Matrix', 'data.table', 'data.frame', 'matrix'", - "'DelayedMatrix' or 'dbSparseMatrix'." + "'DelayedMatrix' or 'dbSparseMatrix'." ) } @@ -321,7 +321,7 @@ evaluate_input <- function(type, x, ...) { rnames <- rownames(x) if (!is.null(rnames)) { vmsg(.v = verbose, "[spatlocs] matrix input has rownames. - Using these as IDs.") + Using these as IDs.") dt[, id := rnames] } .evaluate_spatial_locations(dt, verbose = verbose, ...) diff --git a/R/images.R b/R/images.R index c7f45045..92987554 100644 --- a/R/images.R +++ b/R/images.R @@ -2850,7 +2850,7 @@ to_simple_tif <- function(input_file, unlink(outpath, force = TRUE) # if overwrite, delete original } else { stop("File already exists: ", outpath, - "\nSet overwrite = TRUE to replace.\n", + "\nSet overwrite = TRUE to replace.\n", call. = FALSE ) } diff --git a/R/spatial_query.R b/R/spatial_query.R index 4935eed7..fb16acb2 100644 --- a/R/spatial_query.R +++ b/R/spatial_query.R @@ -177,7 +177,7 @@ spatQuery <- function(gobject, if (!is.null(name)) checkmate::assert_character(name) checkmate::assert_list(filters, types = c("character", "giottoPolygon", "spatLocsObj", "numeric", - "integer", "SpatVector") + "integer", "SpatVector") ) checkmate::assert_character(use_centroids, null.ok = TRUE) checkmate::assert_numeric(buffer) @@ -190,29 +190,29 @@ spatQuery <- function(gobject, # more specific checks on inputs ----------------------------------- # if (length(filters) < 2L) { stop(wrap_txt("At least two elements in filters are needed."), - call. = FALSE) + call. = FALSE) } # `filters` input must be named. filter_names <- names(filters) if (any(vapply(filter_names, is_empty_char, FUN.VALUE = logical(1L)))) { stop(wrap_txt("All elements in filters list must be named"), - call. = FALSE) + call. = FALSE) } if (!is.null(use_centroids)) { if (!all(use_centroids %in% filter_names)) { stop("all entries in `use_centroids` must be names in `filters`\n", - call. = FALSE) + call. = FALSE) } } if (length(buffer) > 1L) { buffer_names <- names(buffer) if (is.null(buffer_names)) { stop("if multiple `buffer` values given, they must be named\n", - call. = FALSE) + call. = FALSE) } if (!all(buffer_names %in% filter_names)) { stop("all names for `buffer` values must be names in `filters`\n", - call. = FALSE) + call. = FALSE) } } @@ -334,7 +334,7 @@ spatQuery <- function(gobject, # not allowed. stop(wrap_txtf( "'%s' is not the last layer of query. - Assigned 'buffer' may not be 0", fname + Assigned 'buffer' may not be 0", fname ), call. = FALSE) } } @@ -355,7 +355,7 @@ spatQueryGiottoPolygons <- spatQuery .squery_get_sv.default <- function(x, ...) { stop(wrap_txt("[spatQuery] unrecognized filter input type:", class(x)), - call. = FALSE) + call. = FALSE) } .squery_get_sv.character <- function(x, gobject, centroids, spat_unit, ...) { @@ -429,7 +429,7 @@ spatQueryGiottoPolygons <- spatQuery if (is.null(sv)) { stop(sprintf("Requested filter '%s' not found in giotto object\n", x), - call. = FALSE) + call. = FALSE) } # filter by x if needed From 00ef2478463e787393ade2f4537ff2a42ebf987b Mon Sep 17 00:00:00 2001 From: Joselyn Chavez Date: Wed, 8 Oct 2025 15:39:26 -0400 Subject: [PATCH 05/17] run devtools::document --- man/names.Rd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/names.Rd b/man/names.Rd index fb92df32..804e44cf 100644 --- a/man/names.Rd +++ b/man/names.Rd @@ -3,18 +3,18 @@ \name{names} \alias{names} \alias{names,giottoLargeImage-method} -\alias{names<-,giottoLargeImage-method} +\alias{names<-,giottoLargeImage,ANY-method} \alias{names,processParam-method} -\alias{names<-,processParam-method} +\alias{names<-,processParam,ANY-method} \title{Names of objects} \usage{ \S4method{names}{giottoLargeImage}(x) -\S4method{names}{giottoLargeImage}(x) <- value +\S4method{names}{giottoLargeImage,ANY}(x) <- value \S4method{names}{processParam}(x) -\S4method{names}{processParam}(x) <- value +\S4method{names}{processParam,ANY}(x) <- value } \arguments{ \item{x}{object} From e131ff1b144075c520c629cf0bf31ed73b590a70 Mon Sep 17 00:00:00 2001 From: iqraAmin Date: Sat, 18 Oct 2025 14:37:01 -0400 Subject: [PATCH 06/17] Add functionality to read OME Map Annotations (K/Vs) (tif_metadata) --- R/images.R | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/R/images.R b/R/images.R index 92987554..2384840f 100644 --- a/R/images.R +++ b/R/images.R @@ -2884,11 +2884,11 @@ ometif_to_tif <- to_simple_tif #' "integer"). `output = "structure"` can help #' with figuring out which is most appropriate. #' @param output character. One of "data.frame" to return a data.frame of the -#' attributes information of the xml node, "xmL" for an \{xml2\} representation +#' attributes information of the xml node, "xml" for an \{xml2\} representation #' of the node, "list" for an R native list (note that many items in the -#' list may have overlapping names that make indexing difficult), or +#' list may have overlapping names that make indexing difficult), #' "structure" to invisibly return NULL, but print the structure of the XML -#' document or node. +#' document/node, or "kv" (extract key/value pairs from OME MapAnnotations). #' @returns list/data.frame/XML depending on `output` #' @examples #' if (FALSE) { @@ -2911,7 +2911,7 @@ tif_metadata <- function(path, node = NULL, page = NULL, type = c("attribute", "text", "double", "integer"), - output = c("data.frame", "xml", "list", "structure")) { + output = c("data.frame", "xml", "list", "structure", "kv")) { checkmate::assert_file_exists(path) package_check( pkg_name = c("tifffile", "imagecodecs", "xml2"), @@ -2923,7 +2923,7 @@ tif_metadata <- function(path, img <- TIF$TiffFile(path) on.exit(try(img$close(), silent = TRUE), add = TRUE) output <- match.arg(output, - choices = c("data.frame", "xml", "list", "structure") + choices = c("data.frame", "xml", "list", "structure", "kv") ) type <- match.arg(type, choices = c("attribute", "text", "double", "integer") @@ -2992,12 +2992,14 @@ ometif_metadata <- tif_metadata # qptiff: per-page description p1 <- as.integer(page)[1] x <- tryCatch(img$pages[[p1 - 1L]]$description, error = function(e) NULL) + if (is.null(x) || !nzchar(x)) { + x <- tryCatch(img$series[[1]]$pages[[p1 - 1L]]$description, error = function(e) NULL) + } } else if (img$is_fluoview) x <- img$fluoview_metadata else if (img$is_nih) x <- img$nih_metadata else if (img$is_astrotiff) x <- img$astrotiff_metadata else if (img$is_imagej) x <- img$imagej_metadata else if (img$is_lsm) x <- img$lsm_metadata - else if (img$is_qpi) x <- img$series[[1]]$pages[[page - 1L]]$description else if (img$is_micromanager) x <- img$micromanager_metadata else stop("unrecognized tif format\n", call. = FALSE) @@ -3009,6 +3011,20 @@ ometif_metadata <- tif_metadata ns <- xml2::xml_ns(x) has_namespace <- length(ns) > 0L + ## NEW: output = "kv" (read OME MapAnnotation K/Vs) --- + if (identical(output, "kv")) { + m_nodes <- xml2::xml_find_all( + x, + ".//*[local-name()='StructuredAnnotations']//*[local-name()='MapAnnotation']/*[local-name()='Value']//*[local-name()='M']" + ) + if (!length(m_nodes)) return(NULL) + keys <- xml2::xml_attr(m_nodes, "K") + values <- xml2::xml_text(m_nodes) + out <- as.list(values) + names(out) <- keys + return(out) + } + if (!is.null(node)) { node_parts <- node node_path <- paste(node_parts, collapse = "/") From 66eca1cee381f69affb662922eae78e3b57f0e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wen=20Wang=20=28=E7=8E=8B=E6=96=87=29?= Date: Tue, 28 Oct 2025 13:57:50 -0400 Subject: [PATCH 07/17] fix: typo --- R/create.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/create.R b/R/create.R index e9fe10f9..1d1a3c8a 100644 --- a/R/create.R +++ b/R/create.R @@ -3136,7 +3136,7 @@ createGiottoImage <- function(gobject = NULL, if (transf == "flip_x_axis") { mg_object <- magick::image_flop(mg_object) } else if (transf == "flip_y_axis") { - mg_object <- magick::image_flop(mg_object) + mg_object <- magick::image_flip(mg_object) } else { wrap_msg(transf, " is not a supported transformation, see details") From 62c2418888365fbb29c96266e033296e943e939b Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:26:22 -0400 Subject: [PATCH 08/17] slot checking changes --- DESCRIPTION | 2 +- NEWS.md | 5 + R/slot_check.R | 548 ++++++++++++++++++++++++++++++------------------- R/zzz.R | 1 + 4 files changed, 343 insertions(+), 213 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6048f273..86897b7b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: GiottoClass Title: Giotto Suite Object Definitions and Framework -Version: 0.4.10 +Version: 0.4.11 Authors@R: c( person("Ruben", "Dries", email = "rubendries@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-7650-7754")), diff --git a/NEWS.md b/NEWS.md index 1970e35f..ea0cd974 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# GiottoClass 0.4.11 (TBD) + +## enhancements +- escape hatch for gobject initialize checking. Set option `"giotto.init_check_severity"` to `"stop"` (default), or `"warning"` depending on needs. + # GiottoClass 0.4.10 (2025/09/30) ## bug fixes diff --git a/R/slot_check.R b/R/slot_check.R index 589ca5b1..510fb6be 100644 --- a/R/slot_check.R +++ b/R/slot_check.R @@ -1,3 +1,62 @@ +.init_check_severity <- function() { + x <- getOption("giotto.init_check_severity", "stop") + if (!x %in% c("stop", "warning")) { + stop("option 'giotto.init_check_severity' ", + "must be one of \"stop\" or \"warning\"", + call. = FALSE + ) + } + x +} + +.init_check_output_prefix <- function( + slotname = NULL, + spat_unit = NULL, + feat_type = NULL, + name = NULL) { + prefix <- "[gobject init-check]" + if (!is.null(slotname)) { + prefix <- sprintf("%s[%s]", prefix, color_purple(slotname)) + } + if (!is.null(spat_unit)) { + prefix <- sprintf("%s[%s]", prefix, color_blue(spat_unit)) + } + if (!is.null(feat_type)) { + prefix <- sprintf("%s[%s]", prefix, color_red(feat_type)) + } + if (!is.null(name)) { + prefix <- sprintf("%s[%s]", prefix, color_teal(name)) + } + paste0(prefix, "\n ") +} + +.init_check_log <- function(x, ..., type = "warn", + slotname = NULL, + spat_unit = NULL, + feat_type = NULL, + name = NULL, + verbose = NULL) { # only used with message type + type <- match.arg(type, c("warning", "stop", "message")) + pre <- .init_check_output_prefix( + slotname = slotname, + spat_unit = spat_unit, + feat_type = feat_type, + name = name + ) + txt <- paste(pre, x) + switch(type, + "warning" = { + warning(wrap_txt(txt, ...), call. = FALSE) + }, + "stop" = { + stop(wrap_txt(txt, ..., errWidth = TRUE), call. = FALSE) + }, + "message" = { + vmsg(.v = verbose, txt, ...) + } + ) +} + #### slot checks #### #' @keywords internal @@ -18,80 +77,65 @@ # polygon and/or expression data missing_su <- !used_su %in% g_su if (any(missing_su)) { - stop(wrap_txt("No expression or polygon information discovered for - spat_unit:", used_su[missing_su], - "Please add expression or polygon information for this spatial", - "unit first", - errWidth = TRUE - )) + .init_check_log( + type = "stop", + slotname = "cell metadata", + spat_unit = used_su[missing_su], + "No expression or polygon information discovered. + Please add expression or polygon info for this spatial unit first." + ) } for (su_i in used_su) { IDs <- spatIDs(gobject, spat_unit = su_i) - search_IDs <- c(head(IDs, 10L), tail(IDs, 10L)) + search_ids <- c(head(IDs, 10L), tail(IDs, 10L)) su_cm <- avail_cm[spat_unit == su_i, ] lapply(seq(nrow(su_cm)), function(obj_i) { + ft_i <- su_cm$feat_type[[obj_i]] + + # console print helper + .cm_log <- function(x, ..., type = "warning") { + .init_check_log( + x = x, + ..., + type = type, + slotname = "cell metadata", + spat_unit = su_i, + feat_type = ft_i, + verbose = verbose + ) + } + # get metadata meta <- getCellMetadata( gobject = gobject, spat_unit = su_i, - feat_type = su_cm$feat_type[[obj_i]], + feat_type = ft_i, output = "cellMetaObj", copy_obj = FALSE, set_defaults = FALSE ) - # no cell_IDs + # no cell_IDs (attempts repair) if (any(meta[][, is.na(cell_ID)])) { # denotes missing or need to repair IDs - - ID_col_guess <- which.max(vapply( - meta[], - function(x) sum(search_IDs %in% x), - FUN.VALUE = integer(1L) - )) - - if (ID_col_guess == 0L) { - # likely that cell_ID col does not exist yet - if (length(IDs) == nrow(meta[])) { - if (isTRUE(verbose)) { - wrap_msg("No cell_ID info found within cell metadata - Directly assigning based on gobject cell_ID") - } - meta[][, cell_ID := IDs] # set by reference - } else { - stop(wrap_txt("No cell_ID info found within cell - metadata and unable", - "to guess IDs based on gobject cell_ID", - errWidth = TRUE - )) - } - } else { # otherwise, cell_ID col found - ID_col_name <- names(ID_col_guess) - meta[][, cell_ID := NULL] # remove older column - data.table::setnames( - meta[], - old = ID_col_name, new = "cell_ID" - ) - if (isTRUE(verbose)) { - wrap_msg( - "Cell metadata: guessing", ID_col_name, - "as cell_ID column." - ) - } - } - - # cell ID guessing and assignment done # + # works by reference + .check_metadata_repair_ids(meta, + meta_type = "cell", + search_ids = search_ids, + gobj_ids = IDs, + verbose = verbose, + .log_fun = .cm_log + ) } - # duplicated IDs if (any(meta[][, duplicated(cell_ID)])) { - stop(wrap_txt( - "Cell metadata: duplicates found in cell_ID column.", - errWidth = TRUE - )) + .cm_log( + type = .init_check_severity(), + "Duplicates found in cell_ID column." + ) } # length mismatch @@ -108,20 +152,21 @@ } if (nrow(meta[]) != length(IDs)) { - stop(wrap_txt( - "Cell metadata: number of entries does not match", - "number of gobject IDs for this spat_unit (", - length(IDs), ")", - errWidth = TRUE - )) + .cm_log( + type = .init_check_severity(), + sprintf( + "Number of entries (%d) != number of gobject IDs (%d)", + nrow(meta[]), length(IDs) + ) + ) } # cell_ID contents mismatch if (!meta[][, setequal(cell_ID, IDs)]) { - stop(wrap_txt( - "Cell_metadata: IDs do not match between metadata and - cell_ID slot for this spat_unit" - )) + .cm_log( + type = .init_check_severity(), + "IDs do not match between metadata and cell_ID slot" + ) } # ensure ID col first @@ -131,6 +176,8 @@ } + + #' @keywords internal #' @noRd .check_feat_metadata <- function(gobject, @@ -148,13 +195,13 @@ # check hierarchical missing_ft <- !used_ft %in% g_ft if (any(missing_ft)) { - stop(wrap_txt( - "No expression or polygon information discovered for feat_type:", - used_ft[missing_ft], - "Please add expression or polygon information for this feature", - "type first", - errWidth = TRUE - )) + .init_check_log( + type = "stop", + slotname = "feature metadata", + feat_type = used_ft[missing_ft], + "No expression values found with this feature type. + Please add expression values data first." + ) } for (ft_i in used_ft) { @@ -162,6 +209,18 @@ lapply(seq(nrow(ft_fm)), function(obj_i) { su_i <- ft_fm$spat_unit[[obj_i]] + .fm_log <- function(x, ..., type = "warning") { + .init_check_log( + x = x, + ..., + type = type, + slotname = "feature metadata", + spat_unit = su_i, + feat_type = ft_i, + verbose = verbose + ) + } + # get metadata meta <- getFeatureMetadata( gobject = gobject, @@ -172,78 +231,45 @@ set_defaults = FALSE ) - # Start checking values when specific expression is added + #----------------- early exit cases -------------------# + # Start checking values when specific expression value is added if (is.null(avail_ex)) { return() } - - if (!nrow(avail_ex[spat_unit == su_i & feat_type == ft_i]) == 0L) { - IDs <- featIDs(getExpression( - gobject = gobject, - spat_unit = su_i, - feat_type = ft_i, - output = "exprObj" - )) - } else { + if (nrow(avail_ex[spat_unit == su_i & feat_type == ft_i]) == 0L) { return() # skip checks if no expression found - } - + } + #------------------------------------------------------# - # check metadata - search_IDs <- c(head(IDs, 10L), tail(IDs, 10L)) + # check IDs based on expression value info + IDs <- featIDs(getExpression( + gobject = gobject, + spat_unit = su_i, + feat_type = ft_i, + output = "exprObj" + )) + search_ids <- c(head(IDs, 10L), tail(IDs, 10L)) - # no feat_IDs + # no feat_IDs (attempts repair) if (any(meta[][, is.na(feat_ID)])) { # denotes missing or need to repair IDs - - ID_col_guess <- which.max(vapply( - meta[], - function(x) sum(search_IDs %in% x), - FUN.VALUE = integer(1L) - )) - - if (ID_col_guess == 0L) { - # likely that feat_ID col does not exist yet - if (length(IDs) == nrow(meta[])) { - if (isTRUE(verbose)) { - wrap_msg("No feat_ID info found within feat metadata - Directly assigning based on gobject feat_ID") - } - meta[][, feat_ID := IDs] # set by reference - } else { - stop(wrap_txt( - "No feat_ID info found within feat metadata and - unable", - "to guess IDs based on gobject feat_ID", - errWidth = TRUE - )) - } - } else { # otherwise, feat_ID col found - ID_col_name <- names(ID_col_guess) - meta[][, feat_ID := NULL] # remove older column - data.table::setnames( - meta[], - old = ID_col_name, new = "feat_ID" - ) - if (isTRUE(verbose)) { - wrap_msg( - "Feature metadata: guessing", ID_col_name, - "as feat_ID column." - ) - } - } - - # feat ID guessing and assignment done # + # works by reference + .check_metadata_repair_ids(meta, + meta_type = "feature", + search_ids = search_ids, + gobj_ids = IDs, + verbose = verbose, + .log_fun = .fm_log + ) } - # duplicated IDs if (any(meta[][, duplicated(feat_ID)])) { - warning(wrap_txt( - "Feature metadata: duplicates found in feat_ID column.", - errWidth = TRUE - )) + .fm_log( + type = .init_check_severity(), + "Duplicates found in feat_ID column." + ) } # length mismatch @@ -260,20 +286,21 @@ } if (nrow(meta[]) != length(IDs)) { - stop(wrap_txt( - "Feature metadata: number of entries does not match", - "number of gobject IDs for this spat_unit (", - length(IDs), ")", - errWidth = TRUE - )) + .fm_log( + type = .init_check_severity(), + sprintf( + "Number of entries (%d) != number of gobject IDs (%d)", + nrow(meta[]), length(IDs) + ) + ) } # feat_ID contents mismatch if (!meta[][, setequal(feat_ID, IDs)]) { - stop(wrap_txt( - "Feature metadata: IDs do not match between metadata and - feat_ID slot for this spat_unit" - )) + .fm_log( + type = .init_check_severity(), + "IDs do not match between metadata and feat_ID slot" + ) } # ensure ID col first @@ -282,7 +309,67 @@ } } - +.check_metadata_repair_ids <- function( + meta, meta_type, search_ids, gobj_ids, verbose = NULL, .log_fun +) { + switch(meta_type, + "cell" = { + id_term <- "cell_ID" + }, + "feature" = { + id_term <- "feat_ID" + }, + stop("unknown meta_type") + ) + + # check across columns for presence of known IDs + id_col_matches <- vapply(meta[], + function(x) sum(search_ids %in% x), + FUN.VALUE = integer(1L) + ) + # select the col with most matches + id_col_guess <- which.max(id_col_matches) + # set to 0 to signal failure if no matches are found + if (setequal(id_col_matches, 0)) id_col_guess <- 0 + + if (id_col_guess == 0L) { + # likely that ID col does not exist yet + if (length(gobj_ids) == nrow(meta[])) { + .log_fun( + type = "message", + sprintf( + "No %s info found within %s metadata. + Directly assigning based on gobject %s", + id_term, meta_type, id_term + ) + ) + meta[][, (id_term) := gobj_ids] # set by reference + } else { + .log_fun( + type = "stop", + sprintf( + "No %s info found within %s metadata. + Unable to guess IDs based on gobject %s", + id_term, meta_type, id_term + ) + ) + } + } else { # otherwise, ID col found + id_col_name <- names(id_col_guess) + meta[][, (id_term) := NULL] # remove older column + data.table::setnames( + meta[], + old = id_col_name, new = id_term + ) + .log_fun( + type = "message", + sprintf("Guessing %s as %s column", id_col_name, id_term) + ) + } + # this function works via set by reference + # no values to return + return() +} @@ -301,67 +388,92 @@ # find available spatial locations avail_sl <- list_spatial_locations(gobject) - avail_ex <- list_expression(gobject) - avail_si <- list_spatial_info(gobject) - - # check hierarchical - missing_unit <- !(avail_sl$spat_unit) %in% - c(avail_ex$spat_unit, avail_si$spat_info) - if (any(missing_unit)) { - stop(wrap_txt( - "No expression or polygon information discovered for spat_unit:", - avail_sl$spat_unit[missing_unit], - "Please add expression or polygon information for this spatial", - "unit first" - )) - } - - for (spat_unit_i in avail_sl[["spat_unit"]]) { - expected_cell_ID_names <- get_cell_id( - gobject = gobject, - spat_unit = spat_unit_i - ) + # avail_ex <- list_expression(gobject) + # avail_si <- list_spatial_info(gobject) + + # # check hierarchical + # missing_unit <- !(avail_sl$spat_unit) %in% + # c(avail_ex$spat_unit, avail_si$spat_info) + # if (any(missing_unit)) { + # .init_check_log( + # type = .init_check_severity(), + # slotname = "spatial locations", + # spat_unit = avail_sl$spat_unit[missing_unit], + # "No expression values or polygon information discovered. + # Please add expression values or polygon information for this + # spatial unit first." + # ) + # } + + for (su_i in avail_sl[["spat_unit"]]) { + gobj_ids <- spatIDs(gobject, spat_unit = su_i) + + if (length(gobj_ids) == 0L) { + # no values means that the expression information or polygons + # data has not been added yet. Nothing to check against. + next + } - for (coord_i in avail_sl[spat_unit == spat_unit_i, name]) { + for (coord_i in avail_sl[spat_unit == su_i, name]) { # 1. get colnames - spatlocsDT <- get_spatial_locations(gobject, - spat_unit = spat_unit_i, + spatlocs <- get_spatial_locations(gobject, + spat_unit = su_i, spat_loc_name = coord_i, output = "data.table", copy_obj = FALSE ) - missing_cell_IDs <- spatlocsDT[, all(is.na(cell_ID))] + missing_ids <- spatlocs[, all(is.na(cell_ID))] + + # setup console prints + .sl_log <- function(x, ..., type = "warning") { + .init_check_log( + x = x, + ..., + type = type, + slotname = "spatial locations", + spat_unit = su_i, + name = coord_i + ) + } - # if cell_ID column is provided then compare with expected cell_IDs - if (!isTRUE(missing_cell_IDs)) { - spatial_cell_id_names <- spatlocsDT[["cell_ID"]] - - if (!setequal(spatial_cell_id_names, expected_cell_ID_names)) { - stop( - "cell_IDs between spatial and expression information - are not the same for: \n spatial unit: ", - spat_unit_i, " and coordinates: ", - coord_i, " \n" + ## check if spatlocs and cell_ID do not match in length + if (spatlocs[, .N] != length(gobj_ids)) { + .sl_log( + type = .init_check_severity(), + sprintf( + "Number of entries (%d) != number of gobject IDs (%d)", + spatlocs[, .N], length(gobj_ids) ) - } - } else { - # if cell_ID column is not provided then add expected cell_IDs - - ## error if spatlocs and cell_ID do not match in length - if (spatlocsDT[, .N] != length(expected_cell_ID_names)) { - stop( - "Number of rows of spatial locations do not match - with cell IDs for: \n spatial unit: ", - spat_unit_i, " and coordinates: ", coord_i, " \n" + ) + if (missing_ids) { # if also missing IDs for spatlocs... + # hardcode this error since the reason it occurs is + # easier to understand when reported here + .sl_log( + type = "stop", + "Number of entries mismatch with gobject IDs AND + missing spatial locations IDs. Aborting." ) } + } + if (missing_ids) { ## ! modify coords within gobject by reference - spatlocsDT <- spatlocsDT[, cell_ID := expected_cell_ID_names] + spatlocs <- spatlocs[, cell_ID := gobj_ids] + } + + # if cell_ID column is provided then compare with expected cell_IDs + if (!missing_ids) { + spatial_cell_id_names <- spatlocs[["cell_ID"]] + + if (!setequal(spatial_cell_id_names, gobj_ids)) { + .sl_log( + type = .init_check_severity(), + "cell_IDs mismatch with gobject IDs" + ) + } } } } - return(invisible()) } @@ -383,11 +495,13 @@ # check hierarchical missing_su <- !used_su %in% avail_sl$spat_unit if (sum(missing_su != 0L)) { - stop(wrap_txt("Matching spatial locations in spat_unit(s)", + .init_check_log( + slotname = "spatial networks", + type = .init_check_severity(), + "Matching spatial locations in spat_unit(s)", used_su[missing_su], - "must be added before the respective spatial networks", - errWidth = TRUE - )) + "must be added before the respective spatial networks." + ) } if (!is.null(used_su)) { @@ -406,12 +520,14 @@ verbose = FALSE ) if (!all(spatIDs(sn_obj) %in% IDs)) { - warning(wrap_txt( - "spat_unit:", su_i, - "name:", su_sn$name[[obj_i]], "\n", + .init_check_log( + type = "warning", + slotname = "spatial networks", + spat_unit = su_i, + name = su_sn$name[[obj_i]], "Spatial network vertex names are not all found in - gobject IDs" - )) + gobject IDs." + ) } }) } @@ -438,11 +554,13 @@ # check hierarchical missing_su <- !used_su %in% avail_sl$spat_unit if (sum(missing_su != 0L)) { - stop(wrap_txt("Matching spatial locations in spat_unit(s)", + .init_check_log( + slotname = "spatial enrichments", + type = .init_check_severity(), + "Matching spatial locations in spat_unit(s)", used_su[missing_su], - "must be added before the respective spatial enrichments", - errWidth = TRUE - )) + "must be added before the respective spatial enrichments." + ) } if (!is.null(used_su)) { @@ -461,13 +579,15 @@ set_defaults = FALSE ) if (!setequal(spatIDs(se_obj), IDs)) { - warning(wrap_txt( - "spat_unit:", su_i, - "feat_type:", su_se$feat_type[[obj_i]], - "name:", su_se$name[[obj_i]], "\n", + .init_check_log( + slotname = "spatial enrichments", + type = "warning", + spat_unit = su_i, + feat_type = su_se$feat_type[[obj_i]], + name = su_se$name[[obj_i]], "Spatial enrichment IDs are not all found in gobject - IDs" - )) + IDs." + ) } }) } @@ -507,11 +627,13 @@ # check hierarchical missing_su_ft <- !used_su_ft %in% ex_su_ft if (sum(missing_su_ft != 0L)) { - stop(wrap_txt("Matching expression values [spat_unit][feat_type]:\n", + .init_check_log( + type = .init_check_severity(), + slotname = "dimension reduction", + "Matching expression values [spat_unit][feat_type]:\n", used_su_ft[missing_su_ft], - "\nmust be added before the respective dimension reductions", - errWidth = TRUE - )) + "\nmust be added before the respective dimension reductions." + ) } if (!is.null(used_su)) { @@ -613,11 +735,13 @@ # check hierarchical missing_su_ft <- !used_su_ft %in% dr_su_ft if (sum(missing_su_ft != 0L)) { - stop(wrap_txt("Matching dimension reductions [spat_unit][feat_type]:\n", + .init_check_log( + type = .init_check_severity(), + slotname = "nearest networks", + "Matching dimension reductions [spat_unit][feat_type]:\n", used_su_ft[missing_su_ft], - "\nmust be added before the respective nearest neighbor networks", - errWidth = TRUE - )) + "\nmust be added before the respective nearest neighbor networks." + ) } if (!is.null(used_su)) { diff --git a/R/zzz.R b/R/zzz.R index 034b46f3..ff2c7c1a 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -14,4 +14,5 @@ init_option("giotto.plotengine3d", "plotly") init_option("giotto.update_param", TRUE) init_option("giotto.no_python_warn", FALSE) + init_option("giotto.init_check_severity", "stop") } From 9790ce4e8526349dc1a1fec6d4a6fc733a1a26b3 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:54:14 -0400 Subject: [PATCH 09/17] chore: docs --- man/names.Rd | 8 ++++---- man/tif_metadata.Rd | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/man/names.Rd b/man/names.Rd index 804e44cf..fb92df32 100644 --- a/man/names.Rd +++ b/man/names.Rd @@ -3,18 +3,18 @@ \name{names} \alias{names} \alias{names,giottoLargeImage-method} -\alias{names<-,giottoLargeImage,ANY-method} +\alias{names<-,giottoLargeImage-method} \alias{names,processParam-method} -\alias{names<-,processParam,ANY-method} +\alias{names<-,processParam-method} \title{Names of objects} \usage{ \S4method{names}{giottoLargeImage}(x) -\S4method{names}{giottoLargeImage,ANY}(x) <- value +\S4method{names}{giottoLargeImage}(x) <- value \S4method{names}{processParam}(x) -\S4method{names}{processParam,ANY}(x) <- value +\S4method{names}{processParam}(x) <- value } \arguments{ \item{x}{object} diff --git a/man/tif_metadata.Rd b/man/tif_metadata.Rd index bcce507f..dc50927b 100644 --- a/man/tif_metadata.Rd +++ b/man/tif_metadata.Rd @@ -10,7 +10,7 @@ tif_metadata( node = NULL, page = NULL, type = c("attribute", "text", "double", "integer"), - output = c("data.frame", "xml", "list", "structure") + output = c("data.frame", "xml", "list", "structure", "kv") ) ometif_metadata( @@ -18,7 +18,7 @@ ometif_metadata( node = NULL, page = NULL, type = c("attribute", "text", "double", "integer"), - output = c("data.frame", "xml", "list", "structure") + output = c("data.frame", "xml", "list", "structure", "kv") ) } \arguments{ @@ -36,11 +36,11 @@ for \code{.qptiff}.} with figuring out which is most appropriate.} \item{output}{character. One of "data.frame" to return a data.frame of the -attributes information of the xml node, "xmL" for an \{xml2\} representation +attributes information of the xml node, "xml" for an \{xml2\} representation of the node, "list" for an R native list (note that many items in the -list may have overlapping names that make indexing difficult), or +list may have overlapping names that make indexing difficult), "structure" to invisibly return NULL, but print the structure of the XML -document or node.} +document/node, or "kv" (extract key/value pairs from OME MapAnnotations).} } \value{ list/data.frame/XML depending on \code{output} From f6e8fdcb7e1a3c55b845eedbfc323666841d73f6 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:54:32 -0400 Subject: [PATCH 10/17] fix: tests now align with changes --- tests/testthat/test_03_accessors.R | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/testthat/test_03_accessors.R b/tests/testthat/test_03_accessors.R index 949791a3..14214811 100644 --- a/tests/testthat/test_03_accessors.R +++ b/tests/testthat/test_03_accessors.R @@ -581,19 +581,12 @@ describe("Giotto Object Setters Validation and Edge Cases", { set_sl_b <- expect_no_error(getSpatialLocations(test_sl, name = "new")) }) - it("errors when setting Spatlocs for a spat_unit not backed by expr or spatial_info", { - # available spat unit in expression is only 'aggregate' - test_sl <- expect_error(setSpatialLocations(test_ex, sl, spat_unit = "new", verbose = FALSE), - regexp = "No expression" - ) - }) - it("errors when Spatlocs spatID is mismatched with expression info", { gpoly <- test_data$gpoly test_ex <- setPolygonInfo(test_ex, gpoly, name = "new", verbose = FALSE) # due to subset, expected that sl will have fewer IDs expect_error(setSpatialLocations(test_ex, sl[1:6], spat_unit = "new", verbose = FALSE), - regexp = "between spatial and" + regexp = "Number of entries" ) }) From 451e35aa8774933a23a3079440097f539cce50ae Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:13:18 -0500 Subject: [PATCH 11/17] new: misc slot for unstructured data --- DESCRIPTION | 2 +- NEWS.md | 5 ++++- R/classes.R | 24 ++++++++++++++++++------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 86897b7b..da1c373c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: GiottoClass Title: Giotto Suite Object Definitions and Framework -Version: 0.4.11 +Version: 0.4.12 Authors@R: c( person("Ruben", "Dries", email = "rubendries@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-7650-7754")), diff --git a/NEWS.md b/NEWS.md index ea0cd974..8448143f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,7 @@ -# GiottoClass 0.4.11 (TBD) +# GiottoClass 0.4.12 (TBD) + +## new +- `misc` slot for storing unstructured data ## enhancements - escape hatch for gobject initialize checking. Set option `"giotto.init_check_severity"` to `"stop"` (default), or `"warning"` depending on needs. diff --git a/R/classes.R b/R/classes.R index 524a1fd6..680e22a8 100644 --- a/R/classes.R +++ b/R/classes.R @@ -459,7 +459,8 @@ updateGiottoObject <- function(gobject) { } # warn if gobject newer than package - if (as.character(.gversion(gobject)) > as.character(packageVersion("GiottoClass"))) { + if (numeric_version(.gversion(gobject)) > + numeric_version(packageVersion("GiottoClass"))) { warning( call. = FALSE, sprintf( @@ -473,7 +474,7 @@ updateGiottoObject <- function(gobject) { # [version-based updates] -------------------------------------------------# # GiottoClass 0.3.0 removes @largeImages slot - if (.gversion(gobject) < "0.3.0") { + if (.gversion(gobject) < numeric_version("0.3.0")) { gobject <- .update_image_slot(gobject) } @@ -481,6 +482,12 @@ updateGiottoObject <- function(gobject) { # TODO remove in future update gobject@images <- lapply(gobject@images, .update_giotto_image) + # GiottoClass 0.4.12 adds @misc slot + if (.gversion(gobject) < numeric_version("0.4.12")) { + attr(gobject, "misc") <- NA_character_ + gobject@misc <- NULL + } + # -------------------------------------------------------------------------# # finally, set updated version number @@ -567,6 +574,7 @@ updateGiottoObject <- function(gobject) { #' @slot join_info information about joined Giotto objects #' @slot multiomics multiomics integration results #' @slot h5_file path to h5 file +#' @slot misc miscellaneous or unstructured data #' @details #' #' \[**initialize**\] @@ -618,7 +626,8 @@ giotto <- setClass( versions = "list", join_info = "ANY", multiomics = "ANY", - h5_file = "ANY" + h5_file = "ANY", + misc = "ANY" # mirai = 'list' ), prototype = list( @@ -643,7 +652,8 @@ giotto <- setClass( versions = .versions_info(), join_info = NULL, multiomics = NULL, - h5_file = NULL + h5_file = NULL, + misc = NULL # mirai = list() ) @@ -694,7 +704,8 @@ setClass( versions = "ANY", join_info = "ANY", multiomics = "ANY", - h5_file = "ANY" + h5_file = "ANY", + misc = "ANY" ), prototype = list( packed_spatial_info = NULL, @@ -718,7 +729,8 @@ setClass( versions = NULL, join_info = NULL, multiomics = NULL, - h5_file = NULL + h5_file = NULL, + misc = NULL ) ) From e86adc668799fa7f11d0aa923de2c4357b413181 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:22:04 -0500 Subject: [PATCH 12/17] require misc to be a list --- R/classes.R | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/R/classes.R b/R/classes.R index 680e22a8..b83d153a 100644 --- a/R/classes.R +++ b/R/classes.R @@ -484,8 +484,7 @@ updateGiottoObject <- function(gobject) { # GiottoClass 0.4.12 adds @misc slot if (.gversion(gobject) < numeric_version("0.4.12")) { - attr(gobject, "misc") <- NA_character_ - gobject@misc <- NULL + attr(gobject, "misc") <- list() } # -------------------------------------------------------------------------# @@ -627,7 +626,7 @@ giotto <- setClass( join_info = "ANY", multiomics = "ANY", h5_file = "ANY", - misc = "ANY" + misc = "list" # mirai = 'list' ), prototype = list( @@ -653,7 +652,7 @@ giotto <- setClass( join_info = NULL, multiomics = NULL, h5_file = NULL, - misc = NULL + misc = list() # mirai = list() ) @@ -705,7 +704,7 @@ setClass( join_info = "ANY", multiomics = "ANY", h5_file = "ANY", - misc = "ANY" + misc = "list" ), prototype = list( packed_spatial_info = NULL, @@ -730,7 +729,7 @@ setClass( join_info = NULL, multiomics = NULL, h5_file = NULL, - misc = NULL + misc = list() ) ) From f77f34f28801c91949fd0185f4ee37c98f40d3d9 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:43:49 -0500 Subject: [PATCH 13/17] fix numeric_version() and numeric incompat --- R/classes.R | 6 +++--- man/giotto-class.Rd | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/R/classes.R b/R/classes.R index b83d153a..b8c13fe9 100644 --- a/R/classes.R +++ b/R/classes.R @@ -453,13 +453,13 @@ updateGiottoObject <- function(gobject) { if (!is.null(attr(gobject, "OS_platform"))) { attr(gobject, "OS_platform") <- NULL } - if (is.null(attr(gobject, "versions"))) { # apply default version 0 + if (is.null(attr(gobject, "versions"))) { # apply default version 0.0.0 attr(gobject, "versions") <- .versions_info() - gobject@versions$gclass <- 0 # untracked + gobject@versions$gclass <- "0.0.0" # untracked } # warn if gobject newer than package - if (numeric_version(.gversion(gobject)) > + if (.gversion(gobject) > numeric_version(packageVersion("GiottoClass"))) { warning( call. = FALSE, diff --git a/man/giotto-class.Rd b/man/giotto-class.Rd index 2993377d..92b5f418 100644 --- a/man/giotto-class.Rd +++ b/man/giotto-class.Rd @@ -83,6 +83,8 @@ protein, metabolites, ... that are provided in the expression slot.} \item{\code{multiomics}}{multiomics integration results} \item{\code{h5_file}}{path to h5 file} + +\item{\code{misc}}{miscellaneous or unstructured data} }} \examples{ From 8bf876f1c69854d48e658df9193780188b279902 Mon Sep 17 00:00:00 2001 From: josschavezf Date: Fri, 7 Nov 2025 17:57:25 -0500 Subject: [PATCH 14/17] run devtools::document --- man/hull.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/hull.Rd b/man/hull.Rd index d74ded57..fbb688b3 100644 --- a/man/hull.Rd +++ b/man/hull.Rd @@ -24,7 +24,7 @@ convHull(x, ...) \item{by}{character (variable name), to get a new geometry for groups of input geometries} -\item{param}{numeric between 0 and 1. For the "concave_*" types only. For \code{type="concave_ratio"} this is The edge length ratio value, between 0 and 1. For \code{type="concave_length"} this the maximum edge length (a value > 0). For \code{type="concave_polygons"} thism specifies the maximum Edge Length as a fraction of the difference between the longest and shortest edge lengths between the polygons. This normalizes the maximum edge length to be scale-free. A value of 1 produces the convex hull; a value of 0 produces the original polygons} +\item{param}{numeric between 0 and 1. For the "concave_*" types only. For \code{type="concave_ratio"} this is the edge length ratio value, between 0 and 1. For \code{type="concave_length"} this the maximum edge length (a value > 0). For \code{type="concave_polygons"} this specifies the maximum Edge Length as a fraction of the difference between the longest and shortest edge lengths between the polygons. This normalizes the maximum edge length to be scale-free. A value of 1 produces the convex hull; a value of 0 produces the original polygons} \item{allowHoles}{logical. May the output polygons contain holes? For "concave_*" methods only} From 9dcbb814500564386d6dc504b97ab3426d62631d Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:56:00 -0500 Subject: [PATCH 15/17] fix: automatically use count column if present --- R/aggregate.R | 2 ++ R/methods-names.R | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/R/aggregate.R b/R/aggregate.R index 920ee96e..0e982fe3 100644 --- a/R/aggregate.R +++ b/R/aggregate.R @@ -328,6 +328,8 @@ setMethod( return_gpolygon = TRUE, verbose = TRUE, ...) { + if (names(y) ) + res <- calculateOverlap( x = x[], y = y[], diff --git a/R/methods-names.R b/R/methods-names.R index 8897467e..6dff405d 100644 --- a/R/methods-names.R +++ b/R/methods-names.R @@ -140,4 +140,7 @@ setMethod("names", signature("processParam"), function(x) names(x@param)) setMethod("names<-", signature("processParam"), function(x, value) { names(x@param) <- value x -}) \ No newline at end of file +}) + +#' @rdname names +setMethod("names", signature("giottoPoints"), function(x) names(x[])) \ No newline at end of file From 937fa302c78eb38dc9f0950a3c01db9681f9c64d Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:10:39 -0500 Subject: [PATCH 16/17] enh: autodetect "count" col in feature points --- R/aggregate.R | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/R/aggregate.R b/R/aggregate.R index 0e982fe3..fa81acc5 100644 --- a/R/aggregate.R +++ b/R/aggregate.R @@ -328,7 +328,16 @@ setMethod( return_gpolygon = TRUE, verbose = TRUE, ...) { - if (names(y) ) + if ("count" %in% names(y) && is.null(count_info_column)) { + vmsg(.v = verbose, + "[overlap] Found column \"count\" in feature info. + - Using as `count_info_column` + [!] Set count_info_column = FALSE to disable.") + count_info_column <- "count" + } + if (isFALSE(count_info_column)) { + count_info_column <- NULL + } res <- calculateOverlap( x = x[], @@ -1262,7 +1271,7 @@ setMethod( ...) { type <- match.arg(type, choices = c("point", "intensity")) checkmate::assert_character(name, len = 1L) - if (!is.null(count_info_column)) { + if (!is.null(count_info_column) && !isFALSE(count_info_column)) { checkmate::assert_character(count_info_column, len = 1L) } checkmate::assert_logical(return_gobject) @@ -1441,6 +1450,19 @@ setMethod( # 2. Perform aggregation to counts DT + + # autodetect counts col + if ("count" %in% names(dtoverlap) && is.null(count_info_column)) { + vmsg(.v = verbose, + "[overlap] Found column \"count\" in feature info. + - Using as `count_info_column` + [!] Set count_info_column = FALSE to disable.") + count_info_column <- "count" + } + if (isFALSE(count_info_column)) { + count_info_column <- NULL + } + if (!is.null(count_info_column)) { # if there is a counts col if (!count_info_column %in% colnames(dtoverlap)) { From b3b22945af8537c49dddae57e833fd32a6a44eb2 Mon Sep 17 00:00:00 2001 From: George Chen <72078254+jiajic@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:12:56 -0500 Subject: [PATCH 17/17] chore: docs --- R/aggregate.R | 8 ++++++-- man/calculateOverlap.Rd | 4 +++- man/hull.Rd | 2 +- man/names.Rd | 3 +++ man/overlapToMatrix.Rd | 4 +++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/R/aggregate.R b/R/aggregate.R index fa81acc5..063fc038 100644 --- a/R/aggregate.R +++ b/R/aggregate.R @@ -80,7 +80,9 @@ polygon_to_raster <- function(polygon, field = NULL) { #' in `feat_subset_column` in order to subset feature points when performing #' overlap calculation. #' @param count_info_column character. (optional) column with count information. -#' Useful in cases when more than one detection is reported per point. +#' Useful in cases when more than one detection is reported per point. If a +#' column called "count" is present in the feature points data, it will be +#' automatically selected. #' @param verbose be verbose #' @param \dots additional params to pass to methods. #' @details `feat_subset_column`, `feat_subset_ids`, and `count_info_column` are @@ -1231,7 +1233,9 @@ calculateOverlapParallel <- function(gobject, #' @param x object containing overlaps info. Can be giotto object or SpatVector #' points or data.table of overlaps generated from `calculateOverlap` #' @param name name for the overlap count matrix -#' @param count_info_column column with count information +#' @param count_info_column column with count information. If a +#' column called "count" is present in the feature points data, it will be +#' automatically selected. #' @param \dots additional params to pass to methods #' @concept overlap #' @returns giotto object or count matrix diff --git a/man/calculateOverlap.Rd b/man/calculateOverlap.Rd index f75ef50c..1facc0c6 100644 --- a/man/calculateOverlap.Rd +++ b/man/calculateOverlap.Rd @@ -111,7 +111,9 @@ in \code{feat_subset_column} in order to subset feature points when performing overlap calculation.} \item{count_info_column}{character. (optional) column with count information. -Useful in cases when more than one detection is reported per point.} +Useful in cases when more than one detection is reported per point. If a +column called "count" is present in the feature points data, it will be +automatically selected.} \item{return_gpolygon}{default = TRUE. Whether to return the entire giottoPolygon provided to \code{x}, but with the overlaps information appended or diff --git a/man/hull.Rd b/man/hull.Rd index fbb688b3..d74ded57 100644 --- a/man/hull.Rd +++ b/man/hull.Rd @@ -24,7 +24,7 @@ convHull(x, ...) \item{by}{character (variable name), to get a new geometry for groups of input geometries} -\item{param}{numeric between 0 and 1. For the "concave_*" types only. For \code{type="concave_ratio"} this is the edge length ratio value, between 0 and 1. For \code{type="concave_length"} this the maximum edge length (a value > 0). For \code{type="concave_polygons"} this specifies the maximum Edge Length as a fraction of the difference between the longest and shortest edge lengths between the polygons. This normalizes the maximum edge length to be scale-free. A value of 1 produces the convex hull; a value of 0 produces the original polygons} +\item{param}{numeric between 0 and 1. For the "concave_*" types only. For \code{type="concave_ratio"} this is The edge length ratio value, between 0 and 1. For \code{type="concave_length"} this the maximum edge length (a value > 0). For \code{type="concave_polygons"} thism specifies the maximum Edge Length as a fraction of the difference between the longest and shortest edge lengths between the polygons. This normalizes the maximum edge length to be scale-free. A value of 1 produces the convex hull; a value of 0 produces the original polygons} \item{allowHoles}{logical. May the output polygons contain holes? For "concave_*" methods only} diff --git a/man/names.Rd b/man/names.Rd index fb92df32..cd5dda7d 100644 --- a/man/names.Rd +++ b/man/names.Rd @@ -6,6 +6,7 @@ \alias{names<-,giottoLargeImage-method} \alias{names,processParam-method} \alias{names<-,processParam-method} +\alias{names,giottoPoints-method} \title{Names of objects} \usage{ \S4method{names}{giottoLargeImage}(x) @@ -15,6 +16,8 @@ \S4method{names}{processParam}(x) \S4method{names}{processParam}(x) <- value + +\S4method{names}{giottoPoints}(x) } \arguments{ \item{x}{object} diff --git a/man/overlapToMatrix.Rd b/man/overlapToMatrix.Rd index 0dd9db93..c5a31209 100644 --- a/man/overlapToMatrix.Rd +++ b/man/overlapToMatrix.Rd @@ -54,7 +54,9 @@ points or data.table of overlaps generated from \code{calculateOverlap}} \item{type}{character. Type of overlap data (either 'point' or 'intensity')} -\item{count_info_column}{column with count information} +\item{count_info_column}{column with count information. If a +column called "count" is present in the feature points data, it will be +automatically selected.} \item{aggr_function}{function to aggregate image information (default = sum)}