Visualization of a SpatialData object and individual Spatial Elements, remote data
This notebook explains how to create interactive visualizations of data that are accessible remotely.
We progress through different visualization tasks, first demonstrating how Vitessce facilitates integrated imaging and spatial single-cell visualizations, then demonstrating visualization of non-spatial and image-only datasets.
Utility dependencies
First, we import utility dependencies which will be used to download the example dataset and manipulate file paths, zip files, and JSON files.
[1]:
import os
from os.path import join, isfile, isdir
from urllib.request import urlretrieve
import zipfile
import json
Dependencies for Vitessce
Here, we import classes and functions from the vitessce
Python package. This package includes not only APIs for visualization configuration but also helper functions for basic data transformation tasks. To specify mappings between data fields and visualization properties, the package contains
classes which wrap standard single-cell data structures stored in formats including AnnData, SpatialData, OME-TIFF, and OME-Zarr:
[ ]:
from vitessce import (
VitessceConfig,
ViewType as vt,
CoordinationType as ct,
CoordinationLevel as CL,
SpatialDataWrapper,
AnnDataWrapper,
ImageOmeTiffWrapper,
ImageOmeZarrWrapper,
ObsSegmentationsOmeZarrWrapper,
get_initial_coordination_scope_prefix,
hconcat,
vconcat,
)
from vitessce.data_utils import (
VAR_CHUNK_SIZE,
generate_h5ad_ref_spec,
multiplex_img_to_ome_tiff,
multiplex_img_to_ome_zarr,
)
In this blog post, we perform basic data transformation tasks to save individual elements of an integrated SpatialData object to separate files in AnnData, OME-TIFF, and OME-Zarr formats. To perform these data transformations, we import the following dependencies. In general, you will typically not need to import all of these dependencies, either because you are only working with data in one of these formats, or because the data you intend to visualize is already saved to a file or directory.
Download example dataset
We download a mouse liver dataset which serves as a SpatialData example dataset.
This dataset was generated by Guilliams et al. and processed using SPArrOW during the SpatialData developer workshop in 2024.
[ ]:
base_url = "https://storage.googleapis.com/vitessce-demo-data/spatialdata-may-2025"
[ ]:
zip_filepath = f"{base_url}/mouse_liver.spatialdata.zarr.zip"
spatialdata_filepath = f"{base_url}/mouse_liver.spatialdata.zarr"
adata_zarr_filepath = f"{base_url}/mouse_liver.anndata.zarr"
adata_h5ad_filepath = f"{base_url}/mouse_liver.h5ad"
ref_spec_json_filepath = f"{base_url}/mouse_liver.h5ad.ref.json"
ome_tiff_filepath = f"{base_url}/mouse_liver.ome.tif"
offsets_json_filepath = f"{base_url}/mouse_liver.ome.tif.offsets.json"
ome_zarr_filepath = f"{base_url}/mouse_liver.ome.zarr"
labels_ome_zarr_filepath = f"{base_url}/mouse_liver.labels.ome.zarr"
Visualization of a SpatialData object
SpatialData objects are the most complex type of data structure we will work with in this blog post. SpatialData objects function as contains for multiple types of Spatial Elements:
Tables (each table is represented as an AnnData object)
Points (e.g., coordinates of transcripts from FISH-based experiments)
Shapes (vector-based shapes such as polygons and circles)
Labels (label images, i.e., segmentation bitmasks; each label image is stored using OME-Zarr)
Images (microscopy images; each image is stored using OME-Zarr)
Configure Vitessce
Vitessce needs to know which pieces of data we are interested in visualizing, the visualization types we would like to use, and how we want to coordinate (or link) the views. To visualize data stored in a SpatialData object, we use the SpatialDataWrapper
class and specify the paths (relative to the root of the Zarr directory store) to different spatial elements of interest.
[ ]:
# Create a VitessceConfig instance.
vc = VitessceConfig(schema_version="1.0.17", name="SpatialData Demo")
# Instantiate the wrapper class, specifying data fields of interest.
wrapper = SpatialDataWrapper(
sdata_url=spatialdata_filepath,
# The following paths are relative to the root of the SpatialData Zarr store on-disk.
table_path="tables/table",
image_path="images/raw_image",
labels_path="labels/segmentation_mask",
obs_feature_matrix_path="tables/table/X",
obs_set_paths=["tables/table/obs/annotation"],
obs_set_names=["Annotation"],
region="nucleus_boundaries",
coordinate_system="global",
coordination_values={
"obsType": "cell"
}
)
# Add a new dataset to the Vitessce configuration,
# then add the wrapper class instance to this dataset.
dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)
# Add views (visualizations) to the configuration.
spatial = vc.add_view("spatialBeta", dataset=dataset)
feature_list = vc.add_view("featureList", dataset=dataset)
layer_controller = vc.add_view("layerControllerBeta", dataset=dataset)
obs_sets = vc.add_view("obsSets", dataset=dataset)
heatmap = vc.add_view("heatmap", dataset=dataset)
[obs_color_encoding_scope] = vc.add_coordination("obsColorEncoding")
obs_color_encoding_scope.set_value("cellSetSelection")
vc.link_views_by_dict([spatial, layer_controller], {
"imageLayer": CL([{
"photometricInterpretation": "BlackIsZero",
"imageChannel": CL([{
"spatialTargetC": 0,
"spatialChannelColor": [255, 255, 255],
"spatialChannelWindow": [0, 4000],
}])
}]),
}, scope_prefix=get_initial_coordination_scope_prefix("A", "image"))
vc.link_views_by_dict([spatial, layer_controller], {
"segmentationLayer": CL([{
"segmentationChannel": CL([{
"obsColorEncoding": obs_color_encoding_scope,
}]),
}]),
}, scope_prefix=get_initial_coordination_scope_prefix("A", "obsSegmentations"))
vc.link_views([spatial, layer_controller, feature_list, obs_sets, heatmap], ["obsType"], [wrapper.obs_type_label])
vc.link_views_by_dict([feature_list, obs_sets, heatmap], {
"obsColorEncoding": obs_color_encoding_scope,
}, meta=False)
# Layout the views in a grid arrangement.
vc.layout((spatial / heatmap) | (layer_controller / (feature_list | obs_sets)));
Render the widget
[ ]:
vw = vc.widget()
vw
Extract AnnData object from SpatialData object
The above example demonstrates how to visualize a spatial ’omics dataset containing not only single-cell information (e.g., a cell-by-gene expression matrix, cell type annotations) but also an image and cell segmentations. To demonstrate how to use Vitessce to visualize data from a (non-spatial) single-cell experiment, we will extract this information from the SpatialData object and save it to a simpler AnnData object (ignoring the imaging and spatially-resolved elements).
As Zarr-formatted data can be easily visualized by Vitessce, we recommend saving the AnnData object to a Zarr store using the write_zarr method. Optionally, the shape of array chunks (for the AnnData X
array) can be specified as a parameter, to optimize performance based on data access patterns. For example, a common pattern is to visualize data across all cells for one gene. To support such a pattern,
the chunk shape can be specified as follows, (total number of cells, small number of genes)
, resulting in tall-and-skinny array chunks.
Alternatively, your AnnData object may already be stored using the H5AD (HDF5-based) format. To demonstrate this scenario, we save the object using the write_h5ad method.
To read H5AD-formatted data, Vitessce requires an accompanying JSON references specification file, which can be constructed using the generate_h5ad_ref_spec
utility function.
Visualization of an AnnData object
Zarr-based AnnData
[ ]:
vc = VitessceConfig(schema_version="1.0.17", name="AnnData (zarr)")
# Add data.
wrapper = AnnDataWrapper(
adata_url=adata_zarr_filepath,
obs_feature_matrix_path="X",
obs_set_paths=["obs/annotation"],
obs_set_names=["Annotation"],
coordination_values={
"obsType": "cell"
}
)
dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)
# Add views.
heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)
feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)
obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)
violin_plots = vc.add_view("obsSetFeatureValueDistribution", dataset=dataset)
vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])
# Layout the views.
vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));
[ ]:
vc.widget()
H5AD-based AnnData
[ ]:
vc = VitessceConfig(schema_version="1.0.17", name="AnnData (h5ad)")
# Add data.
wrapper = AnnDataWrapper(
adata_url=adata_h5ad_filepath,
ref_url=ref_spec_json_filepath,
obs_feature_matrix_path="X",
obs_set_paths=["obs/annotation"],
obs_set_names=["Annotation"],
coordination_values={
"obsType": "cell"
}
)
dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)
# Add views.
heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)
feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)
obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)
violin_plots = vc.add_view("obsSetFeatureValueDistribution", dataset=dataset)
vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])
# Layout the views.
vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));
[ ]:
vc.widget()
Visualization of an image file
OME-Zarr image
[ ]:
vc = VitessceConfig(schema_version="1.0.17", name="Image (ome-zarr)")
# Add data.
img_wrapper = ImageOmeZarrWrapper(
img_url=ome_zarr_filepath,
coordination_values={
"fileUid": "image",
}
)
segmentations_wrapper = ObsSegmentationsOmeZarrWrapper(
img_url=labels_ome_zarr_filepath,
coordination_values={
"fileUid": "segmentations",
}
)
# Here, we chain .add_object calls to add both the image and segmentation
# wrapper instances to the same dataset.
dataset = vc.add_dataset(name='Mouse Liver').add_object(img_wrapper).add_object(segmentations_wrapper)
# Add views.
spatial = vc.add_view("spatialBeta", dataset=dataset)
layer_controller = vc.add_view("layerControllerBeta", dataset=dataset)
vc.link_views_by_dict([spatial, layer_controller], {
'imageLayer': CL([{
# In case there are multiple image files in our dataset,
# we use fileUid to specify which file we intend to visualize
# in this image layer.
'fileUid': 'image',
'photometricInterpretation': 'BlackIsZero',
'imageChannel': CL([{
'spatialTargetC': 0,
'spatialChannelColor': [255, 255, 255],
'spatialChannelWindow': [0, 4000],
}])
}]),
}, scope_prefix=get_initial_coordination_scope_prefix("A", "image"))
vc.link_views_by_dict([spatial, layer_controller], {
'segmentationLayer': CL([{
# In case there are multiple segmentation files in our dataset,
# we use fileUid to specify which file we intend to visualize
# in this segmentation layer.
'fileUid': 'segmentations',
'segmentationChannel': CL([{
'obsColorEncoding': 'spatialChannelColor',
'spatialChannelColor': [0, 255, 0],
'spatialChannelOpacity': 0.75,
'spatialSegmentationFilled': False,
'spatialSegmentationStrokeWidth': 0.25,
}]),
}]),
}, scope_prefix=get_initial_coordination_scope_prefix("A", "obsSegmentations"))
vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], ['cell'])
# Layout the views.
vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));
[ ]:
vw = vc.widget()
vw
OME-TIFF image
[ ]:
vc = VitessceConfig(schema_version="1.0.17", name="Image and segmentations (ome-tiff)")
# Add data.
wrapper = ImageOmeTiffWrapper(
img_url=ome_tiff_filepath,
offsets_url=offsets_json_filepath
)
dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)
# Add views.
spatial = vc.add_view("spatialBeta", dataset=dataset)
layer_controller = vc.add_view("layerControllerBeta", dataset=dataset)
vc.link_views_by_dict([spatial, layer_controller], {
'imageLayer': CL([{
'photometricInterpretation': 'BlackIsZero',
'imageChannel': CL([{
'spatialTargetC': 0,
'spatialChannelColor': [255, 255, 255],
'spatialChannelWindow': [0, 4000],
}])
}]),
}, scope_prefix=get_initial_coordination_scope_prefix("A", "image"))
# Layout the views.
vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));
[ ]:
vc.widget()
Data location options
Vitessce can visualize data not only stored locally (referenced using local file or directory paths) but also stored remotely (referenced using absolute URL paths). Depending on whether the Python kernel running the Jupyter process is running locally versus remotely (e.g., on a cluster or cloud platform such as Google Colab), certain data locations may be challenging to access from the machine running the Jupyter notebook frontend (i.e., the machine on which the web browser used to view the notebook is installed).
To provide data via one of these alternative mechanisms, use parameters with the following suffices when instantiating the data wrapper classes.
_path
: Local file or directory_url
: Remote file or directory_store
: Zarr-store-accessible (for zarr-based formats)_artifact
: Lamin artifact
For example, adata_path
can be exchanged for one of the following options.
AnnDataWrapper(
- adata_path="./mouse_liver.spatialdata.zarr",
+ adata_url="https://example.com/mouse_liver.spatialdata.zarr", # Absolute URL
+ adata_store="./mouse_liver.spatialdata.zarr", # String interpreted as root of DirectoryStore
+ adata_store=zarr.DirectoryStore("./mouse_liver.spatialdata.zarr"), # Instance of zarr.storage
+ adata_artifact=adata_zarr_artifact, # Instance of ln.Artifact
...
Note that the _store
options are only available for Zarr-based formats, such as AnnDataWrapper, SpatialDataWrapper, and ImageOmeZarrWrapper. Further, when multiple files are required, such as both adata_path
and ref_path
(for the JSON reference specification file accompanying an H5AD file) or img_path
and offsets_path
, multiple parameter suffices may need to be changed.