AIM — Annotation-Independent Mapping
Quick install
# Install from PyPi
pip install spatial-aim
# Run GUI
aim gui
AIM maps an unannotated scRNA-seq reference (scRNA) onto single-cell-resolution spatial transcriptomics (ST) in a GUI or CLI. For a scRNA/ST pair it:
- Over-clusters the scRNA reference once with Leiden into
Lstart clusters. - Builds one agglomeration tree over those start clusters.
- Sweeps
K: for everyKin(1,L)it cuts the tree intoKcell states and assigns exactly one to each spot, reconstructing spot expression from the state profiles. - Finds best
K: computes metrics (reconstruction quality, spatial coherence and transcriptional coherence) and helps the user find the most sensible cell state granularity via an interactive GUI.
The spot→state step is modular. In principle, any scRNA to ST alignment tool can be used here.
We implement two baseline mappers (nearest_centroid, wann) and three reference mappers (tangram, tacco, dot).
You are invited to add and try out your mapper of choice!
If you want to replace the Leiden overclustering from Step 1 with your own annotations,
--start_from_annotation takes an existing annotation's L cell types as the start clusters instead.
Outline
Why AIM?
Conventional mapping methods blindly map a pre-annotated scRNA reference onto spots, even if there is not enough signal in the spatial data to reliably distinguish between them. The hierarchical cell type clustering is based on both the scRNA and the ST data at hand. The user can find a cell type granularity that can be mapped reliably.
Input data format
To run AIM, you need a pair of a scRNA dataset and a ST dataset, both as raw counts in .h5ad format.
- scRNA
<Name>.h5ad:X= raw counts (cells × genes);var_names= uppercase gene symbols. - ST
<Name>.h5ad:X= raw counts (spots × genes);var_names= uppercase gene symbols;obsm["spatial"]= (x, y)-coordinates of the spots: (n_spots × 2).
Single pair
Just have the two .h5ad files available and you are good to go.
Batch processing
AIM also supports batch processing of multiple pairs. Organise the data relationally the following way so one scRNA reference can be reused across many ST slices:
DATA/
├── scRNA/
│ ├── index.csv # one row per scRNA dataset
│ └── <scName>.h5ad
├── ST/
│ ├── index.csv # one row per ST dataset
│ └── <stName>.h5ad
└── pairs.csv # links scRNA ↔ ST: PairID, scName, stName, …
See sample_dataset/ for a minimal example, and validate your own layout with aim validate.
For users
Installation
AIM is installable from PyPI:
pip install spatial-aim
This installs one command, aim, with subcommands (aim --help).
You can now already run aim with the baseline mappers.
If you want to be able to select one of the reference aligners within AIM, you need conda installed on your machine.
Please install the corresponding conda environments on your computer.
| Method | Environment | Command |
|---|---|---|
| Tangram | tangram_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_tangram.yml |
| TACCO | tacco_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_tacco.yml |
| DOT | dot_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_dot.yml then Rscript -e "remotes::install_github('saezlab/DOT')" |
For AIM to use the environments, please make sure AIM can find your conda executable. Either set the environment variable
CONDA_EXEor add your path to your conda installation toPATH. Make sureexe = os.environ.get("CONDA_EXE") or shutil.which("conda")returns the path to your conda.
Validate a dataset pair (aim validate)
Check whether your data meets the main requirements.
aim validate --scdata path/to/sc.h5ad --stdata path/to/st.h5ad
or for every pair of a pairs.csv laid out as in Batch Processing:
aim validate --pairs_csv path/to/DATA/pairs.csv
Validations per dataset:
- scRNA reference data alone
- raw count matrices (no NaN values, np negative values, integer-looking, not library-size normalized or log1p'd)
- unique uppercase gene names
- ST data alone
- raw count matrices (no NaN values, np negative values, integer-looking, not library-size normalized or log1p'd)
- unique uppercase gene names
obsm["spatial"]of shape(n_spots, 2).
- Per pair
- the size of the shared-gene intersection
- whether any ST spot is left all-zero by it.
- If an
index.csvsits next to the.h5adfiles, its recorded counts and cell-type keys are cross-checked as well.
Run via GUI (aim gui)
Interactive Streamlit app to run AIM from the browser and interactively browse the results for one scRNA/ST pair without any .
aim gui [--server_port 8501]
Open the printed URL (default http://localhost:8501), set the inputs, pick one or multiple mapper(s), and click Run.
As soon as both .h5ad paths are set, the GUI runs the same checks as aim validate on that pair: a clean pair is confirmed with a tick in the sidebar, any error or warning is shown in a dialog you can close to continue anyway.
The GUI writes each mapper's sweep to <output_dir>/<mapper>/. The results using each mapper are visualized in different tabs. When having run multiple mappers, also a "Compare" tab appears.
Run via CLI (aim run)
Run AIM via CLI on a single pair or in batch mode.
The spot→state mapper is chosen with argument --mapping:
nearest_centroid(default): Per spot, select the cell state by highest cosine similarity between the state's centroid and the spot's expression. One-hot assignment by default.wann: Weighted Adaptive Nearest Neighbor (Di Salvo et al., TMLR 2025): each reference cell gets a label-reliability score; each spot inherits the neighbourhood size of its nearest reference cell and votes over its nearest neighbours (labelled single reference cells by cosine distance) weighted by reliability.tangram/tacco/dot: delegate the spot→state step to given external aligner (one alignment perK, run in its own conda env; slower). Requires the corresponding reference env (see Installation).
The linkage of the agglomeration tree over the start clusters is chosen with --linkage_method:
average(default): UPGMA — average pairwise distance; no size term, tends to peel small tight groups off a growing dominant state.ward: Ward's criterion (R'sward.D); carries a size term and tends to produce balanced states.
The start clusters the tree is built over come from the Leiden over-clustering by default.
Pass --start_from_annotation <obs_column> to use a pre-existing annotation instead: its cell types become the start clusters, no over-clustering is computed at all,
and K sweeps from the number of annotated types down (cells with no label are dropped).
Give such runs their own --output_dir: a run root is named after the mapper alone, so the two modes would otherwise overwrite each other.
Single pair
aim run --scdata path/to/sc.h5ad \
--stdata path/to/st.h5ad \
--output_dir path/to/out_dir \
[--mapping nearest_centroid|wann|tangram|tacco|dot] \
[--linkage_method average|ward] \
[--start_from_annotation <obs_column>] \
[--leiden_resolution 3.0] \
[--k_min 1] [--k_max <L>] [--k_step 1] \
[--logging verbose]
Batch mode (all pairs in pairs.csv):
aim run --pairs_csv path/to/pairs.csv \
--sc_dir path/to/scRNA \
--st_dir path/to/ST \
--output_dir path/to/out_dir \
[--mapping nearest_centroid|wann|tangram|tacco|dot] \
[--linkage_method average|ward] \
[--start_from_annotation <obs_column>] \
[--leiden_resolution 3.0] \
[--k_min 1] [--k_max <L>] [--k_step 1] \
[--logging verbose]
Each pair is written to <out_dir>/<PairID>_<scName>__<stName>/.
Output layout (per pair):
<mapping>/config.yaml— the run configuration.start_clustering.h5ad— per-cell start cluster (start_clusterid +start_cluster_name); computed once, reused for everyK.k_<kkk>/— one folder perK:spot_to_state_mapping_soft.h5ad— the spot→state matrixP(spots ×K); carriesobs["mapping_confidence"]when the mapper defines one.spot_to_state_mapping.csv—Pas CSV (for eyeballing).start_cluster_to_state.csv— the start-cluster→state tree cut.analysis/data/— machine-readable metrics for thatK(no figures; the GUI renders them).
k_comparison.csv— one row perK: the measured metrics, plus each criterion's combined score (harmonic mean of its two curves), the overall score across criteria, and aparetoflag.k_selection.json— the proposal: the Pareto-optimalK, and the bestKper criterion and overall, each with its score. Same computation the GUI's "Choose K" card shows.
Sample-dataset example:
aim run \
--scdata sample_dataset/scRNA/sample_sc.h5ad \
--stdata sample_dataset/ST/sample_st.h5ad \
--output_dir sample_output/sample
The sample dataset is for sample usage only, it has no meaning.
Annotation-based baseline
There is no separate command for it: aim run --start_from_annotation <obs_column> covers it.
The annotated types become the start clusters, and the sweep's K = number-of-types level is the baseline.
For developers
Installation of dev environment
If you want to contribute, clone this repository and create a local conda environment. This will include everything apart from the reference aligner methods.
conda env create -f environment.yml
conda activate aim_env
# Optional: Install the AIM-CLI from local
pip install -e . --no-deps
Environments of reference aligners
If you want to use one of the reference aligners within AIM or standalone, you have to additionally create the following environments. Each reference aligner runs in its own conda environment.
AIM orchestrates them out-of-process via conda run, so you need conda on PATH. The env .yml files live in the repo under src/aim/reference_aligners/.
| Method | Environment | Create |
|---|---|---|
| Tangram | tangram_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_tangram.yml |
| TACCO | tacco_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_tacco.yml |
| DOT | dot_env |
conda env create -f https://raw.githubusercontent.com/domipysch/AIM/main/src/aim/reference_aligners/environment_dot.yml then Rscript -e "remotes::install_github('saezlab/DOT')" |
Adding another reference aligner
Reference aligners are pluggable.
Each runs the same way: a wrapper launched out-of-process in its own conda env.
The framework learns about it from a single registry,src/aim/reference_aligners/registry.py.
Adding a reference aligner is three steps:
1. Add the conda environment file and create it, e.g. conda env create -f src/aim/reference_aligners/environment_<name>.yml.
2. Write src/aim/reference_aligners/run_<name>.py satisfying the CLI API:
| Contract | |
|---|---|
| Args | --scdata, --stdata, --output_folder, --cell_type_key |
| Input | scdata carries the states/types in obs[cell_type_key]; stdata shares genes with scdata and has spatial coords in obsm["spatial"] |
| Output | writes <output_folder>/mapping_prob.h5ad — AnnData with obs = spots (ST order), var = state/type names, X = float32 soft-assignment (S × T) |
Invoked with only those four args, the wrapper must reproduce its canonical mapping.
3. Register it
Add one line to REFERENCE_ALIGNERS (src/aim/reference_aligners/registry.py).
ReferenceAligner("<name>", "<name>_env", "aim.reference_aligners.run_<name>"),
It is then selectable everywhere automatically:
aim gui(it will be selectable in the sidebar)aim run --mapping <name>(as an AIM reference mapper, one alignment perK) — including--start_from_annotation, where it maps onto a pre-existing annotation
4. Create PR
Create a PR with those changes to make it available for all users!
Metadata
Release files for spatial-aim 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| spatial_aim-1.0.0.tar.gz | 22.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spatial_aim-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.6 MB
Release files / spatial_aim-1.0.0.tar.gz
| Download URL | spatial_aim-1.0.0.tar.gz |
|---|---|
| Size | 22.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4ba8e888c1ffe94c39786395aa090f34d809e113a8e03ab096b99c94bbfdd888
|
|
BLAKE2b-256 checksum How to use checksums |
bf8418d404bf34d5180033b43f27b58e25ac8d494726e79c9a17ca049a9dcf04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / spatial_aim-1.0.0-py3-none-any.whl
| Download URL | spatial_aim-1.0.0-py3-none-any.whl |
|---|---|
| Size | 199.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b879c559ff28561e9c53c3bd46533e896631774141ad8c605670a5ac19af2d5a
|
|
BLAKE2b-256 checksum How to use checksums |
dc087e4019762c9842885c451ba82669926e1ded65eedefa1eea0f064d25597e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log