diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3eb78a --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Large test datasets (downloaded locally, not versioned) +data/ +data2/ +data3/ + +# Local test caches / logs / generated outputs +tests_local/*.rds +tests_local/*.log +untitled_consensus_cluster/ + +# R / RStudio +.Rproj.user/ +.Rhistory +.RData +.Ruserdata +*.o +*.so diff --git a/DESCRIPTION b/DESCRIPTION index 8b40e54..2895b54 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: CellTrek Title: Spatial Charting of Single Cell Transcriptomes in Tissues -Version: 0.0.94 +Version: 0.0.95 Authors@R: c( person("Runmin", 'Wei', email='wander1021@gmail.com', role=c('aut', 'cre')), person("Siyuan", 'He', email='SHe2@mdanderson.org', role=c('aut', 'ctb')) ) @@ -10,8 +10,8 @@ License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.1.2 -Imports: - akima, +Imports: + interp, data.table, dbscan, dplyr, diff --git a/NAMESPACE b/NAMESPACE index ba4a4d8..761e1b4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,6 +37,6 @@ import(shiny) import(tibble) import(tidyr) import(visNetwork) -importFrom(akima,interpp) +importFrom(interp,interpp) importFrom(geometry,delaunayn) importFrom(packcircles,circleRepelLayout) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..ffc4ff7 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,34 @@ +# CellTrek 0.0.95 + +## Seurat v5 compatibility + +CellTrek now runs on Seurat v5 (tested with Seurat 5.3.0 / SeuratObject 5.2.0 on R 4.3). +Verified end-to-end on VisiumV1 (brain_st_cortex + brain_sc) and VisiumV2 (stxBrain + +allen_cortex) data through traint, celltrek, scoloc (KL/DT/KD), scoexp (cc) and run_kdist. + +* Replaced direct assay-slot access (`obj[[assay]]@data` / `@counts` / `@scale.data`) + with the version-agnostic `GetAssayData()` / `SetAssayData()` accessors throughout + `celltrek.R`, `scoexp.R` and `FindCorMarkers.R`. The old v3 idiom of moving counts into + the data slot now uses `SetAssayData(..., layer = 'data')`. +* Made dimensional-reduction keys v5-compliant (`celltrekraw_`, `celltrek_`, `pca_`, + `umap_`, `tsne_`). +* `CreateSeuratObject()` inputs are coerced with `as.matrix()` where needed. + +## Spatial coordinates + +* `traint()` resolves ST coordinates with the priority: `coord_df` > the image + `@coordinates[,coord_xy]` slot (VisiumV1, kept at original full-resolution scale) > + `GetTissueCoordinates()` (VisiumV2/FOV `x`/`y`). Preferring `@coordinates` avoids the + coordinate down-scaling that `GetTissueCoordinates()` applies to VisiumV1 objects + (which shrank spot distances and made the fixed `repel_r` over-disperse the charting). + Writes to `image@coordinates` are guarded to VisiumV1 (VisiumV2 has no such slot). +* New `traint(coord_df = )` argument: supply ST spatial coordinates directly to bypass + differences/naming issues across image structures. Resolution priority is + `coord_df` > auto-detect > a clear error asking for `coord_df`. Rownames must cover all + ST cells/spots; columns named in `coord_xy` are used if present, otherwise the first two + columns are taken as (coord_x, coord_y). + +## Dependencies + +* Interpolation now uses `interp::interpp` instead of `akima::interpp` (akima is not + available on all platforms, e.g. macOS arm64). Updated DESCRIPTION and NAMESPACE. diff --git a/R/FindCorMarkers.R b/R/FindCorMarkers.R index a8998b4..ff9c074 100644 --- a/R/FindCorMarkers.R +++ b/R/FindCorMarkers.R @@ -12,7 +12,7 @@ #' #' @examples CorMarkers <- FindCorMarkers(srt_inp=test_seurat, assay='RNA', features=c('k_dist', 'sig_score'), method='spearman') FindCorMarkers <- function(srt_inp, assay='RNA', features, method='spearman') { - exp_dat <- as.matrix(srt_inp[[assay]]@data) + exp_dat <- as.matrix(GetAssayData(srt_inp, assay=assay, layer='data')) output_df <- data.frame(p_val=numeric(), cor=numeric(), p_val_adj=numeric(), feature=character(), gene=character()) if (all(features %in% colnames(srt_inp@meta.data))) for (i in 1:length(features)) { diff --git a/R/celltrek.R b/R/celltrek.R index c71c2e1..01885c6 100644 --- a/R/celltrek.R +++ b/R/celltrek.R @@ -7,6 +7,10 @@ #' @param nfeatures number of features for integration #' @param cell_names cell cluster/type column name in SC meta data #' @param coord_xy coordinates column names in ST images slot +#' @param coord_df optional data frame/matrix of ST spatial coordinates to use directly, +#' bypassing coordinate auto-detection from the image slot. Rownames must cover all ST +#' cells/spots. If it contains the columns named in \code{coord_xy} those are used, +#' otherwise the first two columns are taken as (coord_x, coord_y). #' @param gene_kept selected genes to be kept during integration #' @param norm normalization method: LogNormalize/SCTransform #' @@ -20,15 +24,64 @@ #' @examples #' st_sc_traint <- traint(st_data=brain_st, sc_data=brain_sc, st_assay='Spatial', sc_assay='scint', nfeatures=2000, cell_names='cell_names', coord_xy=c('imagerow', 'imagecol'), gene_kept=NULL) traint <- function (st_data, sc_data, st_assay='Spatial', sc_assay='scint', norm='LogNormalize', nfeatures=2000, - cell_names='cell_names', coord_xy=c('imagerow', 'imagecol'), gene_kept=NULL, ...) { + cell_names='cell_names', coord_xy=c('imagerow', 'imagecol'), coord_df=NULL, gene_kept=NULL, ...) { st_data$id <- names(st_data$orig.ident) sc_data$id <- names(sc_data$orig.ident) sc_data$cell_names <- make.names(sc_data@meta.data[, cell_names]) st_data$type <- 'st' sc_data$type <- 'sc' - st_data$coord_x <- st_data@images[[1]]@coordinates[, coord_xy[1]] - st_data$coord_y <- st_data@images[[1]]@coordinates[, coord_xy[2]] + ## Resolve ST spatial coordinates. Priority: + ## 1) user-supplied coord_df (bypasses all image structures); + ## 2) the image @coordinates slot columns named in coord_xy (VisiumV1) -- used + ## directly to preserve the original full-resolution coordinate scale; + ## 3) GetTissueCoordinates() for image classes without @coordinates + ## (VisiumV2/FOV, which expose x/y). If none work, stop and ask for coord_df. + ## NOTE: GetTissueCoordinates() rescales VisiumV1 coordinates by the image scale + ## factor, which would shrink spot distances; hence we prefer @coordinates here. + st_cells <- Cells(st_data) + if (!is.null(coord_df)) { + coord_df <- as.data.frame(coord_df) + if (all(coord_xy %in% colnames(coord_df))) { + xy <- coord_df[, coord_xy, drop=FALSE] + } else { + if (ncol(coord_df) < 2) stop("coord_df must have at least two columns (coord_x, coord_y)") + xy <- coord_df[, 1:2, drop=FALSE] + } + if (!all(st_cells %in% rownames(xy))) { + stop("coord_df rownames must cover all ST cells/spots (Cells(st_data)); ", + sum(!st_cells %in% rownames(xy)), " missing") + } + st_data$coord_x <- xy[st_cells, 1] + st_data$coord_y <- xy[st_cells, 2] + } else if (length(st_data@images) > 0 && + 'coordinates' %in% slotNames(st_data@images[[1]]) && + all(coord_xy %in% colnames(st_data@images[[1]]@coordinates))) { + ## VisiumV1: raw imagerow/imagecol (original scale) + st_coord <- st_data@images[[1]]@coordinates + st_data$coord_x <- st_coord[st_cells, coord_xy[1]] + st_data$coord_y <- st_coord[st_cells, coord_xy[2]] + } else { + st_coord <- tryCatch(Seurat::GetTissueCoordinates(st_data), error=function(e) NULL) + if (is.null(st_coord)) { + stop("Could not read spatial coordinates from st_data (unsupported image structure). ", + "Please supply them via the 'coord_df' argument.") + } + if (all(coord_xy %in% colnames(st_coord))) { + st_data$coord_x <- st_coord[st_cells, coord_xy[1]] + st_data$coord_y <- st_coord[st_cells, coord_xy[2]] + } else if (all(c('x', 'y') %in% colnames(st_coord))) { + ## VisiumV2/FOV: x is the column (imagecol), y is the row (imagerow) + st_data$coord_x <- st_coord[st_cells, 'y'] + st_data$coord_y <- st_coord[st_cells, 'x'] + } else { + stop("Spatial coordinate columns not found (looked for c('", coord_xy[1], "','", coord_xy[2], + "') or c('x','y')). Please supply them via the 'coord_df' argument.") + } + } + if (anyNA(st_data$coord_x) || anyNA(st_data$coord_y)) { + stop("Some ST cells/spots have missing coordinates after resolution; check coord_df / image coordinates.") + } DefaultAssay(st_data) <- st_assay DefaultAssay(sc_data) <- sc_assay @@ -43,8 +96,8 @@ traint <- function (st_data, sc_data, st_assay='Spatial', sc_assay='scint', norm sc_st_features <- union(sc_st_features, gene_kept) } - sc_st_features <- sc_st_features[(sc_st_features %in% rownames(st_data[[st_assay]]@data)) & - (sc_st_features %in% rownames(sc_data[[sc_assay]]@data))] + sc_st_features <- sc_st_features[(sc_st_features %in% rownames(GetAssayData(st_data, assay=st_assay, layer='data'))) & + (sc_st_features %in% rownames(GetAssayData(sc_data, assay=sc_assay, layer='data')))] cat('Using', length(sc_st_features), 'features for integration... \n') ### @@ -54,26 +107,29 @@ traint <- function (st_data, sc_data, st_assay='Spatial', sc_assay='scint', norm cat('Data transfering... \n') st_data_trans <- Seurat::TransferData(anchorset = sc_st_anchors, - refdata = GetAssayData(sc_data, assay = sc_assay, slot='data')[sc_st_features, ], weight.reduction = 'cca') + refdata = GetAssayData(sc_data, assay = sc_assay, layer='data')[sc_st_features, ], weight.reduction = 'cca') st_data@assays$transfer <- st_data_trans cat('Creating new Seurat object... \n') sc_st_meta <- dplyr::bind_rows(st_data@meta.data, sc_data@meta.data) - counts_temp <- cbind(data.frame(st_data[['transfer']]@data), data.frame(sc_data[[sc_assay]]@data[sc_st_features, ] %>% data.frame)) + counts_temp <- cbind(data.frame(GetAssayData(st_data, assay='transfer', layer='data')), + data.frame(GetAssayData(sc_data, assay=sc_assay, layer='data')[sc_st_features, ] %>% data.frame)) rownames(sc_st_meta) <- make.names(sc_st_meta$id) colnames(counts_temp) <- make.names(sc_st_meta$id) - sc_st_int <- CreateSeuratObject(counts = counts_temp, assay = 'traint', meta.data = sc_st_meta) - sc_st_int[['traint']]@data <- sc_st_int[['traint']]@counts - sc_st_int[['traint']]@counts <- matrix(NA, nrow = 0, ncol = 0) + sc_st_int <- CreateSeuratObject(counts = as.matrix(counts_temp), assay = 'traint', meta.data = sc_st_meta) + sc_st_int <- SetAssayData(sc_st_int, assay='traint', layer='data', + new.data = GetAssayData(sc_st_int, assay='traint', layer='counts')) cat('Scaling -> PCA -> UMAP... \n') sc_st_int <- ScaleData(sc_st_int, features = sc_st_features) %>% RunPCA(features = sc_st_features) sc_st_int <- RunUMAP(sc_st_int, dims = 1:30) sc_st_int@images <- st_data@images - sc_st_int@images[[1]]@coordinates <- data.frame(imagerow=sc_st_int@meta.data$coord_x, - imagecol=sc_st_int@meta.data$coord_y) %>% - set_rownames(rownames(sc_st_int@meta.data)) + if ('coordinates' %in% slotNames(sc_st_int@images[[1]])) { + sc_st_int@images[[1]]@coordinates <- data.frame(imagerow=sc_st_int@meta.data$coord_x, + imagecol=sc_st_int@meta.data$coord_y) %>% + set_rownames(rownames(sc_st_int@meta.data)) + } return (sc_st_int) } @@ -127,7 +183,7 @@ celltrek_repel <- function(celltrek_inp, repel_r=5, repel_iter=10) { #' @return A list of 1. celltrek_distance matrix; 2. trained random forest model (optional) #' #' @import dbscan -#' @importFrom akima interpp +#' @importFrom interp interpp #' @import magrittr #' @import dplyr #' @import randomForestSRC @@ -171,7 +227,7 @@ celltrek_dist <- function (st_sc_int, int_assay='traint', reduction='pca', intp }) %>% Reduce(rbind, .) st_intp_df <- apply(st_pca[, 1:nPCs], 2, function(col_x) { - akima::interpp(x=st_pca$coord_x, y=st_pca$coord_y, z=col_x, + interp::interpp(x=st_pca$coord_x, y=st_pca$coord_y, z=col_x, linear=intp_lin, xo=st_intp_df$coord_x, yo=st_intp_df$coord_y) %>% magrittr::extract2('z') }) %>% data.frame(., id='X', type='st_intp', st_intp_df) %>% na.omit @@ -284,27 +340,26 @@ celltrek_from_dist <- function (dist_mat, coord_df, dist_cut, top_spot=10, spot_ sc_coord_list <- celltrek_chart(dist_mat=dist_mat, coord_df=coord_df, dist_cut=dist_cut, top_spot=top_spot, spot_n=spot_n, repel_r=repel_r, repel_iter=repel_iter) sc_coord_raw <- sc_coord_list[[1]] sc_coord <- sc_coord_list[[2]] - sc_out <- CreateSeuratObject(counts=sc_data[[sc_assay]]@data[, sc_coord$id_raw] %>% set_colnames(sc_coord$id_new), + sc_out <- CreateSeuratObject(counts=as.matrix(GetAssayData(sc_data, assay=sc_assay, layer='data')[, sc_coord$id_raw]) %>% set_colnames(sc_coord$id_new), project='celltrek', assay=sc_assay, meta.data=sc_data@meta.data[sc_coord$id_raw, ] %>% dplyr::rename(id_raw=id) %>% mutate(id_new=sc_coord$id_new) %>% set_rownames(sc_coord$id_new)) sc_out@meta.data <- dplyr::left_join(sc_out@meta.data, sc_coord) %>% data.frame %>% set_rownames(sc_out$id_new) - sc_out[[sc_assay]]@data <- sc_out[[sc_assay]]@counts - sc_out[[sc_assay]]@counts <- matrix(nrow = 0, ncol = 0) + sc_out <- SetAssayData(sc_out, assay=sc_assay, layer='data', new.data=GetAssayData(sc_out, assay=sc_assay, layer='counts')) sc_coord_raw_df <- CreateDimReducObject(embeddings=sc_coord_raw %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord_raw$id_new) %>% as.matrix, - assay=sc_assay, key='celltrek_raw') + assay=sc_assay, key='celltrekraw_') sc_coord_dr <- CreateDimReducObject(embeddings=sc_coord %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord$id_new) %>% as.matrix, - assay=sc_assay, key='celltrek') + assay=sc_assay, key='celltrek_') sc_pca_dr <- CreateDimReducObject(embeddings=sc_data@reductions$pca@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='pca') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='pca_') sc_umap_dr <- CreateDimReducObject(embeddings=sc_data@reductions$umap@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='umap') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='umap_') sc_out@reductions$celltrek <- sc_coord_dr sc_out@reductions$celltrek_raw <- sc_coord_raw_df sc_out@reductions$pca <- sc_pca_dr @@ -312,7 +367,9 @@ celltrek_from_dist <- function (dist_mat, coord_df, dist_cut, top_spot=10, spot_ if (!is.null(st_data)) { sc_out@images <- st_data@images sc_out@images[[1]]@assay <- DefaultAssay(sc_out) - sc_out@images[[1]]@coordinates <- data.frame(imagerow=sc_coord$coord_x, imagecol=sc_coord$coord_y) %>% set_rownames(sc_coord$id_new) + if ('coordinates' %in% slotNames(sc_out@images[[1]])) { + sc_out@images[[1]]@coordinates <- data.frame(imagerow=sc_coord$coord_x, imagecol=sc_coord$coord_y) %>% set_rownames(sc_coord$id_new) + } sc_out@images[[1]]@scale.factors$spot_dis <- spot_dis } output <- list(celltrek=sc_out) @@ -361,7 +418,7 @@ celltrek <- function (st_sc_int, int_assay='traint', sc_data=NULL, sc_assay='RNA if (!is.null(sc_data)) { cat('sc data...') sc_data$id <- Seurat::Cells(sc_data) - sc_out <- CreateSeuratObject(counts=sc_data[[sc_assay]]@data[, sc_coord$id_raw] %>% set_colnames(sc_coord$id_new), + sc_out <- CreateSeuratObject(counts=as.matrix(GetAssayData(sc_data, assay=sc_assay, layer='data')[, sc_coord$id_raw]) %>% set_colnames(sc_coord$id_new), project='celltrek', assay=sc_assay, meta.data=sc_data@meta.data[sc_coord$id_raw, ] %>% dplyr::rename(id_raw=id) %>% @@ -369,36 +426,35 @@ celltrek <- function (st_sc_int, int_assay='traint', sc_data=NULL, sc_assay='RNA set_rownames(sc_coord$id_new)) sc_out@meta.data <- dplyr::left_join(sc_out@meta.data, sc_coord) %>% data.frame %>% set_rownames(sc_out$id_new) - sc_out[[sc_assay]]@data <- sc_out[[sc_assay]]@counts - sc_out[[sc_assay]]@counts <- matrix(nrow = 0, ncol = 0) + sc_out <- SetAssayData(sc_out, assay=sc_assay, layer='data', new.data=GetAssayData(sc_out, assay=sc_assay, layer='counts')) sc_coord_raw_df <- CreateDimReducObject(embeddings=sc_coord_raw %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord_raw$id_new) %>% as.matrix, - assay=sc_assay, key='celltrek_raw') + assay=sc_assay, key='celltrekraw_') sc_coord_dr <- CreateDimReducObject(embeddings=sc_coord %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord$id_new) %>% as.matrix, - assay=sc_assay, key='celltrek') + assay=sc_assay, key='celltrek_') sc_out@reductions$celltrek <- sc_coord_dr sc_out@reductions$celltrek_raw <- sc_coord_raw_df if ('pca' %in% names(sc_data@reductions)) { sc_pca_dr <- CreateDimReducObject(embeddings=sc_data@reductions$pca@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='pca') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='pca_') sc_out@reductions$pca <- sc_pca_dr } if ('umap' %in% names(sc_data@reductions)) { sc_umap_dr <- CreateDimReducObject(embeddings=sc_data@reductions$umap@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='umap') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='umap_') sc_out@reductions$umap <- sc_umap_dr } if ('tsne' %in% names(sc_data@reductions)) { sc_tsne_dr <- CreateDimReducObject(embeddings=sc_data@reductions$tsne@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='tsne') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=sc_assay, key='tsne_') sc_out@reductions$tsne <- sc_tsne_dr } } else { cat('no sc data...') - sc_out <- CreateSeuratObject(counts=st_sc_int[[int_assay]]@data[, sc_coord$id_raw] %>% + sc_out <- CreateSeuratObject(counts=as.matrix(GetAssayData(st_sc_int, assay=int_assay, layer='data')[, sc_coord$id_raw]) %>% set_colnames(sc_coord$id_new), project='celltrek', assay=int_assay, meta.data=st_sc_int@meta.data[sc_coord$id_raw, ] %>% @@ -408,39 +464,42 @@ celltrek <- function (st_sc_int, int_assay='traint', sc_data=NULL, sc_assay='RNA sc_out$coord_x <- sc_coord$coord_x[match(sc_coord$id_new, sc_out$id_new)] sc_out$coord_y <- sc_coord$coord_y[match(sc_coord$id_new, sc_out$id_new)] - sc_out[[int_assay]]@counts <- matrix(nrow = 0, ncol = 0) - sc_out[[int_assay]]@scale.data <- st_sc_int[[int_assay]]@scale.data[, sc_coord$id_raw] %>% set_colnames(sc_coord$id_new) + sc_out <- SetAssayData(sc_out, assay=int_assay, layer='data', new.data=GetAssayData(sc_out, assay=int_assay, layer='counts')) + sc_out <- SetAssayData(sc_out, assay=int_assay, layer='scale.data', + new.data=GetAssayData(st_sc_int, assay=int_assay, layer='scale.data')[, sc_coord$id_raw] %>% set_colnames(sc_coord$id_new)) sc_coord_raw_df <- CreateDimReducObject(embeddings=sc_coord_raw %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord_raw$id_new) %>% as.matrix, - assay=sc_assay, key='celltrek_raw') + assay=sc_assay, key='celltrekraw_') sc_coord_dr <- CreateDimReducObject(embeddings = sc_coord %>% dplyr::mutate(coord1=coord_y, coord2=max(coord_x)+min(coord_x)-coord_x) %>% dplyr::select(c(coord1, coord2)) %>% set_rownames(sc_coord$id_new) %>% as.matrix, - assay=int_assay, key='celltrek') + assay=int_assay, key='celltrek_') sc_out@reductions$celltrek <- sc_coord_dr sc_out@reductions$celltrek_raw <- sc_coord_raw_df if ('pca' %in% names(st_sc_int@reductions)) { sc_pca_dr <- CreateDimReducObject(embeddings=st_sc_int@reductions$pca@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='pca') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='pca_') sc_out@reductions$pca <- sc_pca_dr } if ('umap' %in% names(st_sc_int@reductions)) { sc_umap_dr <- CreateDimReducObject(embeddings=st_sc_int@reductions$umap@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='umap') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='umap_') sc_out@reductions$umap <- sc_umap_dr } if ('tsne' %in% names(st_sc_int@reductions)) { sc_tsne_dr <- CreateDimReducObject(embeddings=st_sc_int@reductions$tsne@cell.embeddings[sc_coord$id_raw, ] %>% - set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='tsne') + set_rownames(sc_coord$id_new) %>% as.matrix, assay=int_assay, key='tsne_') sc_out@reductions$tsne <- sc_tsne_dr } } sc_out@images <- st_sc_int@images sc_out@images[[1]]@assay <- DefaultAssay(sc_out) - sc_out@images[[1]]@coordinates <- data.frame(imagerow=sc_coord$coord_x, imagecol=sc_coord$coord_y) %>% set_rownames(sc_coord$id_new) + if ('coordinates' %in% slotNames(sc_out@images[[1]])) { + sc_out@images[[1]]@coordinates <- data.frame(imagerow=sc_coord$coord_x, imagecol=sc_coord$coord_y) %>% set_rownames(sc_coord$id_new) + } sc_out@images[[1]]@scale.factors$spot_dis <- dist_res$spot_d sc_out@images[[1]]@scale.factors$spot_dis_intp <- spot_dis_intp diff --git a/R/scoexp.R b/R/scoexp.R index 91fb05b..8c82c30 100644 --- a/R/scoexp.R +++ b/R/scoexp.R @@ -225,18 +225,18 @@ scoexp <- function(celltrek_inp, sigm=NULL, assay='RNA', gene_select=NULL, zero_ if (is.null(sigm)) sigm <- celltrek_inp@images[[1]]@scale.factors$spot_dis if (is.null(gene_select)) { cat('gene filtering...\n') - feature_nz <- apply(celltrek_inp[[assay]]@data, 1, function(x) mean(x!=0)*100) + feature_nz <- apply(GetAssayData(celltrek_inp, assay=assay, layer='data'), 1, function(x) mean(x!=0)*100) features <- names(feature_nz)[feature_nz > zero_cutoff] cat(length(features), 'features after filtering...\n') } else if (length(gene_select) > 1) { - features <- intersect(gene_select, rownames(celltrek_inp[[assay]]@data)) + features <- intersect(gene_select, rownames(GetAssayData(celltrek_inp, assay=assay, layer='data'))) if (length(features)==0) stop('No genes in gene_select detected') } celltrek_inp <- Seurat::ScaleData(celltrek_inp, features=features) res <- list(gs=c(), cc=c(), rbfk=c(), wcor=c()) dist_mat <- dist(celltrek_inp@meta.data[, c('coord_x', 'coord_y')]) %>% as.matrix kern_mat <- rbfk(dist_mat, sigm=sigm, zero_diag=F) - expr_mat <- t(as.matrix(celltrek_inp[[assay]]@scale.data)) + expr_mat <- t(as.matrix(GetAssayData(celltrek_inp, assay=assay, layer='scale.data'))) cat('Calculating spatial-weighted cross-correlation...\n') wcor_mat <- wcor(X=expr_mat, W=kern_mat, method=cor_method) diff --git a/README.md b/README.md index 2f1087b..27cddba 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ In this tutorial, we will demonstrate the cell charting workflow based on the mo library(devtools) install_github("navinlabcode/CellTrek") ``` + +> **Seurat v5 compatibility note.** This version has been updated to run on Seurat v5 (tested with Seurat 5.3.0 / SeuratObject 5.2.0 on R 4.3). Internally, direct assay-slot access (`obj[[assay]]@data`/`@counts`/`@scale.data`) was replaced with the version-agnostic `GetAssayData()`/`SetAssayData()` accessors, and dimensional-reduction keys were made v5-compliant. The interpolation step now depends on the `interp` package (a drop-in replacement for `akima`, which is not available on all platforms). The tutorial code below has likewise been updated for v5 (e.g. `HVFInfo()` instead of `@meta.features`). ## 2. Loading the packages and datasets (scRNA-seq and ST data) We start by loading the packages needed for the analyses. Please install them if you haven't. ``` r @@ -124,7 +126,6 @@ Based on the CellTrek result, we can further investigate the co-expression patte L5 IT cells first are extracted from the charting result. ``` r brain_celltrek_l5 <- subset(brain_celltrek, subset=cell_type=='L5 IT') -brain_celltrek_l5@assays$RNA@scale.data <- matrix(NA, 1, 1) brain_celltrek_l5$cluster <- gsub('L5 IT VISp ', '', brain_celltrek_l5$cluster) DimPlot(brain_celltrek_l5, group.by = 'cluster') ``` @@ -132,12 +133,12 @@ DimPlot(brain_celltrek_l5, group.by = 'cluster') We select top 2000 variable genes (exclude mitochondrial, ribosomal and high-zero genes) ``` r brain_celltrek_l5 <- FindVariableFeatures(brain_celltrek_l5) -vst_df <- brain_celltrek_l5@assays$RNA@meta.features %>% data.frame %>% mutate(id=rownames(.)) -nz_test <- apply(as.matrix(brain_celltrek_l5[['RNA']]@data), 1, function(x) mean(x!=0)*100) +vst_df <- HVFInfo(brain_celltrek_l5) %>% data.frame %>% mutate(id=rownames(.)) +nz_test <- apply(as.matrix(GetAssayData(brain_celltrek_l5, assay='RNA', layer='data')), 1, function(x) mean(x!=0)*100) hz_gene <- names(nz_test)[nz_test<20] mt_gene <- grep('^Mt-', rownames(brain_celltrek_l5), value=T) rp_gene <- grep('^Rpl|^Rps', rownames(brain_celltrek_l5), value=T) -vst_df <- vst_df %>% dplyr::filter(!(id %in% c(mt_gene, rp_gene, hz_gene))) %>% arrange(., -vst.variance.standardized) +vst_df <- vst_df %>% dplyr::filter(!(id %in% c(mt_gene, rp_gene, hz_gene))) %>% arrange(., -variance.standardized) feature_temp <- vst_df$id[1:2000] ``` We use scoexp to do the spatial-weighted gene co-expression analysis. diff --git a/man/traint.Rd b/man/traint.Rd index 15132e2..f00a2c9 100644 --- a/man/traint.Rd +++ b/man/traint.Rd @@ -13,6 +13,7 @@ traint( nfeatures = 2000, cell_names = "cell_names", coord_xy = c("imagerow", "imagecol"), + coord_df = NULL, gene_kept = NULL, ... ) @@ -34,6 +35,11 @@ traint( \item{coord_xy}{coordinates column names in ST images slot} +\item{coord_df}{optional data frame/matrix of ST spatial coordinates to use directly, +bypassing coordinate auto-detection from the image slot. Rownames must cover all ST +cells/spots. If it contains the columns named in \code{coord_xy} those are used, +otherwise the first two columns are taken as (coord_x, coord_y).} + \item{gene_kept}{selected genes to be kept during integration} } \value{ diff --git a/tests_local/figs/1_celltrek_spatial.png b/tests_local/figs/1_celltrek_spatial.png new file mode 100644 index 0000000..6ba4d8f Binary files /dev/null and b/tests_local/figs/1_celltrek_spatial.png differ diff --git a/tests_local/figs/2_scoloc_KL_mst.png b/tests_local/figs/2_scoloc_KL_mst.png new file mode 100644 index 0000000..f760a36 Binary files /dev/null and b/tests_local/figs/2_scoloc_KL_mst.png differ diff --git a/tests_local/figs/3_scoexp_heatmap.png b/tests_local/figs/3_scoexp_heatmap.png new file mode 100644 index 0000000..af92649 Binary files /dev/null and b/tests_local/figs/3_scoexp_heatmap.png differ diff --git a/tests_local/figs/4_dataset2_spatial.png b/tests_local/figs/4_dataset2_spatial.png new file mode 100644 index 0000000..9ff7f0b Binary files /dev/null and b/tests_local/figs/4_dataset2_spatial.png differ diff --git a/tests_local/make_figs.R b/tests_local/make_figs.R new file mode 100644 index 0000000..e5c1309 --- /dev/null +++ b/tests_local/make_figs.R @@ -0,0 +1,60 @@ +suppressPackageStartupMessages({ + library(CellTrek); library(Seurat); library(dplyr) + library(ggplot2); library(pheatmap); library(viridis) +}) +set.seed(1) +dir.create('tests_local/figs', showWarnings = FALSE) +ct <- readRDS('tests_local/brain_celltrek_cache.rds') + +## ---- Fig 1: spatial charting (charted single cells on tissue coords) ---- +df <- ct@meta.data +df$cell_type <- factor(df$cell_type, levels = sort(unique(df$cell_type))) +p1 <- ggplot(df, aes(coord_y, max(coord_x)+min(coord_x)-coord_x, color = cell_type)) + + geom_point(size = 0.7, alpha = 0.85) + + coord_fixed() + theme_void() + + guides(color = guide_legend(override.aes = list(size = 3))) + + labs(title = paste0('CellTrek charting: ', nrow(df), ' single cells mapped to tissue'), + color = 'cell type') +ggsave('tests_local/figs/1_celltrek_spatial.png', p1, width = 9, height = 7, dpi = 130) +cat('fig1 done\n') + +## ---- Fig 2: SColoc (KL) consensus co-localization matrix ---- +glut <- c('L2/3 IT','L4','L5 IT','L5 PT','NP','L6 IT','L6 CT','L6b') +g <- subset(ct, subset = cell_type %in% glut) +sg <- CellTrek::scoloc(g, col_cell = 'cell_type', use_method = 'KL', eps = 1e-50, boot_n = 20) +mst <- as.matrix(sg$mst_cons) +labs <- glut; names(labs) <- make.names(glut) +rownames(mst) <- colnames(mst) <- labs[rownames(mst)] +mst_sym <- mst + t(mst) +png('tests_local/figs/2_scoloc_KL_mst.png', width = 900, height = 800, res = 130) +pheatmap(mst_sym, cluster_rows = TRUE, cluster_cols = TRUE, + color = viridis(100), display_numbers = TRUE, number_format = '%.2f', + main = 'SColoc (KL): MST consensus co-localization\n(glutamatergic neurons, 20 bootstraps)') +dev.off() +cat('fig2 done\n') + +## ---- Fig 3: SCoexp (cc) spatial co-expression heatmap on L5 IT ---- +l5 <- subset(ct, subset = cell_type == 'L5 IT') +l5 <- FindVariableFeatures(l5, verbose = FALSE) +vst_df <- HVFInfo(l5) %>% data.frame %>% mutate(id = rownames(.)) +nz <- apply(as.matrix(GetAssayData(l5, assay='RNA', layer='data')), 1, function(x) mean(x!=0)*100) +hz <- names(nz)[nz < 20] +mt <- grep('^Mt-', rownames(l5), value = TRUE) +rp <- grep('^Rpl|^Rps', rownames(l5), value = TRUE) +vst_df <- vst_df %>% filter(!(id %in% c(mt, rp, hz))) %>% arrange(-variance.standardized) +feats <- head(na.omit(vst_df$id), 300) +res <- CellTrek::scoexp(celltrek_inp = l5, assay = 'RNA', approach = 'cc', + gene_select = feats, sigm = 140, avg_cor_min = .3, + zero_cutoff = 3, min_gen = 15, max_gen = 200, maxK = 6, k = 6, reps = 20) +k_df <- do.call(rbind, lapply(seq_along(res$gs), function(i) + data.frame(gene = res$gs[[i]], Module = paste0('K', i)))) +rownames(k_df) <- k_df$gene; k_df$gene <- NULL +gord <- rownames(k_df) +png('tests_local/figs/3_scoexp_heatmap.png', width = 850, height = 800, res = 130) +pheatmap(res$wcor[gord, gord], clustering_method = 'ward.D2', + annotation_row = k_df, show_rownames = FALSE, show_colnames = FALSE, + treeheight_row = 12, treeheight_col = 12, fontsize = 8, + color = viridis(10), main = 'SCoexp: L5 IT spatial co-expression modules') +dev.off() +cat('fig3 done | modules:', length(res$gs), '| sizes:', paste(sapply(res$gs, length), collapse=','), '\n') +cat('ALL FIGS DONE\n') diff --git a/tests_local/regress_coords.R b/tests_local/regress_coords.R new file mode 100644 index 0000000..4b3796e --- /dev/null +++ b/tests_local/regress_coords.R @@ -0,0 +1,28 @@ +## Regression check after the coordinate-scale fix: traint must resolve coords for +## both VisiumV1 (brain_st_cortex, via @coordinates) and VisiumV2 (stxBrain, via GTC x/y). +suppressPackageStartupMessages({library(CellTrek); library(Seurat); library(dplyr)}) +set.seed(1) +mini_sc <- function(sc, per=25){ md<-sc@meta.data; k<-unlist(lapply(split(rownames(md), md$cell_type), function(x) sample(x, min(length(x),per)))); sc[,k] } + +## ---- VisiumV1 ---- +st1 <- readRDS('data/brain_st_cortex.rds'); sc1 <- readRDS('data/brain_sc.rds') +st1 <- RenameCells(st1, new.names=make.names(Cells(st1))); sc1 <- RenameCells(sc1, new.names=make.names(Cells(sc1))) +st1 <- st1[, sample(Cells(st1), 300)]; sc1 <- mini_sc(sc1, 25) +t1 <- CellTrek::traint(st1, sc1, st_assay='Spatial', sc_assay='RNA', cell_names='cell_type') +c1 <- na.omit(t1@meta.data[t1$type=='st', c('coord_x','coord_y')]) +cat('VisiumV1: image', class(st1@images[[1]]), '| coord range', paste(round(range(c1$coord_x)),collapse='-'), + '| spot_dis', round(median(dbscan::kNN(c1,k=6)$dist)), '| st coords set', all(!is.na(t1$coord_x[t1$type=='st'])), '\n') + +## ---- VisiumV2 ---- +e <- new.env(); load('data2/anterior1.rda', envir=e); st2 <- UpdateSeuratObject(get('anterior1', envir=e)) +st2 <- NormalizeData(st2, verbose=FALSE) %>% FindVariableFeatures(nfeatures=2000, verbose=FALSE) +st2 <- RenameCells(st2, new.names=make.names(Cells(st2))) +sc2 <- readRDS('data2/allen_cortex.rds'); sc2$cell_type <- as.character(sc2$subclass) +sc2 <- mini_sc(sc2, 25) +sc2 <- NormalizeData(sc2,verbose=FALSE) %>% FindVariableFeatures(nfeatures=2000,verbose=FALSE) %>% ScaleData(verbose=FALSE) %>% RunPCA(npcs=30,verbose=FALSE) %>% RunUMAP(dims=1:30,verbose=FALSE) +sc2 <- RenameCells(sc2, new.names=make.names(Cells(sc2))) +t2 <- CellTrek::traint(st2, sc2, st_assay='Spatial', sc_assay='RNA', cell_names='cell_type') +c2 <- na.omit(t2@meta.data[t2$type=='st', c('coord_x','coord_y')]) +cat('VisiumV2: image', class(st2@images[[1]]), '| coord range', paste(round(range(c2$coord_x)),collapse='-'), + '| spot_dis', round(median(dbscan::kNN(c2,k=6)$dist)), '| st coords set', all(!is.na(t2$coord_x[t2$type=='st'])), '\n') +cat('== REGRESS OK ==\n') diff --git a/tests_local/run_dataset2.R b/tests_local/run_dataset2.R new file mode 100644 index 0000000..8c96e5f --- /dev/null +++ b/tests_local/run_dataset2.R @@ -0,0 +1,69 @@ +## Second paired dataset test on Seurat v5: +## ST = stxBrain anterior1 (10x Visium, updates to Assay5 + VisiumV2 image) +## scRNA = allen_cortex reference (subclass labels) +## This exercises the VisiumV2 (v5 image class) code path, which the first +## dataset (brain_st_cortex, VisiumV1) did not. +suppressPackageStartupMessages({ + library(CellTrek); library(Seurat); library(dplyr); library(magrittr) + library(ggplot2) +}) +set.seed(1) +cat('Seurat', as.character(packageVersion('Seurat')), '\n') +dir.create('tests_local/figs', showWarnings = FALSE) + +## ---- ST: stxBrain anterior1 ---- +e <- new.env(); load('data2/anterior1.rda', envir = e) +st <- UpdateSeuratObject(get('anterior1', envir = e)) +cat('ST anterior1:', ncol(st), 'spots | image class:', class(st@images[[1]]), '\n') +st <- NormalizeData(st, verbose = FALSE) %>% FindVariableFeatures(nfeatures = 2000, verbose = FALSE) +st <- RenameCells(st, new.names = make.names(Cells(st))) + +## ---- scRNA: allen_cortex, subsample + preprocess ---- +sc <- readRDS('data2/allen_cortex.rds') +sc$cell_type <- as.character(sc$subclass) +set.seed(42) +md <- sc@meta.data +keep <- unlist(lapply(split(rownames(md), md$cell_type), function(x) sample(x, min(length(x), 70)))) +sc <- subset(sc, cells = keep) +sc <- NormalizeData(sc, verbose = FALSE) %>% + FindVariableFeatures(nfeatures = 2000, verbose = FALSE) %>% + ScaleData(verbose = FALSE) %>% RunPCA(npcs = 30, verbose = FALSE) %>% + RunUMAP(dims = 1:30, verbose = FALSE) +sc <- RenameCells(sc, new.names = make.names(Cells(sc))) +cat('scRNA allen:', ncol(sc), 'cells |', length(unique(sc$cell_type)), 'cell types\n') + +## ---- Step 1: traint ---- +cat('\n==== traint (VisiumV2 ST) ====\n') +tr <- CellTrek::traint(st_data = st, sc_data = sc, st_assay = 'Spatial', + sc_assay = 'RNA', cell_names = 'cell_type') +cat('traint:', ncol(tr), 'cells | coord_x set:', + all(!is.na(tr$coord_x[tr$type == 'st'])), '\n') + +## ---- Step 2: celltrek ---- +cat('\n==== celltrek ====\n') +res <- CellTrek::celltrek(st_sc_int = tr, int_assay = 'traint', sc_data = sc, + sc_assay = 'RNA', reduction = 'pca', intp = TRUE, + intp_pnt = 5000, intp_lin = FALSE, nPCs = 30, ntree = 1000, + dist_thresh = 0.55, top_spot = 5, spot_n = 5, + repel_r = 20, repel_iter = 20, keep_model = FALSE)$celltrek +cat('celltrek:', ncol(res), 'charted cells | coords in meta:', + all(c('coord_x','coord_y') %in% colnames(res@meta.data)), '\n') +saveRDS(res, 'tests_local/dataset2_celltrek_cache.rds') + +## ---- Fig: spatial charting ---- +df <- res@meta.data +df$cell_type <- factor(df$cell_type, levels = sort(unique(df$cell_type))) +p <- ggplot(df, aes(coord_y, max(coord_x)+min(coord_x)-coord_x, color = cell_type)) + + geom_point(size = 0.6, alpha = 0.85) + coord_fixed() + theme_void() + + guides(color = guide_legend(override.aes = list(size = 3))) + + labs(title = paste0('Dataset 2 (stxBrain anterior + allen_cortex): ', + nrow(df), ' cells charted'), color = 'subclass') +ggsave('tests_local/figs/4_dataset2_spatial.png', p, width = 9, height = 7, dpi = 130) +cat('spatial fig saved\n') + +## ---- Step 3: scoloc (DT, no external dep) ---- +cat('\n==== scoloc (DT) ====\n') +sg <- CellTrek::scoloc(res, col_cell = 'cell_type', use_method = 'DT', boot_n = 10) +cat('scoloc mst_cons:', paste(dim(sg$mst_cons), collapse = 'x'), '\n') + +cat('\n==== DATASET 2 ALL STAGES COMPLETED ====\n') diff --git a/tests_local/run_pipeline.R b/tests_local/run_pipeline.R new file mode 100644 index 0000000..d514d37 --- /dev/null +++ b/tests_local/run_pipeline.R @@ -0,0 +1,84 @@ +## Local functional test of CellTrek on Seurat v5 +## Runs the README workflow on the mouse brain data (subsampled for speed). +suppressPackageStartupMessages({ + library(CellTrek) + library(Seurat) + library(dplyr) + library(magrittr) +}) +set.seed(1) +cat('Seurat', as.character(packageVersion('Seurat')), '\n') + +data_dir <- 'data' +brain_st <- readRDS(file.path(data_dir, 'brain_st_cortex.rds')) +brain_sc <- readRDS(file.path(data_dir, 'brain_sc.rds')) + +## syntactically valid names +brain_st <- RenameCells(brain_st, new.names = make.names(Cells(brain_st))) +brain_sc <- RenameCells(brain_sc, new.names = make.names(Cells(brain_sc))) + +## subsample SC for speed, keeping every cell type +set.seed(42) +md <- brain_sc@meta.data +keep <- unlist(lapply(split(rownames(md), md$cell_type), function(x) sample(x, min(length(x), 60)))) +brain_sc <- subset(brain_sc, cells = keep) +cat('SC cells after subsample:', ncol(brain_sc), '| cell types:', length(unique(brain_sc$cell_type)), '\n') +cat('ST spots:', ncol(brain_st), '\n') + +cache <- 'tests_local/brain_celltrek_cache.rds' +if (file.exists(cache)) { + cat('\n==== loading cached celltrek object ====\n') + brain_celltrek <- readRDS(cache) +} else { +## ---- Step 1: traint (co-embedding) ---- +cat('\n==== traint ====\n') +brain_traint <- CellTrek::traint(st_data = brain_st, sc_data = brain_sc, + sc_assay = 'RNA', cell_names = 'cell_type') +cat('traint object:', ncol(brain_traint), 'cells |', + 'reductions:', paste(names(brain_traint@reductions), collapse=','), '\n') +stopifnot('pca' %in% names(brain_traint@reductions)) + +## ---- Step 2: celltrek (charting) ---- +cat('\n==== celltrek ====\n') +brain_celltrek <- CellTrek::celltrek(st_sc_int = brain_traint, int_assay = 'traint', + sc_data = brain_sc, sc_assay = 'RNA', + reduction = 'pca', intp = TRUE, intp_pnt = 5000, + intp_lin = FALSE, nPCs = 30, ntree = 1000, + dist_thresh = 0.55, top_spot = 5, spot_n = 5, + repel_r = 20, repel_iter = 20, keep_model = TRUE)$celltrek +saveRDS(brain_celltrek, cache) +} +cat('celltrek object:', ncol(brain_celltrek), 'charted cells\n') +cat('coord range x:', paste(round(range(brain_celltrek$coord_x)), collapse='-'), + '| y:', paste(round(range(brain_celltrek$coord_y)), collapse='-'), '\n') +stopifnot(all(c('coord_x','coord_y') %in% colnames(brain_celltrek@meta.data))) +stopifnot('celltrek' %in% names(brain_celltrek@reductions)) + +## ---- Step 3: SColoc (KL) ---- +cat('\n==== scoloc (KL) ====\n') +glut_cell <- c('L2/3 IT','L4','L5 IT','L5 PT','NP','L6 IT','L6 CT','L6b') +glut_cell <- glut_cell[glut_cell %in% brain_celltrek$cell_type] +brain_glut <- subset(brain_celltrek, subset = cell_type %in% glut_cell) +cat('glut cells:', ncol(brain_glut), '\n') +brain_sgraph_KL <- CellTrek::scoloc(brain_glut, col_cell = 'cell_type', + use_method = 'KL', eps = 1e-50, boot_n = 10) +cat('\nscoloc mst_cons dim:', paste(dim(brain_sgraph_KL$mst_cons), collapse='x'), '\n') + +## ---- Step 4: SCoexp (cc) on L5 IT ---- +cat('\n==== scoexp (cc) ====\n') +brain_l5 <- subset(brain_celltrek, subset = cell_type == 'L5 IT') +cat('L5 IT cells:', ncol(brain_l5), '\n') +if (ncol(brain_l5) >= 30) { + brain_l5 <- FindVariableFeatures(brain_l5, verbose = FALSE) + feats <- VariableFeatures(brain_l5) + feats <- head(feats, 300) + res_cc <- CellTrek::scoexp(celltrek_inp = brain_l5, assay = 'RNA', approach = 'cc', + gene_select = feats, sigm = 140, avg_cor_min = .3, + zero_cutoff = 3, min_gen = 10, max_gen = 200, maxK = 6, k = 6, reps = 10) + cat('scoexp modules found:', length(res_cc$gs), '\n') + cat('module sizes:', paste(sapply(res_cc$gs, length), collapse=','), '\n') +} else { + cat('skip scoexp: too few L5 IT cells in subsample\n') +} + +cat('\n==== ALL STAGES COMPLETED ====\n') diff --git a/tests_local/test_coord_df_param.R b/tests_local/test_coord_df_param.R new file mode 100644 index 0000000..0f6dec0 --- /dev/null +++ b/tests_local/test_coord_df_param.R @@ -0,0 +1,39 @@ +## Lightweight validation of the new traint(coord_df=) parameter on a normal, +## fully-operable object (brain_st_cortex, VisiumV1). Small subsample for speed. +suppressPackageStartupMessages({library(CellTrek); library(Seurat); library(dplyr)}) +set.seed(1) +st <- readRDS('data/brain_st_cortex.rds') +sc <- readRDS('data/brain_sc.rds') +st <- RenameCells(st, new.names = make.names(Cells(st))) +sc <- RenameCells(sc, new.names = make.names(Cells(sc))) +## subsample for speed +st <- st[, sample(Cells(st), 250)] +set.seed(2); md <- sc@meta.data +keep <- unlist(lapply(split(rownames(md), md$cell_type), function(x) sample(x, min(length(x), 25)))) +sc <- sc[, keep] + +## coord_df with NON-standard column names (col1 -> coord_x, col2 -> coord_y) +tc <- GetTissueCoordinates(st) +my_coord <- data.frame(rowpos = tc[, 'imagerow'], colpos = tc[, 'imagecol']) +rownames(my_coord) <- Cells(st) + +cat('== Test 1: traint WITH coord_df (non-standard col names) ==\n') +tr <- CellTrek::traint(st, sc, st_assay = 'Spatial', sc_assay = 'RNA', + cell_names = 'cell_type', coord_df = my_coord) +st_cells <- tr$id[tr$type == 'st'] +ok <- isTRUE(all.equal(unname(tr$coord_x[tr$type=='st']), + unname(my_coord[st_cells, 1]), tolerance = 1e-6)) +cat('coord_x matches coord_df col1:', ok, '\n') +cat('traint cells:', ncol(tr), '\n') + +cat('\n== Test 2: bad rownames -> expect graceful error ==\n') +bad <- my_coord; rownames(bad) <- paste0('X', seq_len(nrow(bad))) +r2 <- tryCatch({ CellTrek::traint(st, sc, sc_assay='RNA', cell_names='cell_type', coord_df=bad); 'NO ERROR (BAD)'}, + error=function(e) paste0('ERRORED as expected: ', conditionMessage(e))) +cat(r2, '\n') + +cat('\n== Test 3: auto-detect still works WITHOUT coord_df ==\n') +tr2 <- CellTrek::traint(st, sc, st_assay='Spatial', sc_assay='RNA', cell_names='cell_type') +cat('auto-detect coord_x set:', all(!is.na(tr2$coord_x[tr2$type=='st'])), '\n') + +cat('\n== COORD_DF PARAM TEST DONE ==\n')