Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Automation, HPC and CI

CyteType works the same way in a script as in a notebook. This page covers what is different when no one is at the keyboard: how the job gets an API key, where files go, how to fit a job into a wall-time limit, how to save the results, and how to run many datasets.

Before you start, check that:

API keys in non-interactive environments

Browser sign-in does not work in batch jobs or CI. Instead, create a separate key for each CI system or cluster in the dashboard (API keys, then Create API key; the key is shown only once). Do not reuse the key that browser sign-in saved: browser sign-in gives every computer the same “CyteType CLI” key, so disabling it signs them all out (see Sign in and API keys). Then use the key in one of two ways.

Pass the key in code. Store the key in your CI system’s secrets or another secret store, expose it to the job as an environment variable, and pass it as auth_token:

Python
R
import os

from cytetype import CyteType

annotator = CyteType(
    adata,
    group_key="leiden",
    rank_key="rank_genes_leiden",
    auth_token=os.environ["CYTETYPE_API_TOKEN"],
)

Passing the key to CyteType() rather than to run() also avoids the note about a missing saved sign-in.

CYTETYPE_API_TOKEN is a name you choose; the clients never read it on their own.

Save the key once (HPC). On a login node, run cytetype login (R: LoginCyteTypeR()) and paste a key from the dashboard. The key is saved in ~/.config/cytetype/credentials.json, or under $XDG_CONFIG_HOME/cytetype/ when that variable is set. Batch jobs on compute nodes find it there when they share your home directory and see the same XDG_CONFIG_HOME; they then need no auth_token. See Sign in and API keys for details and for the order in which the clients choose a key.

A minimal batch script

These scripts annotate one dataset and save the result. They take an input and an output path, write their working files next to the output, and name each file after the output, so several copies can run at the same time.

Python
R
"""Annotate one dataset: python annotate.py input.h5ad output.h5ad"""
import os
import sys

import scanpy as sc
from cytetype import CyteType, CyteTypeTimeoutError

input_path, output_path = sys.argv[1], sys.argv[2]
name = os.path.splitext(os.path.basename(output_path))[0]
work_dir = os.path.dirname(os.path.abspath(output_path))

# Log-normalized expression in adata.X, clusters in adata.obs["leiden"].
adata = sc.read_h5ad(input_path)
sc.tl.rank_genes_groups(adata, groupby="leiden", method="wilcoxon", key_added="rank_genes_leiden")

annotator = CyteType(
    adata,
    group_key="leiden",
    rank_key="rank_genes_leiden",
    vars_h5_path=os.path.join(work_dir, f"{name}_vars.h5"),
    obs_duckdb_path=os.path.join(work_dir, f"{name}_obs.duckdb"),
    auth_token=os.environ["CYTETYPE_API_TOKEN"],  # leave out to use a saved key
)

try:
    annotator.run(
        study_context="Peripheral blood mononuclear cells from a healthy human donor, 10x Genomics 3' scRNA-seq.",
        metadata={"title": name},
        query_filename=os.path.join(work_dir, f"{name}_query.json"),
        timeout_seconds=3 * 60 * 60,
    )
except CyteTypeTimeoutError:
    print("The job is still running; fetch the results later with get_results().", file=sys.stderr)

annotator.cleanup()
# The saved file holds the results, or the job details needed to fetch them later.
annotator.adata.write_h5ad(output_path)

Submit the script with your scheduler, for example with sbatch on Slurm, and give it enough memory for the preparation (see Large datasets) and enough wall time for the wait.

Where files are written

By default, the clients write into the working directory. In a shared or read-only directory, set the paths explicitly:

FileDefault locationChange it with
vars.h5, obs.duckdbWorking directoryvars_h5_path and obs_duckdb_path in CyteType() or PrepareCyteTypeR()
query.jsonWorking directoryquery_filename, or save_query=False to skip it
cytetypeR_results_<job_id>.json, written by GetResults(job_id = ...) in RWorking directorysave_json = FALSE to skip it; to move it, change the working directory with setwd()
Temporary DuckDB files while obs.duckdb is writtenThe system temporary directory (in R, under the session’s tempdir())TMPDIR, set before Python or R starts
The saved API key, which the clients read during a run~/.config/cytetype/credentials.jsonXDG_CONFIG_HOME

Long jobs and wall-time limits

The annotation runs on CyteType’s servers, and your batch job only waits for it. Its wall time must cover the preparation, the upload and the wait. The server stops a job after 4 hours, and CyteType emails you when a job completes or fails.

Python
R

Set timeout_seconds (default 2 hours) to fit within your wall time. When it runs out, run() raises CyteTypeTimeoutError; the job keeps running, and the object already holds its job details. The script above saves the object in that case, so a later job can fetch the results.

With short wall times, submit and save right away, then fetch the results in a later job:

# Job 1: submit, wait at most 5 minutes, save.
try:
    annotator.run(study_context="...", timeout_seconds=300)
except CyteTypeTimeoutError:
    pass  # still running; the job details are stored in annotator.adata.uns
annotator.cleanup()
annotator.adata.write_h5ad("sample1_submitted.h5ad")
# Job 2, later: fetch the results.
import anndata as ad
from cytetype import CyteType

adata = ad.read_h5ad("sample1_submitted.h5ad")
annotator = CyteType(adata, group_key="leiden", rank_key="rank_genes_leiden")
results = annotator.get_results()  # None while the job is still running
annotator.cleanup()
if results is not None:
    adata.write_h5ad("sample1_cytetype.h5ad")

Creating CyteType in the second job prepares the data again, so give that job the same memory as the first. See Fetch results later.

Save the annotated object

Python
R
annotator.cleanup()  # also removes the temporary __cytetype_gene_symbols column
adata.write_h5ad("sample1_cytetype.h5ad")

The h5ad file keeps the result columns in adata.obs and the entries cytetype_results and cytetype_jobDetails in adata.uns (the results are stored as a JSON string, so they survive the round trip). On the reloaded object, get_results() returns them without contacting the server.

Results in AnnData and Seurat lists everything that is stored.

Run many datasets

Python
R
import scanpy as sc
from cytetype import CyteType, QuotaExceededError, RateLimitError

for name, path in datasets.items():  # for example {"sample1": "data/sample1.h5ad", ...}
    adata = sc.read_h5ad(path)
    annotator = CyteType(
        adata,
        group_key="leiden",
        rank_key="rank_genes_leiden",
        vars_h5_path=f"scratch/{name}_vars.h5",
        obs_duckdb_path=f"scratch/{name}_obs.duckdb",
    )
    try:
        adata = annotator.run(
            study_context=study_contexts[name],
            metadata={"title": name},
            query_filename=f"scratch/{name}_query.json",
        )
    except (RateLimitError, QuotaExceededError) as error:
        print(f"Stopped before {name}: {error.message}")
        annotator.cleanup()
        break
    annotator.cleanup()
    adata.write_h5ad(f"results/{name}_cytetype.h5ad")

Logs and progress output

See also