From 18b73a6c7ef679c83e307aef8eba98b96518f5ad Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:40:51 -0500 Subject: [PATCH 1/6] WIP: add prefer_local option --- src/vitessce/config.py | 8 ++++---- src/vitessce/widget.py | 4 ++-- src/vitessce/wrappers.py | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/vitessce/config.py b/src/vitessce/config.py index b19f714e..6d0478af 100644 --- a/src/vitessce/config.py +++ b/src/vitessce/config.py @@ -242,12 +242,12 @@ def get_artifacts(self): return artifacts - def get_stores(self, base_url=None): + def get_stores(self, base_url=None, prefer_local=False): stores = {} for obj in self.objs: stores = { **stores, - **obj.get_stores(base_url) + **obj.get_stores(base_url, prefer_local=prefer_local) } return stores @@ -1615,7 +1615,7 @@ def get_artifacts(self): artifacts.update(d.get_artifacts()) return artifacts - def get_stores(self, base_url=None): + def get_stores(self, base_url=None, prefer_local=False): """ Convert the routes for this view config from the datasets. @@ -1626,7 +1626,7 @@ def get_stores(self, base_url=None): for d in self.config["datasets"]: stores = { **stores, - **d.get_stores(base_url) + **d.get_stores(base_url, prefer_local=prefer_local) } return stores diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index ed3b7772..61039ffc 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -472,7 +472,7 @@ class VitessceWidget(anywidget.AnyWidget): store_urls = List(trait=Unicode(''), default_value=[]).tag(sync=True) - def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.4', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, invoke_timeout=30000): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.4', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=30000): """ Construct a new Vitessce widget. @@ -506,7 +506,7 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= config_dict = config.to_dict(base_url=base_url) routes = config.get_routes() - self._stores = config.get_stores(base_url=base_url) + self._stores = config.get_stores(base_url=base_url, prefer_local=prefer_local) self._plugins = plugins or [] plugin_esm = [p.plugin_esm for p in self._plugins] diff --git a/src/vitessce/wrappers.py b/src/vitessce/wrappers.py index 70948914..1fa119f6 100644 --- a/src/vitessce/wrappers.py +++ b/src/vitessce/wrappers.py @@ -126,8 +126,17 @@ def get_artifacts(self): :rtype: dict[str, lamindb.Artifact] """ return self.artifacts - - def get_stores(self, base_url): + + def try_getting_artifact_stores(self): + artifact_stores = dict() + for artifact_url, artifact in self.artifacts.items(): + local_path = artifact._local_filepath + if os.path.isdir(local_path): + store = zarr.DirectoryStore(local_path) + artifact_stores[artifact_url] = store + return artifact_stores + + def get_stores(self, base_url, prefer_local=False): """ Obtain the stores that have been created for this wrapper class. @@ -139,6 +148,8 @@ def get_stores(self, base_url): for relative_url, store in relative_stores.items(): absolute_url = base_url + relative_url absolute_stores[absolute_url] = store + if prefer_local: + absolute_stores.update(self.try_getting_artifact_stores()) return absolute_stores def get_file_defs(self, base_url): From d55d2a04a03a4c53f8addb00a7d3964924ecbd14 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:42:57 -0500 Subject: [PATCH 2/6] Update --- src/vitessce/widget.py | 1 + src/vitessce/wrappers.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index 61039ffc..fd673a4a 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -487,6 +487,7 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= :param str custom_js_url: A URL to a JavaScript file to use (instead of 'vitessce' or '@vitessce/dev' NPM package). :param list[WidgetPlugin] plugins: A list of subclasses of VitesscePlugin. Optional. :param bool remount_on_uid_change: Passed to the remountOnUidChange prop of the React component. By default, True. + :param bool prefer_local: Should local data be preferred (only applies to `*_artifact` data objects)? By default, True. :param int invoke_timeout: The timeout in milliseconds for invoking Python functions from JavaScript. By default, 30000. .. code-block:: python diff --git a/src/vitessce/wrappers.py b/src/vitessce/wrappers.py index 1fa119f6..7f7a91c2 100644 --- a/src/vitessce/wrappers.py +++ b/src/vitessce/wrappers.py @@ -131,7 +131,7 @@ def try_getting_artifact_stores(self): artifact_stores = dict() for artifact_url, artifact in self.artifacts.items(): local_path = artifact._local_filepath - if os.path.isdir(local_path): + if local_path is not None and os.path.isdir(local_path): store = zarr.DirectoryStore(local_path) artifact_stores[artifact_url] = store return artifact_stores From 741b64298a043a194a5261a814de8eaeaa909f29 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:38:06 -0400 Subject: [PATCH 3/6] Add spatialdata notebook --- docs/notebooks/spatial_data_mouseliver.ipynb | 192 +++++++++++++++++++ src/vitessce/widget.py | 6 +- 2 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 docs/notebooks/spatial_data_mouseliver.ipynb diff --git a/docs/notebooks/spatial_data_mouseliver.ipynb b/docs/notebooks/spatial_data_mouseliver.ipynb new file mode 100644 index 00000000..8d80dd0c --- /dev/null +++ b/docs/notebooks/spatial_data_mouseliver.ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "# Vitessce Widget Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Visualization of a SpatialData object\n", + "\n", + "Data from https://github.com/scverse/spatialdata-notebooks/blob/main/notebooks/examples/transformations.ipynb" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Import dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from os.path import join, isfile, isdir\n", + "from urllib.request import urlretrieve\n", + "import zipfile\n", + "\n", + "from vitessce import (\n", + " VitessceConfig,\n", + " ViewType as vt,\n", + " CoordinationType as ct,\n", + " CoordinationLevel as CL,\n", + " SpatialDataWrapper,\n", + " get_initial_coordination_scope_prefix\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = \"data\"\n", + "zip_filepath = join(data_dir, \"mouse_liver.spatialdata.zarr.zip\")\n", + "spatialdata_filepath = join(data_dir, \"mouse_liver.spatialdata.zarr\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not isdir(spatialdata_filepath):\n", + " if not isfile(zip_filepath):\n", + " os.makedirs(data_dir, exist_ok=True)\n", + " urlretrieve('https://s3.embl.de/spatialdata/spatialdata-sandbox/mouse_liver.zip', zip_filepath)\n", + " with zipfile.ZipFile(zip_filepath,\"r\") as zip_ref:\n", + " zip_ref.extractall(data_dir)\n", + " os.rename(join(data_dir, \"data.zarr\"), spatialdata_filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure Vitessce\n", + "\n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc = VitessceConfig(\n", + " schema_version=\"1.0.17\",\n", + " name='SpatialData Demo (mouse_liver)',\n", + ")\n", + "# Add data to the configuration:\n", + "wrapper = SpatialDataWrapper(\n", + " sdata_store=spatialdata_filepath,\n", + " # The following paths are relative to the root of the SpatialData zarr store on-disk.\n", + " table_path=\"tables/table\",\n", + " image_path=\"images/raw_image\",\n", + " labels_path=\"labels/segmentation_mask\",\n", + " #shapes_path=\"shapes/nucleus_boundaries\",\n", + " #points_path=\"points/transcripts\",\n", + " obs_feature_matrix_path=\"tables/table/X\",\n", + " obs_set_paths=[\"tables/table/obs/annotation\"],\n", + " obs_set_names=[\"Annotation\"],\n", + " region=\"nucleus_boundaries\",\n", + " coordinate_system=\"global\",\n", + " coordination_values={\n", + " \"obsType\": \"cell\" \n", + " }\n", + ")\n", + "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", + "\n", + "# Add views (visualizations) to the configuration:\n", + "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", + "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", + "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", + "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", + "\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'imageLayer': CL([{\n", + " 'photometricInterpretation': 'BlackIsZero',\n", + " 'imageChannel': CL([{\n", + " 'spatialChannelColor': [255, 255, 255],\n", + " 'spatialChannelWindow': [0, 4000],\n", + " }])\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'segmentationLayer': CL([{\n", + " 'segmentationChannel': CL([{\n", + " 'obsColorEncoding': 'cellSetSelection',\n", + " }]),\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"obsSegmentations\"))\n", + "\n", + "vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], [wrapper.obs_type_label])\n", + "\n", + "# Layout the views\n", + "vc.layout(spatial | (feature_list / layer_controller / obs_sets));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Render the widget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vw = vc.widget(custom_js_url=\"http://localhost:3001/packages/main/dev/dist/index.js\")\n", + "vw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index fd673a4a..709a4850 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -463,7 +463,7 @@ class VitessceWidget(anywidget.AnyWidget): next_port = DEFAULT_PORT - js_package_version = Unicode('3.5.4').tag(sync=True) + js_package_version = Unicode('3.5.10').tag(sync=True) js_dev_mode = Bool(False).tag(sync=True) custom_js_url = Unicode('').tag(sync=True) plugin_esm = List(trait=Unicode(''), default_value=[]).tag(sync=True) @@ -472,7 +472,7 @@ class VitessceWidget(anywidget.AnyWidget): store_urls = List(trait=Unicode(''), default_value=[]).tag(sync=True) - def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.4', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=30000): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.10', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=30000): """ Construct a new Vitessce widget. @@ -586,7 +586,7 @@ def _plugin_command(self, params, buffers): # Launch Vitessce using plain HTML representation (no ipywidgets) -def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.5.4', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): +def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.5.10', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): from IPython.display import display, HTML uid_str = "vitessce" + get_uid_str(uid) From c569c4331afec59b00c7734c6878cbc53088e660 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:39:10 -0400 Subject: [PATCH 4/6] Lint --- src/vitessce/wrappers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vitessce/wrappers.py b/src/vitessce/wrappers.py index 7f7a91c2..795da13d 100644 --- a/src/vitessce/wrappers.py +++ b/src/vitessce/wrappers.py @@ -126,7 +126,7 @@ def get_artifacts(self): :rtype: dict[str, lamindb.Artifact] """ return self.artifacts - + def try_getting_artifact_stores(self): artifact_stores = dict() for artifact_url, artifact in self.artifacts.items(): From bad7f397d080891a4488a821ebbe0617bd9a6e8b Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Fri, 18 Apr 2025 17:26:50 -0400 Subject: [PATCH 5/6] WIP: spatialdata_mouseliver notebook --- docs/notebooks/spatial_data_mouseliver.ipynb | 449 ++++++++++++++++++- src/vitessce/data_utils/ome.py | 2 +- 2 files changed, 434 insertions(+), 17 deletions(-) diff --git a/docs/notebooks/spatial_data_mouseliver.ipynb b/docs/notebooks/spatial_data_mouseliver.ipynb index 8d80dd0c..1147c0e8 100644 --- a/docs/notebooks/spatial_data_mouseliver.ipynb +++ b/docs/notebooks/spatial_data_mouseliver.ipynb @@ -13,18 +13,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Visualization of a SpatialData object\n", - "\n", - "Data from https://github.com/scverse/spatialdata-notebooks/blob/main/notebooks/examples/transformations.ipynb" + "## Utility dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from os.path import join, isfile, isdir\n", + "from urllib.request import urlretrieve\n", + "import zipfile\n", + "import json" ] }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ - "## Import dependencies" + "## Dependencies for Vitessce" ] }, { @@ -33,21 +42,75 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from os.path import join, isfile, isdir\n", - "from urllib.request import urlretrieve\n", - "import zipfile\n", - "\n", "from vitessce import (\n", " VitessceConfig,\n", " ViewType as vt,\n", " CoordinationType as ct,\n", " CoordinationLevel as CL,\n", " SpatialDataWrapper,\n", - " get_initial_coordination_scope_prefix\n", + " AnnDataWrapper,\n", + " ImageOmeTiffWrapper,\n", + " ImageOmeZarrWrapper,\n", + " ObsSegmentationsOmeZarrWrapper,\n", + " get_initial_coordination_scope_prefix,\n", + " hconcat,\n", + " vconcat,\n", + ")\n", + "from vitessce.data_utils import (\n", + " VAR_CHUNK_SIZE,\n", + " generate_h5ad_ref_spec,\n", + " multiplex_img_to_ome_tiff,\n", + " multiplex_img_to_ome_zarr,\n", ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dependencies for data structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from spatialdata import read_zarr\n", + "from anndata import AnnData\n", + "from ome_zarr.writer import write_image\n", + "import tifffile\n", + "from generate_tiff_offsets import get_offsets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Organization\n", + "- SpatialData (via Zarr)\n", + "- AnnData via Zarr\n", + "- AnnData via H5AD\n", + "- OME-Zarr\n", + "- OME-TIFF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download example dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data from https://github.com/scverse/spatialdata-notebooks/blob/main/notebooks/examples/transformations.ipynb" + ] + }, { "cell_type": "code", "execution_count": null, @@ -56,7 +119,14 @@ "source": [ "data_dir = \"data\"\n", "zip_filepath = join(data_dir, \"mouse_liver.spatialdata.zarr.zip\")\n", - "spatialdata_filepath = join(data_dir, \"mouse_liver.spatialdata.zarr\")" + "spatialdata_filepath = join(data_dir, \"mouse_liver.spatialdata.zarr\")\n", + "adata_zarr_filepath = join(data_dir, \"mouse_liver.anndata.zarr\")\n", + "adata_h5ad_filepath = join(data_dir, \"mouse_liver.h5ad\")\n", + "ref_spec_json_filepath = join(data_dir, \"mouse_liver.h5ad.ref.json\")\n", + "ome_tiff_filepath = join(data_dir, \"mouse_liver.ome.tif\")\n", + "offsets_json_filepath = join(data_dir, \"mouse_liver.ome.tif.offsets.json\")\n", + "ome_zarr_filepath = join(data_dir, \"mouse_liver.ome.zarr\")\n", + "labels_ome_zarr_filepath = join(data_dir, \"mouse_liver.labels.ome.zarr\")" ] }, { @@ -74,6 +144,15 @@ " os.rename(join(data_dir, \"data.zarr\"), spatialdata_filepath)" ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "# Visualization of a SpatialData object" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -95,7 +174,7 @@ ")\n", "# Add data to the configuration:\n", "wrapper = SpatialDataWrapper(\n", - " sdata_store=spatialdata_filepath,\n", + " sdata_path=spatialdata_filepath,\n", " # The following paths are relative to the root of the SpatialData zarr store on-disk.\n", " table_path=\"tables/table\",\n", " image_path=\"images/raw_image\",\n", @@ -123,6 +202,7 @@ " 'imageLayer': CL([{\n", " 'photometricInterpretation': 'BlackIsZero',\n", " 'imageChannel': CL([{\n", + " 'spatialTargetC': 0,\n", " 'spatialChannelColor': [255, 255, 255],\n", " 'spatialChannelWindow': [0, 4000],\n", " }])\n", @@ -156,10 +236,347 @@ "metadata": {}, "outputs": [], "source": [ - "vw = vc.widget(custom_js_url=\"http://localhost:3001/packages/main/dev/dist/index.js\")\n", + "vw = vc.widget(invoke_timeout=300000)\n", "vw" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract AnnData object from SpatialData object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sdata = read_zarr(spatialdata_filepath)\n", + "sdata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "adata = sdata.tables['table']\n", + "adata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "adata.write_zarr(adata_zarr_filepath, chunks=(adata.shape[0], VAR_CHUNK_SIZE))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "adata.write_h5ad(adata_h5ad_filepath)\n", + "ref_dict = generate_h5ad_ref_spec(adata_h5ad_filepath)\n", + "with open(ref_spec_json_filepath, \"w\") as f:\n", + " json.dump(ref_dict, f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Visualization of an AnnData object" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Zarr-based AnnData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc = VitessceConfig(\n", + " schema_version=\"1.0.17\",\n", + " name='AnnData (zarr)',\n", + ")\n", + "# Add data to the configuration:\n", + "wrapper = AnnDataWrapper(\n", + " adata_path=adata_zarr_filepath,\n", + " obs_feature_matrix_path=\"X\",\n", + " obs_set_paths=[\"obs/annotation\"],\n", + " obs_set_names=[\"Annotation\"],\n", + " coordination_values={\n", + " \"obsType\": \"cell\" \n", + " }\n", + ")\n", + "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", + "\n", + "# Add views (visualizations) to the configuration:\n", + "heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)\n", + "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", + "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", + "violin_plots = vc.add_view(\"obsSetFeatureValueDistribution\", dataset=dataset)\n", + "\n", + "vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])\n", + "\n", + "# Layout the views\n", + "vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc.widget()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### H5AD-based AnnData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc = VitessceConfig(\n", + " schema_version=\"1.0.17\",\n", + " name='AnnData (h5ad)',\n", + ")\n", + "# Add data to the configuration:\n", + "wrapper = AnnDataWrapper(\n", + " adata_path=adata_h5ad_filepath,\n", + " ref_path=ref_spec_json_filepath,\n", + " obs_feature_matrix_path=\"X\",\n", + " obs_set_paths=[\"obs/annotation\"],\n", + " obs_set_names=[\"Annotation\"],\n", + " coordination_values={\n", + " \"obsType\": \"cell\" \n", + " }\n", + ")\n", + "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", + "\n", + "# Add views (visualizations) to the configuration:\n", + "heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)\n", + "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", + "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", + "violin_plots = vc.add_view(\"obsSetFeatureValueDistribution\", dataset=dataset)\n", + "\n", + "vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])\n", + "\n", + "# Layout the views\n", + "vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc.widget()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract image from SpatialData object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img_arr = sdata.images['raw_image'].to_numpy()\n", + "labels_arr = sdata.labels['segmentation_mask'].to_numpy()\n", + "labels_arr = labels_arr[np.newaxis, :]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Save image to OME-Zarr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "multiplex_img_to_ome_zarr(img_arr, [\"Channel 0\"], ome_zarr_filepath)\n", + "multiplex_img_to_ome_zarr(labels_arr, [\"cell\"], labels_ome_zarr_filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Save image and segmentations to OME-TIFF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "multiplex_img_to_ome_tiff(img_arr, [\"Channel 0\"], ome_tiff_filepath)\n", + "offsets = get_offsets(ome_tiff_filepath)\n", + "with open(offsets_json_filepath, \"w\") as f:\n", + " json.dump(offsets, f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc = VitessceConfig(\n", + " schema_version=\"1.0.17\",\n", + " name='Image (ome-zarr)',\n", + ")\n", + "# Add data to the configuration:\n", + "img_wrapper = ImageOmeZarrWrapper(\n", + " img_path=ome_zarr_filepath,\n", + " coordination_values={\n", + " \"fileUid\": \"image\",\n", + " }\n", + ")\n", + "segmentations_wrapper = ObsSegmentationsOmeZarrWrapper(\n", + " img_path=labels_ome_zarr_filepath,\n", + " coordination_values={\n", + " \"fileUid\": \"segmentations\",\n", + " }\n", + ")\n", + "dataset = vc.add_dataset(name='Mouse Liver').add_object(img_wrapper).add_object(segmentations_wrapper)\n", + "\n", + "# Add views (visualizations) to the configuration:\n", + "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", + "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", + "\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'imageLayer': CL([{\n", + " 'fileUid': 'image',\n", + " 'photometricInterpretation': 'BlackIsZero',\n", + " 'imageChannel': CL([{\n", + " 'spatialTargetC': 0,\n", + " 'spatialChannelColor': [255, 255, 255],\n", + " 'spatialChannelWindow': [0, 4000],\n", + " }])\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'segmentationLayer': CL([{\n", + " 'fileUid': 'segmentations',\n", + " 'segmentationChannel': CL([{\n", + " 'obsColorEncoding': 'spatialChannelColor',\n", + " 'spatialChannelColor': [0, 255, 0],\n", + " 'spatialChannelOpacity': 0.75,\n", + " 'spatialSegmentationFilled': False,\n", + " 'spatialSegmentationStrokeWidth': 0.25,\n", + " }]),\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"obsSegmentations\"))\n", + "\n", + "vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], ['cell'])\n", + "\n", + "# Layout the views\n", + "vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vw = vc.widget()\n", + "vw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc = VitessceConfig(\n", + " schema_version=\"1.0.17\",\n", + " name='Image and segmentations (ome-tiff)',\n", + ")\n", + "# Add data to the configuration:\n", + "wrapper = ImageOmeTiffWrapper(\n", + " img_path=ome_tiff_filepath,\n", + " offsets_path=offsets_json_filepath\n", + ")\n", + "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", + "\n", + "# Add views (visualizations) to the configuration:\n", + "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", + "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", + "\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'imageLayer': CL([{\n", + " 'photometricInterpretation': 'BlackIsZero',\n", + " 'imageChannel': CL([{\n", + " 'spatialTargetC': 0,\n", + " 'spatialChannelColor': [255, 255, 255],\n", + " 'spatialChannelWindow': [0, 4000],\n", + " }])\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "\n", + "# Layout the views\n", + "vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vc.widget()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data location options\n", + "\n", + "- `_path`: Local file or directory\n", + "- `_url`: Remote file or directory\n", + "- `_store`: Zarr-store-accessible (for zarr-based formats)\n", + "- `_artifact`: Lamin artifact" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/src/vitessce/data_utils/ome.py b/src/vitessce/data_utils/ome.py index 75a0272b..dafb9d13 100644 --- a/src/vitessce/data_utils/ome.py +++ b/src/vitessce/data_utils/ome.py @@ -15,7 +15,7 @@ def needs_bigtiff(img_arr_shape): :rtype: bool """ num_pixels = 1 - for n in img_arr_shape.shape: + for n in img_arr_shape: num_pixels *= n return (num_pixels > 2**32) From 71630d1d476dcf6422fff91e636e9419a0215c05 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:02:10 -0400 Subject: [PATCH 6/6] Update notebook --- docs/notebooks/spatial_data_mouseliver.ipynb | 312 ++++++++++++++----- src/vitessce/widget.py | 8 +- 2 files changed, 246 insertions(+), 74 deletions(-) diff --git a/docs/notebooks/spatial_data_mouseliver.ipynb b/docs/notebooks/spatial_data_mouseliver.ipynb index 1147c0e8..0b8b571a 100644 --- a/docs/notebooks/spatial_data_mouseliver.ipynb +++ b/docs/notebooks/spatial_data_mouseliver.ipynb @@ -3,10 +3,36 @@ { "cell_type": "markdown", "metadata": { - "nbsphinx": "hidden" + "nbsphinx": "hidden", + "tags": [] }, "source": [ - "# Vitessce Widget Tutorial" + "# Interactive visualization of Lamin Artifacts using Vitessce" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This blog post explains how to create interactive visualizations of data that is accessible via Lamin Hub using [Vitessce](https://vitessce.io/).\n", + "Vitessce is an open-source tool designed for interactive visualization of single-cell, spatial 'omics, and bioimaging datasets.\n", + "\n", + "\n", + "In this post, 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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" ] }, { @@ -16,6 +42,13 @@ "## Utility dependencies" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we import utility dependencies which will be used to download the example dataset and manipulate file paths, zip files, and JSON files." + ] + }, { "cell_type": "code", "execution_count": null, @@ -36,6 +69,22 @@ "## Dependencies for Vitessce" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we import classes and functions from the `vitessce` Python package.\n", + "This package includes not only APIs for [visualization configuration](https://python-docs.vitessce.io/api_config.html) but also [helper functions](https://python-docs.vitessce.io/api_data.html#vitessce-data-utils) for basic data transformation tasks.\n", + "To specify mappings between data fields and visualization properties, the package contains [classes](https://python-docs.vitessce.io/api_data.html#module-vitessce.wrappers) which wrap standard single-cell data structures stored in formats including [AnnData](https://doi.org/10.1101/2021.12.16.473007), [SpatialData](https://doi.org/10.1038/s41592-024-02212-x), [OME-TIFF](https://doi.org/10.1007/978-3-030-23937-4_1), and [OME-Zarr](https://doi.org/10.1038/s41592-021-01326-w):\n", + "\n", + "- [AnnDataWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.AnnDataWrapper)\n", + "- [ImageOmeTiffWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.ImageOmeTiffWrapper)\n", + "- [ImageOmeZarrWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.ImageOmeZarrWrapper)\n", + "- [ObsSegmentationsOmeTiffWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.ObsSegmentationsOmeTiffWrapper)\n", + "- [ObsSegmentationsOmeZarrWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.ObsSegmentationsOmeZarrWrapper)\n", + "- [SpatialDataWrapper](https://python-docs.vitessce.io/api_data.html#vitessce.wrappers.SpatialDataWrapper)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -71,6 +120,15 @@ "## Dependencies for data structures" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "To perform these data transformations, we import the following dependencies.\n", + "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." + ] + }, { "cell_type": "code", "execution_count": null, @@ -85,18 +143,6 @@ "from generate_tiff_offsets import get_offsets" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Organization\n", - "- SpatialData (via Zarr)\n", - "- AnnData via Zarr\n", - "- AnnData via H5AD\n", - "- OME-Zarr\n", - "- OME-TIFF" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -108,7 +154,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Data from https://github.com/scverse/spatialdata-notebooks/blob/main/notebooks/examples/transformations.ipynb" + "We download a mouse liver dataset which serves as a SpatialData [example dataset](https://github.com/scverse/spatialdata-notebooks/blob/main/notebooks/examples/transformations.ipynb).\n", + "\n", + "This dataset was generated by [Guilliams et al.](https://doi.org/10.1016/j.cell.2021.12.018) and processed using [SPArrOW](https://doi.org/10.1101/2024.07.04.601829) during the SpatialData [developer workshop](https://doi.org/10.37044/osf.io/8ck3e) in 2024." ] }, { @@ -129,6 +177,13 @@ "labels_ome_zarr_filepath = join(data_dir, \"mouse_liver.labels.ome.zarr\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code uses Python's `urlretrieve` to download the SpatialData object as a zip file, then unzips the file using the `zipfile` module." + ] + }, { "cell_type": "code", "execution_count": null, @@ -153,13 +208,28 @@ "# Visualization of a SpatialData object" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "SpatialData objects are the most complex type of data structure we will work with in this blog post.\n", + "SpatialData objects function as contains for multiple types of Spatial Elements:\n", + "\n", + "- Tables (each table is represented as an AnnData object)\n", + "- Points (e.g., coordinates of transcripts from FISH-based experiments)\n", + "- Shapes (vector-based shapes such as polygons and circles)\n", + "- Labels (label images, i.e., segmentation bitmasks; each label image is stored using OME-Zarr)\n", + "- Images (microscopy images; each image is stored using OME-Zarr)" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Configure Vitessce\n", "\n", - "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." + "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.\n", + "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](https://zarr.readthedocs.io/en/v2.18.5/api/storage.html#zarr.storage.DirectoryStore)) to different spatial elements of interest." ] }, { @@ -168,19 +238,16 @@ "metadata": {}, "outputs": [], "source": [ - "vc = VitessceConfig(\n", - " schema_version=\"1.0.17\",\n", - " name='SpatialData Demo (mouse_liver)',\n", - ")\n", - "# Add data to the configuration:\n", + "# Create a VitessceConfig instance.\n", + "vc = VitessceConfig(schema_version=\"1.0.17\", name=\"SpatialData Demo\")\n", + "\n", + "# Instantiate the wrapper class, specifying data fields of interest.\n", "wrapper = SpatialDataWrapper(\n", " sdata_path=spatialdata_filepath,\n", - " # The following paths are relative to the root of the SpatialData zarr store on-disk.\n", + " # The following paths are relative to the root of the SpatialData Zarr store on-disk.\n", " table_path=\"tables/table\",\n", " image_path=\"images/raw_image\",\n", " labels_path=\"labels/segmentation_mask\",\n", - " #shapes_path=\"shapes/nucleus_boundaries\",\n", - " #points_path=\"points/transcripts\",\n", " obs_feature_matrix_path=\"tables/table/X\",\n", " obs_set_paths=[\"tables/table/obs/annotation\"],\n", " obs_set_names=[\"Annotation\"],\n", @@ -190,37 +257,40 @@ " \"obsType\": \"cell\" \n", " }\n", ")\n", + "# Add a new dataset to the Vitessce configuration,\n", + "# then add the wrapper class instance to this dataset.\n", "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", "\n", - "# Add views (visualizations) to the configuration:\n", + "# Add views (visualizations) to the configuration.\n", "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", - "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", + "feature_list = vc.add_view(\"featureList\", dataset=dataset)\n", "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", - "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", + "obs_sets = vc.add_view(\"obsSets\", dataset=dataset)\n", + "heatmap = vc.add_view(\"heatmap\", dataset=dataset)\n", "\n", "vc.link_views_by_dict([spatial, layer_controller], {\n", - " 'imageLayer': CL([{\n", - " 'photometricInterpretation': 'BlackIsZero',\n", - " 'imageChannel': CL([{\n", - " 'spatialTargetC': 0,\n", - " 'spatialChannelColor': [255, 255, 255],\n", - " 'spatialChannelWindow': [0, 4000],\n", + " \"imageLayer\": CL([{\n", + " \"photometricInterpretation\": \"BlackIsZero\",\n", + " \"imageChannel\": CL([{\n", + " \"spatialTargetC\": 0,\n", + " \"spatialChannelColor\": [255, 255, 255],\n", + " \"spatialChannelWindow\": [0, 4000],\n", " }])\n", " }]),\n", "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", "\n", "vc.link_views_by_dict([spatial, layer_controller], {\n", - " 'segmentationLayer': CL([{\n", - " 'segmentationChannel': CL([{\n", - " 'obsColorEncoding': 'cellSetSelection',\n", + " \"segmentationLayer\": CL([{\n", + " \"segmentationChannel\": CL([{\n", + " \"obsColorEncoding\": \"cellSetSelection\",\n", " }]),\n", " }]),\n", "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"obsSegmentations\"))\n", "\n", - "vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], [wrapper.obs_type_label])\n", + "vc.link_views([spatial, layer_controller, feature_list, obs_sets, heatmap], [\"obsType\"], [wrapper.obs_type_label])\n", "\n", - "# Layout the views\n", - "vc.layout(spatial | (feature_list / layer_controller / obs_sets));" + "# Layout the views in a grid arrangement.\n", + "vc.layout((spatial / heatmap) | (layer_controller / (feature_list | obs_sets)));" ] }, { @@ -236,7 +306,7 @@ "metadata": {}, "outputs": [], "source": [ - "vw = vc.widget(invoke_timeout=300000)\n", + "vw = vc.widget()\n", "vw" ] }, @@ -247,6 +317,14 @@ "## Extract AnnData object from SpatialData object" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "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](https://anndata.readthedocs.io/) object (ignoring the imaging and spatially-resolved elements)." + ] + }, { "cell_type": "code", "execution_count": null, @@ -267,6 +345,16 @@ "adata" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As Zarr-formatted data can be easily visualized by Vitessce, we recommend saving the AnnData object to a Zarr store using the [write_zarr](https://anndata.readthedocs.io/en/stable/generated/anndata.AnnData.write_zarr.html) method.\n", + "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.\n", + "For example, a common pattern is to visualize data across all cells for one gene.\n", + "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." + ] + }, { "cell_type": "code", "execution_count": null, @@ -276,13 +364,36 @@ "adata.write_zarr(adata_zarr_filepath, chunks=(adata.shape[0], VAR_CHUNK_SIZE))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, your AnnData object may already be stored using the H5AD (HDF5-based) format.\n", + "To demonstrate this scenario, we save the object using the [write_h5ad](https://anndata.readthedocs.io/en/stable/generated/anndata.AnnData.write_h5ad.html) method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "adata.write_h5ad(adata_h5ad_filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To read H5AD-formatted data, Vitessce requires an accompanying JSON [references specification](https://fsspec.github.io/kerchunk/spec.html) file, which can be constructed using the `generate_h5ad_ref_spec` utility function." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "adata.write_h5ad(adata_h5ad_filepath)\n", "ref_dict = generate_h5ad_ref_spec(adata_h5ad_filepath)\n", "with open(ref_spec_json_filepath, \"w\") as f:\n", " json.dump(ref_dict, f)" @@ -308,11 +419,8 @@ "metadata": {}, "outputs": [], "source": [ - "vc = VitessceConfig(\n", - " schema_version=\"1.0.17\",\n", - " name='AnnData (zarr)',\n", - ")\n", - "# Add data to the configuration:\n", + "vc = VitessceConfig(schema_version=\"1.0.17\", name=\"AnnData (zarr)\")\n", + "# Add data.\n", "wrapper = AnnDataWrapper(\n", " adata_path=adata_zarr_filepath,\n", " obs_feature_matrix_path=\"X\",\n", @@ -324,7 +432,7 @@ ")\n", "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", "\n", - "# Add views (visualizations) to the configuration:\n", + "# Add views.\n", "heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)\n", "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", @@ -332,7 +440,7 @@ "\n", "vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])\n", "\n", - "# Layout the views\n", + "# Layout the views.\n", "vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));" ] }, @@ -358,11 +466,8 @@ "metadata": {}, "outputs": [], "source": [ - "vc = VitessceConfig(\n", - " schema_version=\"1.0.17\",\n", - " name='AnnData (h5ad)',\n", - ")\n", - "# Add data to the configuration:\n", + "vc = VitessceConfig(schema_version=\"1.0.17\", name=\"AnnData (h5ad)\")\n", + "# Add data.\n", "wrapper = AnnDataWrapper(\n", " adata_path=adata_h5ad_filepath,\n", " ref_path=ref_spec_json_filepath,\n", @@ -375,7 +480,7 @@ ")\n", "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", "\n", - "# Add views (visualizations) to the configuration:\n", + "# Add views.\n", "heatmap = vc.add_view(vt.HEATMAP, dataset=dataset)\n", "feature_list = vc.add_view(vt.FEATURE_LIST, dataset=dataset)\n", "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\n", @@ -383,7 +488,7 @@ "\n", "vc.link_views([heatmap, feature_list, obs_sets], ['obsType', 'featureValueColormapRange'], ['cell', [0, 0.01]])\n", "\n", - "# Layout the views\n", + "# Layout the views.\n", "vc.layout((heatmap / violin_plots) | (feature_list / obs_sets));" ] }, @@ -403,6 +508,13 @@ "## Extract image from SpatialData object" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In contrast to extraction of the non-spatial data from the SpatialData object, we can extract the imaging (and segmentation/label image) data and save it to a dedicated bioimaging file format." + ] + }, { "cell_type": "code", "execution_count": null, @@ -421,6 +533,14 @@ "### Save image to OME-Zarr" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For small images, the data can be saved to OME-Zarr or OME-TIFF format using [utility functions](https://python-docs.vitessce.io/api_data.html#vitessce-data-utils) from the Vitessce package.\n", + "Larger images require generation of an [image pyramid](https://en.wikipedia.org/wiki/Pyramid_(image_processing)), which can be performed using tools from the OME ecosystem such as [bioformats2raw](https://github.com/glencoesoftware/bioformats2raw) and [raw2ometiff](https://github.com/glencoesoftware/raw2ometiff)." + ] + }, { "cell_type": "code", "execution_count": null, @@ -438,6 +558,14 @@ "### Save image and segmentations to OME-TIFF" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To efficiently visualize OME-TIFF data using Vitessce, a JSON-based offsets file can be constructed using [generate-tiff-offsets](https://github.com/hms-dbmi/generate-tiff-offsets).\n", + "This JSON file contains byte offsets into different partitions of the TIFF file, effectively resulting in an \"indexed TIFF\" which is described by [Manz et al. 2022](https://doi.org/10.1038/s41592-022-01482-7)." + ] + }, { "cell_type": "code", "execution_count": null, @@ -450,17 +578,29 @@ " json.dump(offsets, f)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Visualization of an image file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OME-Zarr image" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "vc = VitessceConfig(\n", - " schema_version=\"1.0.17\",\n", - " name='Image (ome-zarr)',\n", - ")\n", - "# Add data to the configuration:\n", + "vc = VitessceConfig(schema_version=\"1.0.17\", name=\"Image (ome-zarr)\")\n", + "\n", + "# Add data.\n", "img_wrapper = ImageOmeZarrWrapper(\n", " img_path=ome_zarr_filepath,\n", " coordination_values={\n", @@ -473,14 +613,19 @@ " \"fileUid\": \"segmentations\",\n", " }\n", ")\n", + "# Here, we chain .add_object calls to add both the image and segmentation\n", + "# wrapper instances to the same dataset.\n", "dataset = vc.add_dataset(name='Mouse Liver').add_object(img_wrapper).add_object(segmentations_wrapper)\n", "\n", - "# Add views (visualizations) to the configuration:\n", + "# Add views.\n", "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", "\n", "vc.link_views_by_dict([spatial, layer_controller], {\n", " 'imageLayer': CL([{\n", + " # In case there are multiple image files in our dataset,\n", + " # we use fileUid to specify which file we intend to visualize\n", + " # in this image layer.\n", " 'fileUid': 'image',\n", " 'photometricInterpretation': 'BlackIsZero',\n", " 'imageChannel': CL([{\n", @@ -493,6 +638,9 @@ "\n", "vc.link_views_by_dict([spatial, layer_controller], {\n", " 'segmentationLayer': CL([{\n", + " # In case there are multiple segmentation files in our dataset,\n", + " # we use fileUid to specify which file we intend to visualize\n", + " # in this segmentation layer.\n", " 'fileUid': 'segmentations',\n", " 'segmentationChannel': CL([{\n", " 'obsColorEncoding': 'spatialChannelColor',\n", @@ -506,7 +654,7 @@ "\n", "vc.link_views([spatial, layer_controller, feature_list, obs_sets], ['obsType'], ['cell'])\n", "\n", - "# Layout the views\n", + "# Layout the views.\n", "vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));" ] }, @@ -520,24 +668,28 @@ "vw" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OME-TIFF image" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "vc = VitessceConfig(\n", - " schema_version=\"1.0.17\",\n", - " name='Image and segmentations (ome-tiff)',\n", - ")\n", - "# Add data to the configuration:\n", + "vc = VitessceConfig(schema_version=\"1.0.17\", name=\"Image and segmentations (ome-tiff)\")\n", + "# Add data.\n", "wrapper = ImageOmeTiffWrapper(\n", " img_path=ome_tiff_filepath,\n", " offsets_path=offsets_json_filepath\n", ")\n", "dataset = vc.add_dataset(name='Mouse Liver').add_object(wrapper)\n", "\n", - "# Add views (visualizations) to the configuration:\n", + "# Add views.\n", "spatial = vc.add_view(\"spatialBeta\", dataset=dataset)\n", "layer_controller = vc.add_view(\"layerControllerBeta\", dataset=dataset)\n", "\n", @@ -552,7 +704,7 @@ " }]),\n", "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", "\n", - "# Layout the views\n", + "# Layout the views.\n", "vc.layout(hconcat(spatial, layer_controller, split=(2, 1)));" ] }, @@ -571,10 +723,30 @@ "source": [ "## Data location options\n", "\n", + "Vitessce can visualize data [not only stored](https://python-docs.vitessce.io/data_options.html) locally (referenced using local file or directory paths) but also stored remotely (referenced using absolute URL paths).\n", + "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).\n", + "\n", + "To provide data via one of these alternative mechanisms, use parameters with the following suffices when instantiating the data wrapper classes.\n", + "\n", "- `_path`: Local file or directory\n", "- `_url`: Remote file or directory\n", "- `_store`: Zarr-store-accessible (for zarr-based formats)\n", - "- `_artifact`: Lamin artifact" + "- `_artifact`: Lamin artifact\n", + "\n", + "For example, `adata_path` can be exchanged for one of the following options.\n", + "\n", + "```diff\n", + "AnnDataWrapper(\n", + "- adata_path=\"./mouse_liver.spatialdata.zarr\",\n", + "+ adata_url=\"https://example.com/mouse_liver.spatialdata.zarr\", # Absolute URL\n", + "+ adata_store=\"./mouse_liver.spatialdata.zarr\", # String interpreted as root of DirectoryStore\n", + "+ adata_store=zarr.DirectoryStore(\"./mouse_liver.spatialdata.zarr\"), # Instance of zarr.storage\n", + "+ adata_artifact=adata_zarr_artifact, # Instance of ln.Artifact\n", + " ...\n", + "```\n", + "\n", + "Note that the `_store` options are only available for Zarr-based formats, such as AnnDataWrapper, SpatialDataWrapper, and ImageOmeZarrWrapper.\n", + "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." ] }, { diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index 709a4850..b9469ee8 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -463,16 +463,16 @@ class VitessceWidget(anywidget.AnyWidget): next_port = DEFAULT_PORT - js_package_version = Unicode('3.5.10').tag(sync=True) + js_package_version = Unicode('3.5.11').tag(sync=True) js_dev_mode = Bool(False).tag(sync=True) custom_js_url = Unicode('').tag(sync=True) plugin_esm = List(trait=Unicode(''), default_value=[]).tag(sync=True) remount_on_uid_change = Bool(True).tag(sync=True) - invoke_timeout = Int(30000).tag(sync=True) + invoke_timeout = Int(300000).tag(sync=True) store_urls = List(trait=Unicode(''), default_value=[]).tag(sync=True) - def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.10', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=30000): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.5.11', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=300000): """ Construct a new Vitessce widget. @@ -586,7 +586,7 @@ def _plugin_command(self, params, buffers): # Launch Vitessce using plain HTML representation (no ipywidgets) -def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.5.10', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): +def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.5.11', js_dev_mode=False, custom_js_url='', plugin_esm=DEFAULT_PLUGIN_ESM, remount_on_uid_change=True): from IPython.display import display, HTML uid_str = "vitessce" + get_uid_str(uid)