diff --git a/docs/notebooks/spatial_data_mouseliver.ipynb b/docs/notebooks/spatial_data_mouseliver.ipynb new file mode 100644 index 00000000..0b8b571a --- /dev/null +++ b/docs/notebooks/spatial_data_mouseliver.ipynb @@ -0,0 +1,781 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden", + "tags": [] + }, + "source": [ + "# 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": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 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, + "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": {}, + "source": [ + "## 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, + "metadata": {}, + "outputs": [], + "source": [ + "from vitessce import (\n", + " VitessceConfig,\n", + " ViewType as vt,\n", + " CoordinationType as ct,\n", + " CoordinationLevel as CL,\n", + " SpatialDataWrapper,\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": "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, + "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": [ + "## Download example dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "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\")\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\")" + ] + }, + { + "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, + "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": { + "tags": [] + }, + "source": [ + "# 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.\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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 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", + " table_path=\"tables/table\",\n", + " image_path=\"images/raw_image\",\n", + " labels_path=\"labels/segmentation_mask\",\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", + "# 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", + "spatial = vc.add_view(\"spatialBeta\", 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(\"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", + " }])\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, heatmap], [\"obsType\"], [wrapper.obs_type_label])\n", + "\n", + "# Layout the views in a grid arrangement.\n", + "vc.layout((spatial / heatmap) | (layer_controller / (feature_list | obs_sets)));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Render the widget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vw = vc.widget()\n", + "vw" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 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, + "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": "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, + "metadata": {}, + "outputs": [], + "source": [ + "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": [ + "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(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", + " 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.\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(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", + " 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.\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": "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, + "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": "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, + "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": "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, + "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": "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(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", + " \"fileUid\": \"image\",\n", + " }\n", + ")\n", + "segmentations_wrapper = ObsSegmentationsOmeZarrWrapper(\n", + " img_path=labels_ome_zarr_filepath,\n", + " coordination_values={\n", + " \"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.\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", + " '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", + " # 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", + " '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": "markdown", + "metadata": {}, + "source": [ + "### OME-TIFF image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "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.\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", + "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\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." + ] + }, + { + "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/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/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) diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index ed3b7772..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.4').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.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.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. @@ -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 @@ -506,7 +507,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] @@ -585,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.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) diff --git a/src/vitessce/wrappers.py b/src/vitessce/wrappers.py index 70948914..795da13d 100644 --- a/src/vitessce/wrappers.py +++ b/src/vitessce/wrappers.py @@ -127,7 +127,16 @@ def get_artifacts(self): """ 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 local_path is not None and 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):