Strand AI Platform Quickstart

This vignette walks end-to-end through the Strand AI Platform API using the strand R package: upload a Whole Slide Image (WSI), submit a Lattice H&E to multiplex protein inference job, wait for completion, and pull results back as a SpatialExperiment.

The chunks below set eval = FALSE so the vignette renders cleanly on CRAN without network access. To run them yourself, mint an API key at app.strandai.com/settings/api-keys, set STRAND_API_KEY, and run interactively.

1. Set up

library(strandai)

# Reads STRAND_API_KEY; for local development:
# Sys.setenv(STRAND_BASE_URL = "http://localhost:3000")
client <- strand_client()
client

2. Upload a slide

strand_upload_file() initiates a resumable session and streams the file to GCS in 8 MiB chunks. The final PUT triggers automatic ingest, and the function polls only until the upload status leaves uploading. Dimensions can remain unavailable while preprocessing continues; estimation reports when the sample is ready.

upload <- strand_upload_file(client, "slide.svs", progress = TRUE)
upload

3. Work with samples

strand_samples_list() lists owned samples, the public cohort, or both. Every item has an ownership discriminator and a canonical id accepted by strand_samples_get(). Owned detail includes recent inference jobs; public detail contains public geometry and marker channels without job history.

page <- strand_samples_list(client, scope = "all")
sample <- strand_samples_get(client, page$items[[1]]$id)

if (sample$ownership == "mine") {
  sample <- strand_patch_sample(
    client, sample$id,
    name = "Baseline biopsy",
    tags = c("baseline", "responding"),
    mpp = 0.26
  )
}

4. Price credits

A dry run performs the same validation and pricing as submission without reserving credits or creating a job.

markers <- c("CD3", "CD8", "Ki67", "PD-L1")
est <- strand_predict(client, upload$id, markers, dry_run = TRUE)
message(
  est$patch_count, " patches x ", est$marker_count, " markers = ",
  est$estimated_credits, " credits"
)
message("org balance: ", est$org_balance)

5. Submit and wait

Submission atomically reserves the estimated credits in the same database transaction that creates the job. If your org has insufficient credits, you’ll see a strand_insufficient_credits_error.

job <- strand_predict(client, upload$id, markers)
status <- strand_job_wait(job, progress = TRUE)
stopifnot(status$status == "completed")

Choosing a model

Pass model = "v0.7" to select the current dispatchable Lattice version explicitly. Omitting model lets the platform pick the same current default. Older "v0.4" and "v0.5" labels can appear on historical jobs but are sunset for new submissions.

job <- strand_predict(client, upload$id, markers, model = "v0.7")

Fire-and-forget

For long pipeline runs, pass wait = FALSE to strand_run() to return the job handle as soon as upload + submit complete. The job continues server-side — you can poll or download later from the same or a fresh process.

job <- strand_run(client, "slide.svs", markers, wait = FALSE)
# ...later:
status <- strand_job_wait(job)
spe <- strand_download_results(job)

6. Download as SpatialExperiment

strand_download_results() walks the OME-Zarr result store via the API-key-authenticated proxy and assembles the channels into a SpatialExperiment. Rows are markers, columns are pixels; spatial coordinates are stored in spatialCoords(spe).

For very large slides, supply path = "out/" to mirror the zarr store to disk and read selectively with Rarr instead.

spe <- strand_download_results(job)
spe

Error handling

tryCatch(
  strand_predict(client, upload$id, markers),
  strand_insufficient_credits_error = function(e) {
    message("Need ", e$required, " more credits.")
  }
)