Skip to content

Commit 2abab8d

Browse files
committed
Update 02 notebook
1 parent 73f65b6 commit 2abab8d

1 file changed

Lines changed: 352 additions & 8 deletions

File tree

notebooks/02-Multiple-Views.ipynb

Lines changed: 352 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
"source": [
1010
"# Session 2: Multiple Views\n",
1111
"\n",
12-
"Welcome to the second part of the hands-on tutorial! In this notebook, we:\n",
12+
"A single spatial view can display layered images, segmentations, spots, and points.\n",
13+
"However, Vitessce is designed to display **multiple views simultaneously**. Each view may show different data, or a different visual representation of the same underlying data.\n",
1314
"\n",
14-
"- Introduce additional view types from Vitessce\n",
15+
"In this notebook we:\n",
1516
"\n",
16-
"- Demonstrate how to arrange multiple views together in the grid using the layout function\n",
17-
"\n",
18-
"To get started, make sure you have `vitessce` installed. "
17+
"1. Introduce additional Vitessce view types\n",
18+
"2. Learn how to arrange views with the layout function\n",
19+
"3. Build a multi-view dashboard for a single-cell dataset\n",
20+
"4. Combine spatial and non-spatial views for a spatial dataset\n",
21+
"5. Learn how these topics relate to EasyVitessce"
1922
]
2023
},
2124
{
@@ -27,13 +30,354 @@
2730
},
2831
"outputs": [],
2932
"source": [
30-
"!pip install \"vitessce[all]==3.9.0\""
33+
"!pip install \"vitessce[all]==3.9.2\" \"scanpy\" \"easy-vitessce==0.0.11\""
34+
]
35+
},
36+
{
37+
"cell_type": "markdown",
38+
"id": "1df4c734-1142-4929-99d0-15315f1ec0e1",
39+
"metadata": {},
40+
"source": [
41+
"## View types overview\n",
42+
"\n",
43+
"Vitessce provides many view types. The most commonly used ones for single-cell and spatial data are:\n",
44+
"\n",
45+
"| View type constant | String name | What it shows |\n",
46+
"|---|---|---|\n",
47+
"| `vt.SCATTERPLOT` | `\"scatterplot\"` | 2D embedding (UMAP, PCA, t-SNE) |\n",
48+
"| `vt.HEATMAP` | `\"heatmap\"` | Cell-by-gene expression heatmap |\n",
49+
"| `vt.OBS_SETS` | `\"obsSets\"` | View and select cell types or clusters |\n",
50+
"| `vt.FEATURE_LIST` | `\"featureList\"` | Searchable gene list |\n",
51+
"| `vt.OBS_SET_FEATURE_VALUE_DISTRIBUTION` | `\"obsSetFeatureValueDistribution\"` | Violin plot per observation set (i.e., cell type or cluster) |\n",
52+
"| `vt.DOT_PLOT` | `\"dotPlot\"` | Dot plot (mean expression and percent-expressing for multiple cell types and genes) |\n",
53+
"| `\"spatialBeta\"` | `\"spatialBeta\"` | Spatial and imaging view |\n",
54+
"| `\"layerControllerBeta\"` | `\"layerControllerBeta\"` | Spatial layer and channel controls |\n",
55+
"\n",
56+
"The `vt` view type constants can be imported with `from vitessce import ViewType as vt`."
57+
]
58+
},
59+
{
60+
"cell_type": "markdown",
61+
"id": "cbba046a",
62+
"metadata": {},
63+
"source": [
64+
"## Layout operators\n",
65+
"\n",
66+
"The `VitessceConfig.layout` function supports using Python's `/` and `|` operators to compose views into a 12-by-12 grid:\n",
67+
"\n",
68+
"| Expression | Result |\n",
69+
"|---|---|\n",
70+
"| `view1 \\| view2` | `view1` and `view2` side-by-side (horizontal split) |\n",
71+
"| `view1 / view2` | `view1` above `view2` below (vertical split) |\n",
72+
"| `view1 \\| (view2 / view3)` | `view1` on the top half; `view2` bottom-left; `view3` on the bottom-right |\n",
73+
"| `(view1 \\| view2) / view3` | `view1` and `view2` side-by-side on top; `view3` spans the full width below |\n",
74+
"\n",
75+
"```python\n",
76+
"# Pass the layout expression to config.layout()\n",
77+
"config.layout(umap | (obs_sets / feature_list))\n",
78+
"```\n",
79+
"\n",
80+
"The `/` operator here is not division -- Vitessce overrides it on view objects to mean \"above / below\".\n",
81+
"\n",
82+
"### `hconcat` and `vconcat`\n",
83+
"\n",
84+
"The slash and pipe operators are concise, but due to being operators, they short-circuit so `A | B | C` will effectively result in `(A | B) | C`. To avoid this issue, you can use the `hconcat` (\"horizontally concatenate\") and `vconcat` (\"vertically concatenate\") functions.\n",
85+
"\n",
86+
"```python\n",
87+
"from vitessce import hconcat, vconcat\n",
88+
"\n",
89+
"config.layout(hconcat(umap, vconcat(obs_sets, feature_list))) # equivalent to the above\n",
90+
"```\n",
91+
"\n",
92+
"Further, `hconcat` and `vconcat` support providing a `split` parameter to control the \"weight\" of each view when being concatenated together to fit into the grid space.\n",
93+
"\n",
94+
"For example, `hconcat(view1, view2, view3, split=[1, 2, 1])` will result in `view2` being twice as wide as `view1` and `view3`."
95+
]
96+
},
97+
{
98+
"cell_type": "markdown",
99+
"id": "ca53d623",
100+
"metadata": {},
101+
"source": [
102+
"## Example 1: PBMC single-cell data\n",
103+
"\n",
104+
"We will use the [PBMC 68k reduced](https://scanpy.readthedocs.io/en/stable/generated/scanpy.datasets.pbmc68k_reduced.html) dataset that ships with Scanpy. It contains ~500 peripheral blood mononuclear cells (PBMCs) with precomputed UMAP and PCA embeddings and Leiden cluster labels.\n",
105+
"\n",
106+
"### Step 1 — Load and prepare the data\n",
107+
"\n",
108+
"The `AnnDataWrapper` expects an [AnnData Zarr store](https://anndata.readthedocs.io/en/latest/fileformat-prose.html) on disk."
31109
]
32110
},
33111
{
34112
"cell_type": "code",
35113
"execution_count": null,
36-
"id": "1df4c734-1142-4929-99d0-15315f1ec0e1",
114+
"id": "5d25a246",
115+
"metadata": {},
116+
"outputs": [],
117+
"source": [
118+
"import os\n",
119+
"from os.path import join, isdir\n",
120+
"import scanpy as sc\n",
121+
"\n",
122+
"from vitessce.data_utils import optimize_adata, VAR_CHUNK_SIZE\n",
123+
"\n",
124+
"# Load Scanpy's built-in reduced PBMC dataset.\n",
125+
"adata = sc.datasets.pbmc68k_reduced()\n",
126+
"print(adata)\n",
127+
"print(\"\\nAvailable embeddings:\", list(adata.obsm.keys()))\n",
128+
"print(\"Available obs columns:\", list(adata.obs.columns))"
129+
]
130+
},
131+
{
132+
"cell_type": "code",
133+
"execution_count": null,
134+
"id": "cfbb275d",
135+
"metadata": {},
136+
"outputs": [],
137+
"source": [
138+
"zarr_path = join(\"data\", \"pbmc68k.zarr\")\n",
139+
"\n",
140+
"if not isdir(zarr_path):\n",
141+
" os.makedirs(\"data\", exist_ok=True)\n",
142+
" adata.write_zarr(zarr_path, chunks=[adata.shape[0], VAR_CHUNK_SIZE])\n",
143+
"\n",
144+
"print(\"Zarr store ready:\", zarr_path)"
145+
]
146+
},
147+
{
148+
"cell_type": "markdown",
149+
"id": "c747cf72",
150+
"metadata": {},
151+
"source": [
152+
"### Step 2 — Build the configuration\n",
153+
"\n",
154+
"`AnnDataWrapper` tells Vitessce how to read each part of the AnnData object:\n",
155+
"\n",
156+
"- `obs_set_paths` / `obs_set_names` — the cell cluster columns (shown in the OBS_SETS view)\n",
157+
"- `obs_embedding_paths` / `obs_embedding_names` — the dimensionality-reduction arrays (shown as scatterplots)\n",
158+
"- `obs_feature_matrix_path` — the gene expression matrix (shown in the heatmap and used for gene coloring)"
159+
]
160+
},
161+
{
162+
"cell_type": "code",
163+
"execution_count": null,
164+
"id": "653a5951",
165+
"metadata": {},
166+
"outputs": [],
167+
"source": [
168+
"from vitessce import VitessceConfig, ViewType as vt, AnnDataWrapper\n",
169+
"\n",
170+
"vc = VitessceConfig(schema_version=\"1.0.18\", name=\"PBMC 68k dataset with Multiple Views\")\n",
171+
"\n",
172+
"dataset = vc.add_dataset(name=\"PBMC 68k\").add_object(\n",
173+
" AnnDataWrapper(\n",
174+
" adata_store=zarr_path,\n",
175+
" obs_set_paths=[\"obs/bulk_labels\", \"obs/louvain\"],\n",
176+
" obs_set_names=[\"Cell Type\", \"Leiden Cluster\"],\n",
177+
" obs_embedding_paths=[\"obsm/X_umap\", \"obsm/X_pca\"],\n",
178+
" obs_embedding_names=[\"UMAP\", \"PCA\"],\n",
179+
" obs_feature_matrix_path=\"X\",\n",
180+
" )\n",
181+
")\n",
182+
"\n",
183+
"# --- Define views ---\n",
184+
"umap = vc.add_view(vt.SCATTERPLOT, dataset=dataset)\n",
185+
"pca = vc.add_view(vt.SCATTERPLOT, dataset=dataset)\n",
186+
"obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n",
187+
"genes = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n",
188+
"heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)\n",
189+
"\n",
190+
"vc.link_views_by_dict([umap], { \"embeddingType\": \"UMAP\" })\n",
191+
"vc.link_views_by_dict([pca], { \"embeddingType\": \"PCA\" })\n",
192+
"\n",
193+
"# --- Define the layout ---\n",
194+
"# UMAP on the top-left, PCA top-right, cell sets and gene list bottom-right, heatmap on the bottom-left\n",
195+
"vc.layout((umap | pca) / (heatmap | (obs_sets / genes)))\n",
196+
"vc.widget()"
197+
]
198+
},
199+
{
200+
"cell_type": "markdown",
201+
"id": "7e89a7ea",
202+
"metadata": {},
203+
"source": [
204+
"### Exercises\n",
205+
"\n",
206+
"👉 **Modify the layout expression above** so that the heatmap takes up the full width or height of the grid.\n",
207+
"\n",
208+
"👉 **Your own layout:** Rearrange the five views (`umap`, `pca`, `obs_sets`, `genes`, `heatmap`) any way you like using `|` and `/` or `hconcat` and `vconcat`."
209+
]
210+
},
211+
{
212+
"cell_type": "markdown",
213+
"id": "06be7298",
214+
"metadata": {},
215+
"source": [
216+
"## Example 2: Combining spatial and non-spatial views\n",
217+
"\n",
218+
"Spatial transcriptomics datasets have both a tissue image (the spatial component) and a gene expression matrix (the non-spatial component). Vitessce can show both in the same dashboard.\n",
219+
"\n",
220+
"We will re-use the Visium SpatialData object from Session 1. If you haven't run Session 1 yet, run the download cell from that notebook first to create `data/visium.spatialdata.zarr`.\n",
221+
"\n",
222+
"The `SpatialDataWrapper` supports all the same AnnData-style fields as `AnnDataWrapper`, so we can add a UMAP scatterplot alongside the spatial view — provided the SpatialData table contains an embedding."
223+
]
224+
},
225+
{
226+
"cell_type": "code",
227+
"execution_count": null,
228+
"id": "021a2503",
229+
"metadata": {},
230+
"outputs": [],
231+
"source": [
232+
"import os\n",
233+
"from os.path import join, isfile, isdir\n",
234+
"from urllib.request import urlretrieve\n",
235+
"import zipfile\n",
236+
"\n",
237+
"data_dir = \"data\"\n",
238+
"sdata_filepath = join(data_dir, \"visium.spatialdata.zarr\")\n",
239+
"\n",
240+
"# Download if not already present (same code as Session 1)\n",
241+
"if not isdir(sdata_filepath):\n",
242+
" zip_filepath = join(data_dir, \"visium.spatialdata.zarr.zip\")\n",
243+
" if not isfile(zip_filepath):\n",
244+
" os.makedirs(data_dir, exist_ok=True)\n",
245+
" urlretrieve(\n",
246+
" \"https://data-2.vitessce.io/sdata-datasets/visium.spatialdata.zarr.zip\",\n",
247+
" zip_filepath,\n",
248+
" )\n",
249+
" with zipfile.ZipFile(zip_filepath, \"r\") as zip_ref:\n",
250+
" zip_ref.extractall(data_dir)\n",
251+
" os.rename(join(data_dir, \"data.zarr\"), sdata_filepath)\n",
252+
"\n",
253+
"print(\"Visium dataset ready.\")"
254+
]
255+
},
256+
{
257+
"cell_type": "code",
258+
"execution_count": null,
259+
"id": "891306c7",
260+
"metadata": {},
261+
"outputs": [],
262+
"source": [
263+
"from vitessce import (\n",
264+
" VitessceConfig,\n",
265+
" ViewType as vt,\n",
266+
" SpatialDataWrapper,\n",
267+
" vconcat, hconcat,\n",
268+
")\n",
269+
"\n",
270+
"vc2 = VitessceConfig(schema_version=\"1.0.18\", name=\"Visium example with Spatial + Non-Spatial Views\")\n",
271+
"\n",
272+
"wrapper = SpatialDataWrapper(\n",
273+
" sdata_store=sdata_filepath,\n",
274+
" image_path=\"images/ST8059050_hires_image\",\n",
275+
" obs_spots_path=\"shapes/ST8059050\",\n",
276+
" table_path=\"tables/table\",\n",
277+
" obs_feature_matrix_path=\"tables/table/X\",\n",
278+
" coordinate_system=\"ST8059050\",\n",
279+
" coordination_values={\"obsType\": \"spot\"},\n",
280+
")\n",
281+
"dataset2 = vc2.add_dataset(name=\"Visium\").add_object(wrapper)\n",
282+
"\n",
283+
"# Spatial views\n",
284+
"spatial = vc2.add_view(\"spatialBeta\", dataset=dataset2)\n",
285+
"lc = vc2.add_view(\"layerControllerBeta\", dataset=dataset2)\n",
286+
"\n",
287+
"# Non-spatial views\n",
288+
"obs_sets2 = vc2.add_view(vt.OBS_SETS, dataset=dataset2)\n",
289+
"genes2 = vc2.add_view(vt.FEATURE_LIST, dataset=dataset2)\n",
290+
"heatmap2 = vc2.add_view(vt.HEATMAP, dataset=dataset2)\n",
291+
"\n",
292+
"# Link obsType so all views refer to \"spot\"\n",
293+
"vc2.link_views([spatial, lc, obs_sets2, genes2, heatmap2], [\"obsType\"], [wrapper.obs_type_label])\n",
294+
"\n",
295+
"vc2.layout(spatial | (vconcat(lc, obs_sets2, genes2) / heatmap2))\n",
296+
"vc2.widget()"
297+
]
298+
},
299+
{
300+
"cell_type": "markdown",
301+
"id": "ff951ca3",
302+
"metadata": {},
303+
"source": [
304+
"## The EasyVitessce approach\n",
305+
"\n",
306+
"Similar PBMC visualizations can be created with far less code using [EasyVitessce](https://vitessce.github.io/easy_vitessce/easy_vitessce.html)'s support for the Scanpy plotting APIs. When `easy_vitessce` is imported, Scanpy functions like `sc.pl.embedding()` and `sc.pl.dotplot()` automatically return interactive Vitessce widgets instead of static matplotlib figures."
307+
]
308+
},
309+
{
310+
"cell_type": "code",
311+
"execution_count": null,
312+
"id": "a8c4dec6",
313+
"metadata": {},
314+
"outputs": [],
315+
"source": [
316+
"import easy_vitessce as ev # intercepts scanpy plotting calls\n",
317+
"import scanpy as sc\n",
318+
"\n",
319+
"# This line is required when the notebook kernel is running on a different machine (e.g., Google Colab, Docker container, or HPC cluster)\n",
320+
"ev.config.set({ 'data.wrapper_param_suffix': '_store' })\n",
321+
"\n",
322+
"adata = sc.datasets.pbmc68k_reduced()\n",
323+
"\n",
324+
"# Interactive UMAP colored by cell type\n",
325+
"sc.pl.embedding(adata, basis=\"umap\", color=\"bulk_labels\")"
326+
]
327+
},
328+
{
329+
"cell_type": "code",
330+
"execution_count": null,
331+
"id": "06d50740",
332+
"metadata": {},
333+
"outputs": [],
334+
"source": [
335+
"# Show multiple genes side-by-side in separate panels\n",
336+
"sc.pl.embedding(adata, basis=\"umap\", color=[\"CD79A\", \"CD53\", \"CLIC1\", \"ANXA1\"], ncols=2)"
337+
]
338+
},
339+
{
340+
"cell_type": "markdown",
341+
"id": "6ef364a5",
342+
"metadata": {},
343+
"source": [
344+
"### Exercise 2\n",
345+
"\n",
346+
"👉 **Try the following modifications:**\n",
347+
"\n",
348+
"1. **Change the coloring** in `sc.pl.embedding()` from `\"bulk_labels\"` to a gene name such as `\"CST3\"` or `\"LYZ\"`.\n",
349+
"\n",
350+
"2. **Add a dot plot** by running the cell below. A dot plot shows mean expression and percentage of expressing cells for a selected set of genes grouped by cell type.\n",
351+
"\n",
352+
"```python\n",
353+
"sc.pl.dotplot(\n",
354+
" adata,\n",
355+
" var_names=[\"CD79A\", \"CD79B\", \"CST3\", \"LYZ\", \"PSAP\"],\n",
356+
" groupby=\"bulk_labels\",\n",
357+
")\n",
358+
"```\n",
359+
"\n",
360+
"3. **Add a violin plot** showing the expression distribution of one gene across cell types:\n",
361+
"\n",
362+
"```python\n",
363+
"sc.pl.violin(adata, keys=\"LYZ\", groupby=\"bulk_labels\")\n",
364+
"```\n"
365+
]
366+
},
367+
{
368+
"cell_type": "code",
369+
"execution_count": null,
370+
"id": "10858a23",
371+
"metadata": {},
372+
"outputs": [],
373+
"source": [
374+
"sc.pl.embedding(adata, basis=\"umap\", color=\"???\") # Change the color"
375+
]
376+
},
377+
{
378+
"cell_type": "code",
379+
"execution_count": null,
380+
"id": "eadb6102-7fe7-4846-b23b-45a1ac281b81",
37381
"metadata": {},
38382
"outputs": [],
39383
"source": []
@@ -65,7 +409,7 @@
65409
"name": "python",
66410
"nbconvert_exporter": "python",
67411
"pygments_lexer": "ipython3",
68-
"version": "3.8.16"
412+
"version": "3.12.5"
69413
}
70414
},
71415
"nbformat": 4,

0 commit comments

Comments
 (0)