Design & architecture¶
SpaceBlocks is built around one idea: a technology-specific HeadBlock turns raw data into a standardized contract h5ad AnnData object, and a technology-agnostic CoreBlock does all the analysis on top of it.
Adding a platform means writing a new HeadBlock; the CoreBlock is shared and stable across technologies.
Importantly, the CoreBlocks immediately admit external data, as soon as they pass the validate_input rule (see below).
Repository structure¶
SpaceBlocks/
├── workflow/ the executable workflow (auto-found by Snakemake)
│ ├── Snakefile globals, mode selection, targets
│ ├── rules/*.smk one file per rule (+ common.smk for shared helpers)
│ ├── scripts/*.py, *.R rule implementations
│ ├── envs/*.yaml one Conda env per rule group
│ └── schemas/*.yaml config + sample-sheet validation
├── config/ config.yaml, README.md (config reference), sample sheets
├── docs/ this documentation site (MkDocs)
├── .tests/ tiny synthetic decoupled dataset for CI
├── demos/ full public-data runs (data fetched, not committed)
├── tools/ stand-alone helper scripts
└── profiles/default/ SLURM profile (retries, resources)
The Blocks¶
── HeadBlock (technology-specific) ──────────────────────────────────────────────
mode: visiumhd spaceranger_count_vhd → generate_qupath_vhd → prepare_input_vhd
mode: xenium5k convert_zarr_x5k → generate_qupath_x5k → prepare_input_x5k
mode: decoupled (no HeadBlock — you provide the contract h5ads)
│ ‖ COREBLOCK INPUT POINT (validate_input): contract h5ad structure validation
▼ each HeadBlock writes the unfiltered contract h5ad; although these can also be prepared manually (mode: decoupled)
── CoreBlock (technology-agnostic) ──────────────────────────────────────────────
STEP 1 · preprocessing
validate_input → [qc_sweep] → preprocess_umap → leiden_analysis
→ generate_annotation_template
→ ingest_ref (opt.) → spatial_niches (opt.)
STEP 2 · postprocessing
annotate_cells → integrate_samples → pseudobulk_aggregate → pseudobulk_de
→ neighbourhood_analysis → subcluster → sample_report
STEP 3 · exploration
explore_genes_integrated → explore_genes_sample
See the Rule reference for a one-paragraph description of every rule.
The validation object contract¶
The point where the HeadBlocks meet the CoreBlocks is the validate_input rule. This rule takes as an input the standardized h5ad AnnData files (the contracts, generated manually or via the HeadBlocks) and outputs a .json validation file, which confirms the data is suitable for entering pre- and postprocessing steps.
It is a single coupling point with one h5ad per sample. Every HeadBlock produces the same contract file structure, so the CoreBlock is identical for all technologies:
- Path — one config key (
contract.unfiltered_h5ad, derived in the Snakefile), written by the active HeadBlock'sprepare_input_*and read byvalidate_input,qc_sweep, andpreprocess_umap, so producer and consumers can never drift. - Shape of AnnData object:
X— raw integer counts (no filtering, no normalisation).obsm["spatial"]— per-cell(x, y)centroids, finite.uns["spatial"][sample]—{images: {hires}, scalefactors}when a tissue/morphology image exists; omitted otherwise (the CoreBlock then scatters on coordinates).obs—sample(required);region_annotationwhen a GeoJSON was provided; andcell_id(optional but strongly recommended, and must equalobs_namesto prevent unexpected behaviour, since downstream joins key on it —validate_inputwarns otherwise).- The image is a HeadBlock concern. The CoreBlock never rebuilds an image — it consumes the
embedded
uns["spatial"]image when present and otherwise scatters onobsm["spatial"].
For a worked walk-through of building these files by hand (naming, layout, config), see Preparing inputs for decoupled mode.
Output structure¶
For what every file is and why it is useful, see Outputs; the tree below shows where each lands.
{post_processing_outdir}/
├── Samples/{sample}/
│ ├── {sample}_unfiltered.h5ad CONTRACT H5AD (from HadBlocks: prepare_input_{vhd,x5k})
│ ├── QuPath_image/… generate_qupath_{vhd,x5k}
│ ├── validation/input_validation.json validate_input
│ ├── qc_sweep/ qc_sweep (optional)
│ ├── adata_{sample}.h5ad, metadata_{sample}.tsv, {sample}_report.tsv preprocess_umap
│ ├── leiden_resolution_*/ leiden_analysis
│ ├── annotation/, adata_{sample}_annotated.h5ad annotate_cells
│ └── neighbourhood_analysis/{annot_type}/ neighbourhood_analysis
├── integrated_samples/ concatenated / harmony / sketched / samples_report.pdf
├── pseudobulk/{annot_type}/{analysis_level}/ aggregated/ + de_results/
├── Subcompartments/{name}/{Harmony,NoHarmony}/ subcluster
├── spatial_niches/ tsv/, plots/
├── gene_exploration/ expression_ranges.tsv, {entry}/…
└── cluster_annotations_template.tsv generate_annotation_template
Key design decisions¶
- HeadBlock/CoreBlock split. A technology-specific set of rules (HeadBlock) produces a standardized unfiltered AnnData file (contract); the common CoreBlocks consume it for the analyses. Inclusion of new platform/s only requires the development of a new HeadBlock, and the CoreBlock does not need to be changed, unless it has to be expanded.
validate_inputis a DAG gate. The division in ensured by a validation rule, which can pass or give a hard/soft failure.- A
.jsonis written if the contract structure is validated. - A hard failure prevents the
.jsonfrom being written, and thus the CoreBlock from running. - Soft issues (missing region annotation, no mito genes, no image) are recorded, but do not necessarily prevent CoreBlocks from running (config file has a paramter for soft-passing).
- Coherent naming. Head rules carry a 3-letter technology code (
_vhd,_x5k) so the organization is easy to follow and heads can coexist. qc_sweeprule is diagnostic only — it never filters, clusters, or writes an h5ad.- External annotation takes over. When enabled, it becomes the primary annotation everywhere. The Configuration allows flexibility to retain all cells or remove externally unannotated ones.
- Config-driven colours — regions, sample metadata, and cell types, applied consistently across every plot, with a grey fallback for undefined levels. This allows precise and consitent color representations through the analyses.
- Retries scale memory.
mem_mbgrows with the attempt number, so an OOM-killed job is resubmitted with more RAM.
Extending SpaceBlocks¶
The aim of SpaceBlocks is to allow for long-term maintainable and extensible Spatial Transcriptomics analyses across platforms and tools. Below you can find guidelines for development.
Extending HeadBlocks to a new platform¶
If you wished to run SpaceBlocks but your platform is not listed among the available SpaceBlocks HeadBlocks, there are two options available:
1. Running SpaceBlocks in mode: decoupled (easier, recommended).
2. Writting a new SpaceBlocks HeadBlock.
See public data end-to-end example runs for demonstrations about how to run SpaceBlocks in decoupled mode.
If you wished to write a new HeadBlock:
- Create a 3-letter
<code>to identify the technology. - Write the Snakemake rules and Python scripts to prepare the AnnData h5ad contracts. Name them as
rules/*_<code>.smkandscripts/*_<code>.py. - Add the mode in the
SnakefileparameterKNOWN_HEADS. - Create a
demo/(public data download, GeoJSON region annotations, short documentation) with a prepared config example and instructions for reproducibility with a test run. - Include the documentation in the
demos.md. - Create a Pull Request to the
developmentbranch. We will revise it promptly.
Extending the CoreBlocks¶
SpaceBlocks includes many utilities for ST analyses, but there is room for extension and inclusion of flexible analysis parameters (i.e. methods for normalization, integration, variable gene selection, etc.).
If you wished to contribute, we will be happy to revise contributions to the SpaceBlocks workflow.
Contributing¶
Want to contribute or extend SpaceBlocks? See CONTRIBUTING.md for the conventions and validation workflow.