Here, we generate a small crop of a 10X Xenium and a 10X Visium H&E image dataset for our spatial guides. Both datasets are spatially aligned via an affine transformation.
The datasets were originally obtained via:
- Visium: https://s3.embl.de/spatialdata/spatialdata-sandbox/visium_associated_xenium_io_aligned.zip
- Xenium 1: https://s3.embl.de/spatialdata/spatialdata-sandbox/xenium_rep1_io_aligned.zip
- Xenium 2: https://s3.embl.de/spatialdata/spatialdata-sandbox/xenium_rep2_io_aligned.zip
They can also be found at https://spatialdata.scverse.org/en/latest/tutorials/notebooks/datasets/README.html.
The original associated publication is Janesick, A., Shelansky, R., Gottscho, A.D. et al. High resolution mapping of the tumor microenvironment using integrated single-cell, spatial and in situ analysis. Nat Commun 14, 8353 (2023). https://doi.org/10.1038/s41467-023-43458-x.
The authors explored tissue heterogeneity in FFPE human breast cancer sections, identifying distinct tumor regions and rare boundary cells at the myoepithelial border confining malignant cells by generating Visium and Xenium spatial omics data. This approach revealed molecular differences between tumor regions and identified biomarkers involved in progression toward invasive carcinoma.
Visium provided whole transcriptome spatial data with spot-based resolution, allowing researchers to identify the general territories of different cell types and delineate three distinct tumor domains including two molecularly distinct DCIS types and invasive tumor. Xenium delivered subcellular spatial resolution for 313 targeted genes, enabling precise mapping of transcripts to individual cells and revealing rare cell populations at tumor boundaries that would be undetectable with lower-resolution technologies.
!lamin connect laminlabs/lamindata
→ connected lamindb: laminlabs/lamindata
import lamindb as ln
import spatialdata as sd
import warnings
warnings.filterwarnings("ignore")
ln.track()
→ connected lamindb: laminlabs/lamindata → loaded Transform('MN1DpkKGjzbk0001'), re-started Run('NZUFH71T...') at 2025-03-26 12:39:20 UTC → notebook imports: lamindb==1.3.0 spatialdata==0.3.0
visium_sd = sd.read_zarr("visium_aligned.zarr")
xenium_1_sd = sd.read_zarr("xenium_1_aligned.zarr")
xenium_2_sd = sd.read_zarr("xenium_2_aligned.zarr")
merged_sd = merged = sd.SpatialData(
images={
"CytAssist_FFPE_Human_Breast_Cancer_full_image": visium_sd.images[
"CytAssist_FFPE_Human_Breast_Cancer_full_image"
],
},
shapes={
"cell_circles": xenium_1_sd.shapes["cell_circles"],
"cell_boundaries": xenium_1_sd.shapes["cell_boundaries"],
},
tables={"table": xenium_1_sd["table"]},
)
# Add sample level metadata
sdata_to_assay = {
visium_sd: "Visium Spatial Gene Expression",
xenium_1_sd: "10x Xenium",
xenium_2_sd: "10x Xenium",
merged_sd: "spatial transcriptomics"
}
for sdata, assay in sdata_to_assay.items():
sdata.attrs["sample"] = {
"assay": assay,
"disease": "ductal breast carcinoma in situ",
"tissue": "breast",
"organism": "human"
}
if "Xenium" in assay:
sdata.attrs["sample"]["panel"] = "Xenium Human Breast Panel"
sdata.table.var = sdata.table.var.reset_index().set_index("gene_ids")
sdata.table.var = sdata.table.var.rename(columns={"index": "symbols"})
visium_sd_af = ln.Artifact.from_spatialdata(visium_sd, key="full_visium_guide.zarr").save(store_kwargs=dict(batch_size=64))
xenium_1_sd_af = ln.Artifact.from_spatialdata(xenium_1_sd, key="full_xenium_1_guide.zarr").save(store_kwargs=dict(batch_size=64))
xenium_2_sd_af = ln.Artifact.from_spatialdata(xenium_2_sd, key="full_xenium_2_guide.zarr").save(store_kwargs=dict(batch_size=64))
merged_af = ln.Artifact.from_spatialdata(merged_sd, key="merged_guide.zarr").save(store_kwargs=dict(batch_size=64))
INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside /home/lukas/.cache/lamindb/Sp3WNSWTor9M0RLI0000.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from visium_aligned.zarr the new file path: /home/lukas/.cache/lamindb/Sp3WNSWTor9M0RLI0000.zarr → creating new artifact version for key='full_visium_guide.zarr' (storage: 's3://lamindata') ... uploading LA1CHFeOGqZShstc0001.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/full_visium_guide.zarr already exists, replacing it. INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside /home/lukas/.cache/lamindb/CzOO9ukMdZqURiPP0000.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from xenium_1_aligned.zarr the new file path: /home/lukas/.cache/lamindb/CzOO9ukMdZqURiPP0000.zarr → creating new artifact version for key='full_xenium_1_guide.zarr' (storage: 's3://lamindata') ... uploading FAxZpOWlM9Ml3EcU0001.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/full_xenium_1_guide.zarr already exists, replacing it. INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside /home/lukas/.cache/lamindb/i622CER4W9fKJuqA0000.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from xenium_2_aligned.zarr the new file path: /home/lukas/.cache/lamindb/i622CER4W9fKJuqA0000.zarr → creating new artifact version for key='full_xenium_2_guide.zarr' (storage: 's3://lamindata') ... uploading 7T8JkEDyMFgN3J2M0001.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/full_xenium_2_guide.zarr already exists, replacing it. INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside /home/lukas/.cache/lamindb/mob9XYBHL8fEQ6uw0000.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from None the new file path: /home/lukas/.cache/lamindb/mob9XYBHL8fEQ6uw0000.zarr ... uploading mob9XYBHL8fEQ6uw0000.zarr: 100.0%
# write explicitly to disk to ensure that the SpatialData is self contained
# ignore the warning which is misleading
sdata_to_name = {
visium_sd: "visium_aligned_guide_min.zarr",
xenium_1_sd: "xenium_aligned_1_guide_min.zarr",
xenium_2_sd: "xenium_aligned_2_guide_min.zarr",
merged_sd: "merged_guide_min.zarr"
}
min_coordinate = [12790, 12194]
max_coordinate = [15100, 15221]
for sdata, path in sdata_to_name.items():
min_sdata = sdata.query.bounding_box(
min_coordinate=min_coordinate,
max_coordinate=max_coordinate,
axes=["y", "x"],
target_coordinate_system="aligned",
)
min_sdata.write(path, overwrite=True)
INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside visium_aligned_guide_min.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from None the new file path: visium_aligned_guide_min.zarr INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside xenium_aligned_1_guide_min.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from None the new file path: xenium_aligned_1_guide_min.zarr INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside xenium_aligned_2_guide_min.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from None the new file path: xenium_aligned_2_guide_min.zarr INFO The SpatialData object is not self-contained (i.e. it contains some elements that are Dask-backed from locations outside merged_guide_min.zarr). Please see the documentation of `is_self_contained()` to understand the implications of working with SpatialData objects that are not self-contained. INFO The Zarr backing store has been changed from None the new file path: merged_guide_min.zarr
for path in sdata_to_name.values():
af = ln.Artifact.from_spatialdata(path, key=path).save()
→ creating new artifact version for key='visium_aligned_guide_min.zarr' (storage: 's3://lamindata') ... uploading visium_aligned_guide_min.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/visium_aligned_guide_min.zarr already exists, replacing it. → creating new artifact version for key='xenium_aligned_1_guide_min.zarr' (storage: 's3://lamindata') ... uploading xenium_aligned_1_guide_min.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/xenium_aligned_1_guide_min.zarr already exists, replacing it. → creating new artifact version for key='xenium_aligned_2_guide_min.zarr' (storage: 's3://lamindata') ... uploading xenium_aligned_2_guide_min.zarr: 100.0% ! The cache path /home/lukas/.cache/lamindb/lamindata/xenium_aligned_2_guide_min.zarr already exists, replacing it. ... uploading merged_guide_min.zarr: 100.0%
ln.finish()