Downloading Xenium data#

spatialrefinery.download_xenium_study fetches a 10x Genomics Xenium study’s raw asset bundle from a manifest of curl -O <url> lines – the same manifest 10x’s dataset pages let you copy when you choose the “curl” download option. It retries transient failures, writes atomically (an interrupted download never leaves a truncated file behind), downloads in parallel, and unzips *_outs.zip archives in place (while always skipping the large *_xe_outs.zip Xenium Explorer bundles, which aren’t needed for SpatialData conversion).

This notebook uses Human Kidney Preview Data (Xenium Human Multi-Tissue and Cancer Panel), kidney cancer (PRCC) section, as its worked example – study Xenium_V1_hKidney_cancer_section.

What you need: a manifest file for the study you want. Runtime: the dry run below is instant; the real download depends on your network – for this study it’s ~3.4 GB for the outs bundle plus ~0.5 GB for the H&E image, so a few minutes on a decent connection.

from pathlib import Path

from spatialrefinery import download_xenium_study

The manifest format#

A manifest is a plain text file with one curl -O <url> line per asset. 10x’s dataset pages (10xgenomics.com/datasets/...) offer this as a download option next to the individual file links – copy it into a .txt file and pass that path as source.

The cell below writes the manifest for Human Kidney Preview Data (Xenium Human Multi-Tissue and Cancer Panel) – the kidney cancer (PRCC) section, study Xenium_V1_hKidney_cancer_section, Xenium Onboard Analysis v1.5.0 – exactly as 10x lists it: the main outputs bundle, the post-Xenium H&E image, and the H&E alignment CSV. Swap in the URLs from your own study to use a different dataset.

Manifests for other studies may also list a *_xe_outs.zip line (the Xenium Explorer bundle, often tens of GB). download_xenium_study never auto-extracts those, and you can drop them from a download entirely with the kinds filter shown below.

DATA_DIR = Path("xenium_example")
DATA_DIR.mkdir(exist_ok=True)

manifest_path = DATA_DIR / "manifest.txt"
manifest_path.write_text(
    """\
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_outs.zip
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_image.ome.tif
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_imagealignment.csv
"""
)
print(manifest_path.read_text())
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_outs.zip
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_image.ome.tif
curl -O https://cf.10xgenomics.com/samples/xenium/1.5.0/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_imagealignment.csv

Dry run: see what would be downloaded#

dry_run=True resolves the manifest and reports the plan – study names, asset kinds, destinations – without any network activity. Useful to sanity-check a manifest before committing to a multi-GB download.

plan = download_xenium_study(manifest_path, outdir=DATA_DIR / "raw", dry_run=True)
for result in plan:
    print(f"{result.asset.study:40s} {result.asset.kind:15s} -> {result.path}")
INFO     Fetching 3 asset(s) with up to 8 parallel workers.                                                        
Xenium_V1_hKidney_cancer_section         he_alignment    -> xenium_example/raw/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_imagealignment.csv
Xenium_V1_hKidney_cancer_section         outs            -> xenium_example/raw/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_outs.zip
Xenium_V1_hKidney_cancer_section         he_image        -> xenium_example/raw/Xenium_V1_hKidney_cancer_section/Xenium_V1_hKidney_cancer_section_he_image.ome.tif

Each entry is a DownloadResult: .status ("downloaded", "cached", "skipped", or "failed"), .ok (a bool convenience property), .asset (the source RemoteAsset, with .url, .study, .filename, .kind), and .error when a download failed.

Downloading a subset#

Restrict to specific asset kinds to avoid pulling everything. Here we take the main "outs" bundle (transcripts, cell/nucleus boundaries, morphology image) plus the H&E image and its alignment CSV – everything the next tutorial needs. That’s all three assets for this study, but on a manifest that also lists a Xenium Explorer bundle the same filter would leave "xe_outs" on the server.

raw_dir = DATA_DIR / "raw"
results = download_xenium_study(
    manifest_path,
    outdir=raw_dir,
    kinds=["outs", "he_image", "he_alignment"],
    max_workers=8,
)

failed = [r for r in results if not r.ok]
if failed:
    print(f"{len(failed)}/{len(results)} asset(s) failed:")
    for r in failed:
        print(f"  - {r.asset.url}: {r.error}")
else:
    print(f"Downloaded {len(results)} asset(s) to {raw_dir}")
INFO     Fetching 3 asset(s) with up to 8 parallel workers.                                                        
Downloaded 3 asset(s) to xenium_example/raw

Output layout#

Assets land under outdir/<study>/<filename>, where <study> is the second-to-last path segment of the URL. *_outs.zip is unzipped in place – 10x’s outs archives have no top-level folder, so their contents extract directly into the study directory, alongside the downloaded files:

raw/
└── Xenium_V1_hKidney_cancer_section/
    ├── Xenium_V1_hKidney_cancer_section_outs.zip
    ├── Xenium_V1_hKidney_cancer_section_he_image.ome.tif
    ├── Xenium_V1_hKidney_cancer_section_he_imagealignment.csv
    ├── experiment.xenium
    ├── gene_panel.json
    ├── metrics_summary.csv
    ├── analysis_summary.html
    ├── transcripts.parquet
    ├── cells.parquet
    ├── cell_boundaries.parquet
    ├── nucleus_boundaries.parquet
    ├── cell_feature_matrix.h5
    ├── morphology.ome.tif
    ├── morphology_focus.ome.tif
    └── morphology_mip.ome.tif

(Plus the .csv.gz, .tar.gz, and .zarr.zip equivalents 10x ships for the same tables.) The directory holding experiment.xenium is what you pass as dataset_path in the next tutorial.

Running this as a script#

python scripts/xenium_download.py --input_file manifest.txt --outdir raw_files --workers 8

What’s next#

Continue to Xenium to SpatialData zarr to convert the downloaded bundle into a SpatialData zarr store.