Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: AzimuthAPI
Title: Pan-Azimuth Web API Interface
Version: 0.9.0
Version: 1.0.0
Authors@R:
person("Satija", "Lab", email = "satijalabnygc@gmail.com", role = c("aut", "cre"))
Description: An R package providing an interface to the Pan-Azimuth Web API for single-cell RNA sequencing analysis.
Expand Down
4 changes: 2 additions & 2 deletions R/annotation.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#' @param process_obj Whether to process the object
#' @param cutoff_abs Absolute cutoff for label filtering
#' @param cutoff_frac Fractional cutoff for label filtering
#' @param model_version Version of the model to use
#' @param model_version Version of the model to use (default: 'v1')
#' @param assay Assay to use for annotation
#'
#' @importFrom SeuratObject Idents<-
Expand Down Expand Up @@ -61,7 +61,7 @@ ANNotate <- function(
spread = 1.0,
verbose = TRUE,
init = "spectral",
model_version = "v0",
model_version = "v1",
process_obj = TRUE,
cutoff_abs = 5,
cutoff_frac = 0.001,
Expand Down
4 changes: 2 additions & 2 deletions R/argument_parser.R
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ parse_annotate_args <- function() {

parser$add_argument(
"--model_version",
default = "v0",
help = "Version of the model to use (default: 'v0')",
default = "v1",
help = "Version of the model to use (default: 'v1')",
type = "character"
)

Expand Down
64 changes: 60 additions & 4 deletions R/cloud.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,59 @@
#'
#' @param object Seurat object to annotate
#' @param assay Name of the assay to use (default: 'RNA')
#' @param ip Hostname or IP address of the cloud server (default: 'azimuthapi.satijalab.org')
#' @param port Port number for the API (default: 5000)
#' @param ip Server hostname (default: 'azimuthapi.satijalab.org')
#' @param port Server port (default: NULL)
#' @param scheme Server URL scheme (default: 'https').
#' @param ... Additional arguments for the API to pass to the model (see ANNotate function for details)
#' @return Annotated Seurat object
#' @details
#' Use `CloudAzimuth(object)` for the public cloud service. The `ip`, `port`, and `scheme`
#' arguments should not be set manually by users; the default values follow standard use.
#' @importFrom httr POST GET upload_file content status_code
#' @importFrom RCurl url.exists
#' @importFrom SeuratObject LayerData Idents IsMatrixEmpty CreateAssay5Object CreateSeuratObject Cells Idents<-
#' @concept annotation
#' @export
CloudAzimuth <- function(object = object, assay = 'RNA', ip = 'azimuthapi.satijalab.org',
port = 5000, ...) {
port = NULL, scheme = NULL, ...) {

cli::cli_h1("Running Pan-human Azimuth on the cloud")

api_base_url <- paste0('http://', ip, ":", port)
if (grepl("^https?://", ip)) {
stop("`ip` should not include 'http://' or 'https://'. Use `scheme` to choose HTTP or HTTPS.")
}

# the default scheme and port depend on whether the user is connecting to the production server or a custom host
# defaults preserve the official HTTPS path and the temporary legacy HTTP path
if (identical(ip, "azimuthapi.satijalab.org")) {
if (is.null(scheme) && is.null(port)) {
scheme <- "https"
} else if (is.null(scheme) && isTRUE(port == 5000)) {
scheme <- "http"
}
} else {
if (is.null(scheme)) {
scheme <- "http"
}
if (is.null(port) && identical(scheme, "http")) {
port <- 5000
}
}

# restrict the official host to supported public entrypoints
if (identical(ip, "azimuthapi.satijalab.org")) {
https <- identical(scheme, "https") && is.null(port)
http_5000 <- identical(scheme, "http") && isTRUE(port == 5000)
allowed_official_access <- https || http_5000
if (isTRUE(http_5000)) {
cli::cli_alert_warning("Using HTTP connection to the AzimuthAPI server. This is not recommended for security reasons and will be deprecated in the future. Please use HTTPS instead.")
} else if (isFALSE(allowed_official_access)) {
stop("For the AzimuthAPI server, use either (default) HTTPS with no port or (legacy) HTTP on port 5000.")
}
}

api_base_url <- build_cloud_api_base_url(ip = ip, port = port, scheme = scheme)

update <- check_api_version(api_base_url)

if (isTRUE(update)) {
Expand Down Expand Up @@ -134,3 +172,21 @@ process_rds_file <- function(api_base_url, file_path, ...) {
cli::cli_alert_success("Annotation complete. Output saved to: {save_path}")
}

#' Build base URL for the cloud API
#'
#' @param ip Hostname or IP address of the cloud server
#' @param port Port number for the API, or `NULL` to omit it
#' @param scheme URL scheme to use
#' @return Character containing the base API URL to connect to
#' @noRd
build_cloud_api_base_url <- function(ip, port = NULL, scheme = "https") {
if (!is.character(scheme) || length(scheme) != 1 || is.na(scheme) || !scheme %in% c("http", "https")) {
stop("`scheme` must be either 'http' or 'https'.")
}

if (is.null(port) || identical(port, "")) {
return(paste0(scheme, "://", ip))
}

paste0(scheme, "://", ip, ":", port)
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AzimuthAPI

## Version 0.9.0
## Version 1.0.0

An R package providing an interface to the Pan-human Azimuth neural network, enabling users to run cell type annotation on single-cell and spatial transcriptomics data.

Expand All @@ -15,7 +15,7 @@ Two options for annotation are available:
## Installation

```r
# Install devtools if not already installed
# Install remotes if not already installed
install.packages("remotes")

# Install AzimuthAPI from GitHub
Expand Down
4 changes: 2 additions & 2 deletions man/ANNotate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions man/CloudAzimuth.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 105 additions & 1 deletion tests/testthat/test_interface.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,117 @@ test_that("CloudAzimuth returns an annotated Seurat object", {
.package = "AzimuthAPI"
)

annotated <- CloudAzimuth(query, ip = "azimuthapi.satijalab.org", port = 5000)
annotated <- CloudAzimuth(query, ip = "azimuthapi.satijalab.org")

expect_s4_class(annotated, "Seurat")
expect_true(all(c("final_level_labels", "azimuth_label") %in% colnames(annotated@meta.data)))
expect_identical(unname(annotated@meta.data$azimuth_label), rep(c("T cell", "B cell"), length.out = ncol(query)))
})

test_that("CloudAzimuth builds HTTPS and legacy URLs correctly", {
expect_identical(
build_cloud_api_base_url("azimuthapi.satijalab.org"),
"https://azimuthapi.satijalab.org"
)

expect_identical(
build_cloud_api_base_url("localhost", port = 5000, scheme = "http"),
"http://localhost:5000"
)

expect_identical(
build_cloud_api_base_url("azimuthapi.satijalab.org", scheme = "http", port = 5000),
"http://azimuthapi.satijalab.org:5000"
)

expect_error(
build_cloud_api_base_url("azimuthapi.satijalab.org", scheme = "ftp"),
"`scheme` must be either 'http' or 'https'.",
fixed = TRUE
)

})

test_that("CloudAzimuth requires ip without scheme prefix", {
expect_error(
CloudAzimuth(object = NULL, ip = "https://example.org"),
"`ip` should not include 'http://' or 'https://'.",
fixed = TRUE
)
})

test_that("CloudAzimuth defaults to official HTTPS endpoint", {
query <- make_test_object()
expected <- query
expected@meta.data$final_level_labels <- rep(c("T cell", "B cell"), length.out = ncol(expected))
expected@meta.data$azimuth_label <- expected@meta.data$final_level_labels
api_base_urls <- character()

testthat::local_mocked_bindings(
check_api_version = function(url) {
api_base_urls <<- c(api_base_urls, url)
invisible(url)
},
process_rds_file = function(url, file_path, ...) {
api_base_urls <<- c(api_base_urls, url)
saveRDS(expected, file = sub("\\.rds$", "_ANN.rds", file_path))
invisible(NULL)
},
.package = "AzimuthAPI"
)

CloudAzimuth(query, ip = "azimuthapi.satijalab.org")
expect_true(all(api_base_urls[1:2] == "https://azimuthapi.satijalab.org"))
})

test_that("CloudAzimuth preserves alternate-host HTTP defaults", {
query <- make_test_object()
expected <- query
expected@meta.data$final_level_labels <- rep(c("T cell", "B cell"), length.out = ncol(expected))
expected@meta.data$azimuth_label <- expected@meta.data$final_level_labels
api_base_urls <- character()

testthat::local_mocked_bindings(
check_api_version = function(url) {
api_base_urls <<- c(api_base_urls, url)
invisible(url)
},
process_rds_file = function(url, file_path, ...) {
api_base_urls <<- c(api_base_urls, url)
saveRDS(expected, file = sub("\\.rds$", "_ANN.rds", file_path))
invisible(NULL)
},
.package = "AzimuthAPI"
)

CloudAzimuth(query, ip = "localhost")
expect_true(all(api_base_urls[1:2] == "http://localhost:5000"))
})

test_that("CloudAzimuth preserves legacy official HTTP when port 5000 is explicit", {
query <- make_test_object()
expected <- query
expected@meta.data$final_level_labels <- rep(c("T cell", "B cell"), length.out = ncol(expected))
expected@meta.data$azimuth_label <- expected@meta.data$final_level_labels
api_base_urls <- character()

testthat::local_mocked_bindings(
check_api_version = function(url) {
api_base_urls <<- c(api_base_urls, url)
invisible(url)
},
process_rds_file = function(url, file_path, ...) {
api_base_urls <<- c(api_base_urls, url)
saveRDS(expected, file = sub("\\.rds$", "_ANN.rds", file_path))
invisible(NULL)
},
.package = "AzimuthAPI"
)

CloudAzimuth(query, ip = "azimuthapi.satijalab.org", port = 5000)
expect_true(all(api_base_urls[1:2] == "http://azimuthapi.satijalab.org:5000"))
})

test_that("ANNotate returns an annotated Seurat object", {
skip_if_not_installed("Seurat")
skip_if_not_installed("reticulate")
Expand Down
Loading