Skip to main content

wittgen-b2sc

Thin Python client for the WittGen B2SC API — submit a bulk RNA-seq job, poll it to completion, and pull per-sample single-cell type proportions, gene scores and plots. Built to the OpenAPI contract at https://www.wittgenbio.com/api/v1/openapi.json.

Install

pip install wittgen-b2sc            # core
pip install "wittgen-b2sc[pandas]"  # + DataFrame support

Installing from git requires access to the private platform repo, so it only works for WittGen staff and partners who have been granted it:

pip install "wittgen-b2sc[pandas] @ git+ssh://git@github.com/WittGen-Inc/wittgen-b2sc-platform.git#subdirectory=clients/wittgen-b2sc-python"

Authenticate

Create a per-user API key in the WittGen dashboard (it is shown once — store it as a secret).

from wittgen_b2sc import B2SCClient
client = B2SCClient(api_key="wgk_...")

Quickstart

# 1. Submit a job against a built-in reference dataset
job = client.submit_job("breast-tumour-2k", source="reference_dataset")
job_id = job["job_id"]

# 2. Wait for the run. The reference dataset is a single example bulk and finishes
#    quickly; a large uploaded cohort is what takes hours (see the timeout note below).
client.wait_for_completion(job_id)

# 3. Per-sample cell-type proportions as a tidy DataFrame
df = client.get_proportions(job_id, as_dataframe=True)   # columns: sample, cell_type, proportion
print(df.head())

# 4. Everything the run produced, as presigned downloads
for f in client.list_files(job_id):
    print(f["name"], f["category"], f["url"])

Databricks quickstart

Run inside a Databricks notebook. Store the key in a secret scope, never inline.

# Cell 1 — install
%pip install "wittgen-b2sc[pandas]"

# Cell 2 — client (key from a Databricks secret scope)
from wittgen_b2sc import B2SCClient
api_key = dbutils.secrets.get(scope="wittgen", key="b2sc_api_key")
client = B2SCClient(api_key=api_key)

# Cell 3 — submit + await + load as a Spark-ready pandas DataFrame
job = client.submit_job("breast-tumour-2k", source="reference_dataset")
client.wait_for_completion(job["job_id"])
pdf = client.get_proportions(job["job_id"], as_dataframe=True)
sdf = spark.createDataFrame(pdf)      # -> a Spark DataFrame you can join/aggregate/save to Delta
sdf.display()

API surface

Method Description
list_models() Available disease models — read applies_to before submitting your own data
list_atlases() Pre-computed public cohorts, ready to inspect
get_usage() Your plan and remaining trial quota
upload_file(path, content_type="text/plain") Upload your own matrix → input_file_key
submit_job(disease_model, source, input_file_key=None, geo_accession=None, geo_file=None) Submit a job → job record
list_jobs(limit=None) Your own jobs, newest first (not atlases)
get_job(job_id) Current status
wait_for_completion(job_id, poll_interval=15, timeout=10800, wait_for_analysis=False) Poll until COMPLETED. wait_for_analysis=True additionally waits for the analysis stage where a deployment offers one, and raises ANALYSIS_STAGE_NOT_OFFERED where it does not
get_results_data(job_id) Aggregated summary (mean proportions across the cohort) + metadata
get_proportions(job_id, fmt="long"|"wide", as_dataframe=False) Per-sample proportions
list_files(job_id, category=None, include_raw=False) Every artifact. Most carry a presigned URL; the raw .h5ad needs include_raw=True because presigning it spends a raw download

Running a public GEO series

Give an accession and we resolve it to the gene-level raw count matrix in that series, fetch it and run it:

# Pick a series whose tissue matches the model. list_models() publishes applies_to for exactly this.
job = client.submit_job("breast-tumour-2k", source="geo", geo_accession="GSE…")

The series has to be the tissue the model is for. This example used to pair a breast model with GSE149050, which is systemic-lupus whole blood — a combination the API will run to completion and answer with well-formed breast proportions for a sample that has no breast tissue in it. Nothing errors. Read applies_to and not_applicable_to on list_models() before you choose an accession, and note the 64-sample limit: most public series are wider than that and are refused with a message telling you to split them.

A series carries whatever its submitter uploaded — GSE149050, referenced below only for the raw-counts rule, publishes both raw counts and TPM, and we take the counts. When several files are plausible the call returns 422 with the listing; retry with geo_file="..." naming one. Pass geo_file up front to override our choice.

The models need raw counts. They apply their own CPM + log1p normalization, so a matrix that has already been through that is the wrong input rather than a lesser one — and the quiet failure is the dangerous one: TPM and FPKM are non-negative, so nothing errors and the run finishes with confident, wrong proportions. A series that publishes only normalized values is therefore refused up front with 422 GEO_NO_RAW_COUNTS, naming the unit that blocked it. GSE81538 and GSE96058 are both this case. The same check runs on uploads, where there is no filename to judge.

The series is cached by accession, so the second person to run a given GSE does not wait for it again. Requires a provisioned plan: the data is public, the GPU run is not.

Pre-computed atlases

Some well-known cohorts are already run, so you can look at real output without waiting:

for a in client.list_atlases():
    print(a["title"], a["source_citation"], a["n_samples"])
    df = client.get_proportions(a["job_id"], as_dataframe=True)

They are read-only and owned by WittGen; everything else works on them like any job.

Analysing your own data

upload_file() wraps the two-step presigned-S3 handshake — the PUT is signed over its headers, so hand-rolling it is easy to get wrong:

key = client.upload_file("my_cohort.tsv")
job = client.submit_job("breast-tumour-2k", source="user_upload", input_file_key=key)

The matrix must be .tsv/.csv/.txt, optionally gzipped, under 256 MB, genes × samples, with HGNC symbols in the gene column. Duplicate symbols — the ordinary result of an Ensembl→HGNC mapping — are collapsed by summing their counts, the standard resolution for one gene measured across several loci; the run reports how many rows were collapsed. Pre-aggregate yourself if you want different semantics. Uploading requires a provisioned plan; self-serve accounts run the bundled reference datasets (source="reference_dataset").

Know what the model is for

list_models() carries applies_to and not_applicable_to for each model. Read them before submitting your own matrix or a GEO series:

A deconvolution model given the wrong tissue does not fail. It distributes proportions across the cell types it knows and returns a well-formed table that sums to 1.000. Feed blood to the breast-tumour model and you get plausible epithelial and stromal fractions for a sample that has none. Read applies_to and not_applicable_to on list_models() first.

get_results_data() returns metadata.gene_coverage with the engine's own verdict on how much of the model's gene panel your matrix matched. The model refuses to run below its floor rather than producing a number from a partial panel. That catches a gene-identifier or species mismatch — it cannot tell you the tissue was right, because human liver matches HGNC symbols just as well as human blood.

Order of operations

wait_for_completion returns as soon as the job is COMPLETED, and that is the whole run: the per-sample proportions and the generated single-cell matrix are both ready at that point.

client.wait_for_completion(job_id)
df = client.get_proportions(job_id, as_dataframe=True)
for f in client.list_files(job_id):
    print(f["name"], f["category"], f["url"])

A downstream analysis stage is not offered on every deployment. The engine release guarantees inference — checkpoint and gene-axis integrity, bulk conditioning, the run itself — and downstream analysis (sub-clustering, differential expression, plots) is a separate evaluation. Where it is not offered, GET /b2sc/jobs/{id}/report answers 404, no analysis field appears on a job, and wait_for_analysis=True raises ANALYSIS_STAGE_NOT_OFFERED immediately rather than polling for something that is not coming. Ask the API rather than assuming: if a completed job carries no report_status, this deployment does not run the stage.

What the model accepts

Limit Value
Samples per job 64
Gene rows 100,000 — gene-level annotations (40-60k) are fine, transcript-level is not
File size 256 MB as sent, 512 MB expanded
Gene panel overlap 70% of the model's own panel, on two floors: matched by symbol AND carrying signal (1,400 of 2,000; 3,500 of 5,000). n_genes on list_models()

A matrix that misses the overlap floor is refused rather than run on partial input, and get_results_data() reports where a finished run landed under metadata.gene_coverage.

One job takes at most 64 samples

The model refuses a matrix with more than 64 columns:

Bulk input exceeds the 64-sample limit.

Split a larger cohort into batches of 64 or fewer and submit one job per batch — the per-sample proportion tables concatenate directly, since every batch returns the same cell-type columns in the same order. The 1,231-sample TCGA-BRCA atlas was produced exactly this way, as 20 batches.

The default timeout is 3 hours. Runtime scales with sample count, not with the model: a single-sample reference run finishes in minutes, and a full 64-sample batch stays well inside it. Where a deployment offers the analysis stage it adds time on top, since it runs subcluster differential expression across all 13 cell types. A B2SCTimeout does not cancel the job — it carries .elapsed and .last_status, and you can keep polling get_job().

How long results are kept

A job and its outputs are retained for 365 days from submission, whichever way the input was supplied. After that the job and its files are deleted and get_job() returns 404, so download anything you need to keep — list_files() gives presigned URLs for every artifact.

Atlases are permanent. They are not subject to this window.

Very old jobs may have been moved to archival storage. Those still appear in list_files(), but carry archived instead of a download URL rather than a link that would fail — ask us for a restore if you need one.

Removed in 0.2.0

The AI clinical report (an Opus-written PDF) was removed from the product on 2026-08-04, and with it generate_report(), wait_for_report() and download_report(). The deliverable is the R stage output — proportions, gene scores and plots — via get_proportions() and list_files().

Calling a removed method raises B2SCRemovedError, which names the replacement and shows the equivalent code. It subclasses both B2SCError and AttributeError, so hasattr() feature detection correctly reports the method as absent while a direct call still explains itself.

The wait_for_report= parameter of wait_for_completion was only renamed, not removed — it always waited for the R stage. It still works and warns.

Errors raise B2SCError (.status, .code); a poll timeout raises B2SCTimeout. A 409 from get_proportions means the results are not produced yet — retry.

Data residency: genomic expression data is sensitive. Analysis runs in AWS us-east-1; confirm that placement with WittGen before sending patient-derived data. Note the API does not send your data to any third-party model provider — the AI report that did was removed in 0.2.0.

License

Apache-2.0 — see LICENSE. This client SDK is open source; the WittGen B2SC model and service it talks to remain proprietary. Copyright 2026 WittGen Biotechnologies.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wittgen_b2sc-0.7.2.tar.gz (30.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wittgen_b2sc-0.7.2-py3-none-any.whl (21.0 kB view details)

Uploaded Python 3

File details

Details for the file wittgen_b2sc-0.7.2.tar.gz.

File metadata

  • Download URL: wittgen_b2sc-0.7.2.tar.gz
  • Upload date:
  • Size: 30.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for wittgen_b2sc-0.7.2.tar.gz
Algorithm Hash digest
SHA256 2aa5eb11e031f2b69b5af7144338a960284da21cff34d14f26e01d9220e51743
MD5 a5711d9f28bd9bf63fb9aeabef35d28b
BLAKE2b-256 66f4018395d753e04e454d1f49a8eb5d3642c247109e07e685ac047881b964e4

See more details on using hashes here.

File details

Details for the file wittgen_b2sc-0.7.2-py3-none-any.whl.

File metadata

  • Download URL: wittgen_b2sc-0.7.2-py3-none-any.whl
  • Upload date:
  • Size: 21.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for wittgen_b2sc-0.7.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a2c2913cc48663c1f58e363db0858e0c30581725bf480633013082ae7cc56406
MD5 619beb9a056cedfca5a8c620386ce5f7
BLAKE2b-256 aef421b8b0ae2b51c875c890a34e1e9ca2e368752cc91cf0f523e89d20d3c765

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page