Skip to content

Commit b35a2e6

Browse files
authored
Fixes for vars.h5 and seurat versions (#37)
* seurat versions failsafe * fix vars.h5 attr * Update artifacts.R * Update DESCRIPTION * tests * Increment version number to 0.3.1 * Update DESCRIPTION * Update DESCRIPTION
1 parent fbe3843 commit b35a2e6

7 files changed

Lines changed: 365 additions & 19 deletions

File tree

DESCRIPTION

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: CyteTypeR
22
Title: CyteType for R
3-
Version: 0.3.0
3+
Version: 0.3.1
44
Description: CyteTypeR is the R version of CyteType python package.
55
Authors@R:
66
person("Nygen Analytics AB", , ,"contact@nygen.io", role = c("aut", "cre"))
@@ -28,7 +28,10 @@ Suggests:
2828
duckdb,
2929
furrr,
3030
knitr,
31+
rhdf5filters,
3132
rmarkdown,
3233
testthat (>= 3.0.0)
34+
Remotes:
35+
bioc::rhdf5
3336
Config/testthat/edition: 3
3437
VignetteBuilder: knitr

R/artifacts.R

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@
4747
if (is.logical(vec)) {
4848
storage.mode(vec) <- "integer"
4949
if (any(is.na(vec))) vec[is.na(vec)] <- -1L
50-
rhdf5::h5writeDataset(vec, h5loc = fid, name = col_path, level = 5L)
50+
rhdf5::h5writeDataset(vec, h5loc = fid, name = col_path)
5151
} else if (is.numeric(vec)) {
52-
rhdf5::h5writeDataset(as.double(vec), h5loc = fid, name = col_path, level = 5L)
52+
rhdf5::h5writeDataset(as.double(vec), h5loc = fid, name = col_path)
5353
} else {
54-
rhdf5::h5writeDataset(.as_string_values(vec), h5loc = fid, name = col_path, level = 5L)
54+
rhdf5::h5writeDataset(.as_string_values(vec), h5loc = fid, name = col_path)
5555
}
5656
}
5757
invisible(NULL)
@@ -63,6 +63,10 @@
6363
n_obs <- nrow(m)
6464
n_vars <- ncol(m)
6565

66+
if (!requireNamespace("rhdf5filters", quietly = TRUE)) {
67+
stop("Package 'rhdf5filters' is required to write vars.h5 with LZ4 compression.")
68+
}
69+
6670
if (is.null(col_batch)) {
6771
col_batch <- max(1L, as.integer(100000000 / max(n_obs, 1)))
6872
}
@@ -77,8 +81,8 @@
7781
on.exit(rhdf5::H5Fclose(fid), add = TRUE)
7882

7983
rhdf5::h5createGroup(fid, "vars")
80-
rhdf5::h5writeAttribute(as.integer(n_obs), h5obj = fid, name = "vars/n_obs")
81-
rhdf5::h5writeAttribute(as.integer(n_vars), h5obj = fid, name = "vars/n_vars")
84+
rhdf5::h5writeAttribute(as.integer(n_obs), h5obj = out_file, name = "n_obs", h5loc = "vars")
85+
rhdf5::h5writeAttribute(as.integer(n_vars), h5obj = out_file, name = "n_vars", h5loc = "vars")
8286

8387
# Create extensible datasets (equivalent to maxshape=(None,) in h5py)
8488
max_nnz <- n_obs * n_vars # upper bound
@@ -88,15 +92,15 @@
8892
maxdims = rhdf5::H5Sunlimited(),
8993
chunk = chunk_size,
9094
H5type = "H5T_NATIVE_INT32",
91-
level = 0L # LZ4 not directly available, use level for deflate or 0 for none
95+
filter = "BLOSC_LZ4"
9296
)
9397
rhdf5::h5createDataset(
9498
fid, "vars/data",
9599
dims = 0L,
96100
maxdims = rhdf5::H5Sunlimited(),
97101
chunk = chunk_size,
98102
H5type = "H5T_NATIVE_FLOAT",
99-
level = 5L
103+
filter = "BLOSC_LZ4"
100104
)
101105

102106
indptr <- 0L
@@ -132,7 +136,14 @@
132136
indptr <- c(indptr, new_indptr)
133137
}
134138

135-
rhdf5::h5writeDataset(as.integer(indptr), h5loc = fid, name = "vars/indptr", level = 5L)
139+
rhdf5::h5createDataset(
140+
fid, "vars/indptr",
141+
dims = length(indptr),
142+
H5type = "H5T_NATIVE_INT32",
143+
chunk = min(chunk_size, length(indptr)),
144+
filter = "BLOSC_LZ4"
145+
)
146+
rhdf5::h5writeDataset(as.integer(indptr), h5loc = fid, name = "vars/indptr")
136147

137148
if (!is.null(feature_df)) {
138149
.write_var_metadata(fid, n_cols = n_vars, feature_df = feature_df, feature_names = feature_names)

R/cytetype.R

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,20 @@ CyteTypeR <- function(obj,
277277
tryCatch({
278278
tryCatch({
279279
log_info("Building vars.h5 from normalized counts (cells x features)...")
280-
# GetAssayData returns genes x cells; API expects cells x genes (n_obs x n_vars).
281-
mat <- Matrix::t(Seurat::GetAssayData(obj, layer = "data"))
282-
283-
default_assay <- Seurat::DefaultAssay(obj)
280+
default_assay <- .resolve_seurat_assay_rna(obj)
281+
# Seurat stores expression as features x cells; API expects cells x features (n_obs x n_vars).
282+
expr_mat <- tryCatch(
283+
Seurat::GetAssayData(obj, assay = default_assay, layer = "data"),
284+
error = function(e) Seurat::GetAssayData(obj, assay = default_assay, slot = "data")
285+
)
286+
mat <- Matrix::t(expr_mat)
284287

285288
feature_df <- tryCatch(
286289
as.data.frame(Seurat::GetAssay(obj, default_assay)@meta.features),
287-
error = function(e) NULL
290+
error = function(e) tryCatch(
291+
as.data.frame(Seurat::GetAssay(obj, default_assay)@meta.data),
292+
error = function(e2) NULL
293+
)
288294
)
289295
feature_names <- tryCatch(rownames(obj), error = function(e) NULL)
290296
.save_vars_h5(vars_h5_path, mat, feature_df = feature_df, feature_names = feature_names)

R/seurat_helpers.R

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66
#' @importFrom tibble tibble
77
#' @importFrom Matrix rowSums
88
#'
9+
# Resolve/validate Seurat RNA assay for RNA-seq workflows (strict).
10+
.resolve_seurat_assay_rna <- function(seurat_obj) {
11+
assays <- tryCatch(names(seurat_obj@assays), error = function(e) character(0))
12+
if (length(assays) == 0) stop("No assays found in Seurat object.")
13+
if (!("RNA" %in% assays)) {
14+
stop(
15+
"This workflow requires an assay named 'RNA'. Available assays: ",
16+
paste0(assays, collapse = ", "),
17+
"."
18+
)
19+
}
20+
21+
default_assay <- tryCatch(Seurat::DefaultAssay(seurat_obj), error = function(e) NULL)
22+
if (!is.null(default_assay) && nzchar(as.character(default_assay)) && default_assay != "RNA") {
23+
warning(
24+
"DefaultAssay(seurat_obj) is '", as.character(default_assay),
25+
"'. Using assay='RNA' instead."
26+
)
27+
}
28+
29+
"RNA"
30+
}
31+
932
# Calculate Expression Percentages by Cluster
1033
.calculate_pcent <- function(
1134
seurat_obj,
@@ -14,6 +37,7 @@
1437
batch_size = 2000
1538
){
1639
gene_names <- rownames(seurat_obj)
40+
assay <- .resolve_seurat_assay_rna(seurat_obj)
1741
clusters <- seurat_obj[[group_key]][[group_key]]
1842
unique_clusters <- unique(clusters)
1943

@@ -31,7 +55,11 @@
3155

3256

3357
batch_genes <- gene_names[start_idx:end_idx]
34-
batch_expr_matrix <- GetAssayData(seurat_obj, layer = "data")[batch_genes, , drop = FALSE]
58+
batch_expr_full <- tryCatch(
59+
Seurat::GetAssayData(seurat_obj, assay = assay, layer = "data"),
60+
error = function(e) Seurat::GetAssayData(seurat_obj, assay = assay, slot = "data")
61+
)
62+
batch_expr_matrix <- batch_expr_full[batch_genes, , drop = FALSE]
3563

3664

3765
for (cluster in unique_clusters) {
@@ -142,7 +170,13 @@
142170
.validate_gene_symbols <- function(seurat_obj, gene_symbols){
143171
gene_values <- tryCatch({
144172

145-
meta_features <- seurat_obj[["RNA"]]@meta.features
173+
assay_name <- .resolve_seurat_assay_rna(seurat_obj)
174+
assay_obj <- tryCatch(seurat_obj[[assay_name]], error = function(e) NULL)
175+
meta_features <- tryCatch(
176+
assay_obj@meta.features,
177+
error = function(e) tryCatch(assay_obj@meta.data, error = function(e2) NULL)
178+
)
179+
if (is.null(meta_features)) stop("No feature metadata found on assay")
146180

147181
symbols <- meta_features[[gene_symbols]]
148182

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
test_that(".resolve_seurat_assay_rna returns 'RNA' when RNA is default assay", {
2+
skip_if_not_installed("Seurat")
3+
counts <- Matrix::sparseMatrix(
4+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
5+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
6+
)
7+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "RNA"))
8+
result <- CyteTypeR:::.resolve_seurat_assay_rna(obj)
9+
expect_identical(result, "RNA")
10+
})
11+
12+
test_that(".resolve_seurat_assay_rna warns when DefaultAssay is not RNA but RNA exists", {
13+
skip_if_not_installed("Seurat")
14+
counts <- Matrix::sparseMatrix(
15+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
16+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
17+
)
18+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "RNA"))
19+
obj[["SCT"]] <- obj[["RNA"]]
20+
Seurat::DefaultAssay(obj) <- "SCT"
21+
22+
expect_warning(
23+
result <- CyteTypeR:::.resolve_seurat_assay_rna(obj),
24+
"DefaultAssay.*SCT.*Using assay='RNA'"
25+
)
26+
expect_identical(result, "RNA")
27+
})
28+
29+
test_that(".resolve_seurat_assay_rna errors when no RNA assay exists", {
30+
skip_if_not_installed("Seurat")
31+
counts <- Matrix::sparseMatrix(
32+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
33+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
34+
)
35+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "SCT"))
36+
37+
expect_error(
38+
CyteTypeR:::.resolve_seurat_assay_rna(obj),
39+
"requires an assay named 'RNA'.*Available assays: SCT"
40+
)
41+
})
42+
43+
test_that(".resolve_seurat_assay_rna errors when object has no assays", {
44+
skip_if_not_installed("Seurat")
45+
counts <- Matrix::sparseMatrix(
46+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
47+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
48+
)
49+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "RNA"))
50+
obj@assays <- list()
51+
52+
expect_error(
53+
CyteTypeR:::.resolve_seurat_assay_rna(obj),
54+
"No assays found"
55+
)
56+
})
57+
58+
test_that(".resolve_seurat_assay_rna does not warn when DefaultAssay is already RNA", {
59+
skip_if_not_installed("Seurat")
60+
counts <- Matrix::sparseMatrix(
61+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
62+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
63+
)
64+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "RNA"))
65+
66+
expect_silent(
67+
result <- CyteTypeR:::.resolve_seurat_assay_rna(obj)
68+
)
69+
expect_identical(result, "RNA")
70+
})
71+
72+
test_that(".resolve_seurat_assay_rna handles DefaultAssay error gracefully", {
73+
skip_if_not_installed("Seurat")
74+
counts <- Matrix::sparseMatrix(
75+
i = c(1L, 2L), j = c(1L, 2L), x = c(1.0, 1.0),
76+
dims = c(2L, 2L), dimnames = list(c("g1", "g2"), c("c1", "c2"))
77+
)
78+
obj <- suppressWarnings(Seurat::CreateSeuratObject(counts = counts, assay = "RNA"))
79+
80+
local_mocked_bindings(
81+
DefaultAssay = function(...) stop("broken"),
82+
.package = "Seurat"
83+
)
84+
85+
result <- CyteTypeR:::.resolve_seurat_assay_rna(obj)
86+
expect_identical(result, "RNA")
87+
})

0 commit comments

Comments
 (0)