From 8792ef9a36728f037f62c5228a71bff91b5d6f0b Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:37:46 -0400 Subject: [PATCH 1/7] Add nb --- docs/notebooks/spatial_data_visium_hd.ipynb | 275 ++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 docs/notebooks/spatial_data_visium_hd.ipynb diff --git a/docs/notebooks/spatial_data_visium_hd.ipynb b/docs/notebooks/spatial_data_visium_hd.ipynb new file mode 100644 index 00000000..0c36d998 --- /dev/null +++ b/docs/notebooks/spatial_data_visium_hd.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "# Vitessce Widget Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Visualization of a SpatialData object" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import dependencies\n" + ] + }, + { + "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, \"visium_hd_3.0.0_io.zip\")\n", + "spatialdata_filepath = join(data_dir, \"visium_hd_3.0.0.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/visium_hd_3.0.0_io.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": [ + "## Rasterize bins\n", + "Reference: https://spatialdata.scverse.org/en/stable/tutorials/notebooks/notebooks/examples/technology_visium_hd.html#performant-on-the-fly-data-rasterization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from spatialdata import (\n", + " read_zarr,\n", + " rasterize_bins,\n", + " rasterize_bins_link_table_to_labels\n", + ")" + ] + }, + { + "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": [ + "for bin_size in [\"016\", \"008\", \"002\"]:\n", + " # rasterize_bins() requires a compresed sparse column (csc) matrix\n", + " sdata.tables[f\"square_{bin_size}um\"].X = sdata.tables[f\"square_{bin_size}um\"].X.tocsc()\n", + " rasterized = rasterize_bins(\n", + " sdata,\n", + " f\"Visium_HD_Mouse_Small_Intestine_square_{bin_size}um\",\n", + " f\"square_{bin_size}um\",\n", + " \"array_col\",\n", + " \"array_row\",\n", + " # We want to rasterize to a Labels element, rather than an Image element.\n", + " return_region_as_labels=True\n", + " )\n", + " sdata[f\"rasterized_{bin_size}um\"] = rasterized\n", + " rasterize_bins_link_table_to_labels(\n", + " sdata,\n", + " table_name=f\"square_{bin_size}um\",\n", + " rasterized_labels_name=f\"rasterized_{bin_size}um\",\n", + " )\n", + " sdata.write_element(f\"rasterized_{bin_size}um\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sdata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test with spatialdata-plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import spatialdata_plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sdata.pl.render_images(\"Visium_HD_Mouse_Small_Intestine_full_image\").pl.render_labels(\"rasterized_016um\").pl.show(\"Visium_HD_Mouse_Small_Intestine\")" + ] + }, + { + "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.18\",\n", + " name='Visium HD SpatialData Demo',\n", + ")\n", + "# Add data to the configuration:\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", + " image_path=\"images/Visium_HD_Mouse_Small_Intestine_full_image\",\n", + " table_path=\"tables/square_016um\",\n", + " obs_feature_matrix_path=\"tables/square_016um/X\",\n", + " obs_segmentations_path=\"labels/rasterized_016um\",\n", + " #region=\"CytAssist_FFPE_Human_Breast_Cancer\",\n", + " coordinate_system=\"Visium_HD_Mouse_Small_Intestine\",\n", + " coordination_values={\n", + " # The following tells Vitessce to consider each observation as a \"bin\"\n", + " \"obsType\": \"bin\",\n", + " }\n", + ")\n", + "dataset = vc.add_dataset(name='Visium HD').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", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'imageLayer': CL([{\n", + " 'photometricInterpretation': 'RGB',\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\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()\n", + "vw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "print(json.dumps(vc.to_dict(base_url=f\"http://localhost:{vw.port}\")))" + ] + }, + { + "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 +} From 1ed2b2fa57f903c5b2ca2e3abb135693ce6a543d Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:53:07 -0400 Subject: [PATCH 2/7] Add visium HD nb --- docs/notebooks/spatial_data_visium_hd.ipynb | 48 +++++---------------- src/vitessce/widget.py | 37 ++++++++++------ 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/docs/notebooks/spatial_data_visium_hd.ipynb b/docs/notebooks/spatial_data_visium_hd.ipynb index 0c36d998..0ca3257e 100644 --- a/docs/notebooks/spatial_data_visium_hd.ipynb +++ b/docs/notebooks/spatial_data_visium_hd.ipynb @@ -137,33 +137,6 @@ "sdata" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test with spatialdata-plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import spatialdata_plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sdata.pl.render_images(\"Visium_HD_Mouse_Small_Intestine_full_image\").pl.render_labels(\"rasterized_016um\").pl.show(\"Visium_HD_Mouse_Small_Intestine\")" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -209,8 +182,17 @@ " 'photometricInterpretation': 'RGB',\n", " }]),\n", "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"image\"))\n", + "vc.link_views_by_dict([spatial, layer_controller], {\n", + " 'segmentationLayer': CL([{\n", + " 'segmentationChannel': CL([{\n", + " 'spatialChannelOpacity': 0.5,\n", + " 'obsColorEncoding': 'geneSelection',\n", + " 'featureValueColormapRange': [0, 0.5],\n", + " }])\n", + " }]),\n", + "}, scope_prefix=get_initial_coordination_scope_prefix(\"A\", \"obsSegmentations\"))\n", "obs_sets = vc.add_view(vt.OBS_SETS, dataset=dataset)\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], ['obsType', 'featureSelection'], [wrapper.obs_type_label, ['AA986860']])\n", "\n", "# Layout the views\n", "vc.layout(spatial | (feature_list / layer_controller / obs_sets));" @@ -233,16 +215,6 @@ "vw" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "print(json.dumps(vc.to_dict(base_url=f\"http://localhost:{vw.port}\")))" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index 916d64a6..2738fa44 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -445,7 +445,7 @@ def get_uid_str(uid): function VitessceWidget(props) { const { model, styleContainer } = props; - const [config, setConfig] = React.useState(prependBaseUrl(model.get('config'), model.get('proxy'), model.get('has_host_name'))); + const [config, setConfig] = React.useState(prependBaseUrl(model.get('_config'), model.get('proxy'), model.get('has_host_name'))); const [validateConfig, setValidateConfig] = React.useState(true); const height = model.get('height'); const theme = model.get('theme') === 'auto' ? (prefersDark ? 'dark' : 'light') : model.get('theme'); @@ -484,7 +484,7 @@ def get_uid_str(uid): // Config changed on JS side (from within ), // send updated config to Python side. const onConfigChange = React.useCallback((config) => { - model.set('config', config); + model.set('_config', config); setValidateConfig(false); model.save_changes(); }, [model]); @@ -492,8 +492,8 @@ def get_uid_str(uid): // Config changed on Python side, // pass to component to it is updated on JS side. React.useEffect(() => { - model.on('change:config', () => { - const newConfig = prependBaseUrl(model.get('config'), model.get('proxy'), model.get('has_host_name')); + model.on('change:_config', () => { + const newConfig = prependBaseUrl(model.get('_config'), model.get('proxy'), model.get('has_host_name')); // Force a re-render and re-validation by setting a new config.uid value. // TODO: make this conditional on a parameter from Python. @@ -622,7 +622,12 @@ class VitessceWidget(anywidget.AnyWidget): # Widget properties are defined as traitlets. Any property tagged with `sync=True` # is automatically synced to the frontend *any* time it changes in Python. # It is synced back to Python from the frontend *any* time the model is touched. - config = Dict({}).tag(sync=True) + _config = Dict({}).tag(sync=True) + """dict: Dictionary representation of the Vitessce JSON configuration. Synced via traitlets upon interactions.""" + + config = None + """VitessceConfig: The VitessceConfig instance used to create this widget. Not synced upon interactions.""" + height = Int(600).tag(sync=True) theme = Unicode('auto').tag(sync=True) proxy = Bool(False).tag(sync=True) @@ -631,7 +636,7 @@ class VitessceWidget(anywidget.AnyWidget): next_port = DEFAULT_PORT - js_package_version = Unicode('3.6.11').tag(sync=True) + js_package_version = Unicode('3.6.12').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) @@ -644,7 +649,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.6.11', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=300000, invoke_batched=True, page_mode=False, page_esm=None, prevent_scroll=True): + def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=False, js_package_version='3.6.12', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=300000, invoke_batched=True, page_mode=False, page_esm=None, prevent_scroll=True): """ Construct a new Vitessce widget. @@ -678,7 +683,11 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= base_url, use_port, VitessceWidget.next_port = get_base_url_and_port( port, VitessceWidget.next_port, proxy=proxy) - self.config_obj = config + # Note: + # - self.config is the VitessceConfig instance. + # - self._config is the JSON configuration, synced via traitlets + + self.config = config self.port = use_port config_dict = config.to_dict(base_url=base_url) routes = config.get_routes() @@ -694,7 +703,7 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= uid_str = get_uid_str(uid) super(VitessceWidget, self).__init__( - config=config_dict, height=height, theme=theme, proxy=proxy, + _config=config_dict, height=height, theme=theme, proxy=proxy, js_package_version=js_package_version, js_dev_mode=js_dev_mode, custom_js_url=custom_js_url, plugin_esm=plugin_esm, remount_on_uid_change=remount_on_uid_change, page_mode=page_mode, page_esm=('' if page_esm is None else page_esm), @@ -712,14 +721,14 @@ def handle_config_change(change): # It is optional for plugins to implement on_config_change. pass if new_config is not None: - self.config = new_config + self._config = new_config - self.observe(handle_config_change, names=['config']) + self.observe(handle_config_change, names=['_config']) serve_routes(config, routes, use_port) def _get_coordination_value(self, coordination_type, coordination_scope): - obj = self.config['coordinationSpace'][coordination_type] + obj = self._config['coordinationSpace'][coordination_type] obj_scopes = list(obj.keys()) if coordination_scope is not None: if coordination_scope in obj_scopes: @@ -742,7 +751,7 @@ def get_cell_selection(self, scope=None): return self._get_coordination_value('cellSelection', scope) def close(self): - self.config_obj.stop_server(self.port) + self.config.stop_server(self.port) super().close() @anywidget.experimental.command @@ -780,7 +789,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.6.11', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, page_mode=False, page_esm=None): +def ipython_display(config, height=600, theme='auto', base_url=None, host_name=None, uid=None, port=None, proxy=False, js_package_version='3.6.12', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, page_mode=False, page_esm=None): from IPython.display import display, HTML uid_str = "vitessce" + get_uid_str(uid) From 17a5e8209574dc8eb2c35f7b9f66b8c72faad975 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:16:09 -0400 Subject: [PATCH 3/7] Update VitessceWidget docs --- docs/api_config.rst | 11 +++++++++++ docs/api_data.rst | 4 ++-- src/vitessce/config.py | 11 +++++++++++ src/vitessce/widget.py | 44 +++++++++++++++++++++++++----------------- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/docs/api_config.rst b/docs/api_config.rst index 26a80d6a..b0522230 100644 --- a/docs/api_config.rst +++ b/docs/api_config.rst @@ -19,6 +19,17 @@ vitessce.config :exclude-members: VitessceConfig, VitessceChainableConfig :member-order: bysource +vitessce.widget +*************** + +.. autoclass:: vitessce.widget.VitessceWidget() + :members: + :private-members: + :exclude-members: close + +.. autoclass:: vitessce.widget.VitesscePlugin + :members: + vitessce.constants ****************** diff --git a/docs/api_data.rst b/docs/api_data.rst index 34fffd81..9cecbff1 100644 --- a/docs/api_data.rst +++ b/docs/api_data.rst @@ -19,13 +19,13 @@ vitessce.wrappers :members: vitessce.export -***************** +*************** .. automodule:: vitessce.export :members: vitessce.data_utils -***************** +******************* .. automodule:: vitessce.data_utils.ome :members: diff --git a/src/vitessce/config.py b/src/vitessce/config.py index efaee548..3882c6b8 100644 --- a/src/vitessce/config.py +++ b/src/vitessce/config.py @@ -1791,6 +1791,17 @@ def widget(self, **kwargs): :param int height: The height of the widget, in pixels. By default, 600. :param int port: The port to use when serving data objects on localhost. By default, 8000. :param bool proxy: Is this widget being served through a proxy, for example with a cloud notebook (e.g. Binder)? + :param str js_package_version: The version of the NPM package ('vitessce' if not js_dev_mode else '@vitessce/dev'). + :param bool js_dev_mode: Should @vitessce/dev be used (typically for debugging purposes)? By default, False. + :param str custom_js_url: A URL to a JavaScript file to use (instead of 'vitessce' or '@vitessce/dev' NPM package). + :param list[VitesscePlugin] 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, 300000. + :param bool invoke_batched: Should invocations (Zarr gets) be submitted in batch, or individually? By default, True. + :param bool page_mode: Whether to render the component in grid-mode or page-mode. By default, False. + :param str page_esm: The ES module string for the page component creation function. Optional. + :param bool prevent_scroll: Should mouseover in the Vitessce widget prevent disable the scrolling of the notebook? By default, True. :returns: The Jupyter widget. :rtype: VitessceWidget diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index 2738fa44..ef2723a6 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -597,8 +597,12 @@ class VitesscePlugin: """ A class that represents a Vitessce widget plugin. Custom plugins can be created by subclassing this class. """ - plugin_esm = DEFAULT_PLUGIN_ESM - commands = {} + + #: The ES module string for the plugin. + plugin_esm = DEFAULT_PLUGIN_ESM # type: str + + #: A dictionary mapping command name strings to functions. Functions should take two arguments (message, buffers) and return a tuple (response, buffers). + commands = {} # type: dict def on_config_change(self, new_config): """ @@ -614,7 +618,16 @@ def on_config_change(self, new_config): class VitessceWidget(anywidget.AnyWidget): """ - A class to represent a Jupyter widget for Vitessce. + A class to represent a Jupyter widget for Vitessce. Not intended to be instantiated directly; instead, use ``VitessceConfig.widget``. + + .. code-block:: python + :emphasize-lines: 4 + + from vitessce import VitessceConfig + + vc = VitessceConfig.from_object(my_scanpy_object) + vw = vc.widget() + vw """ _esm = ESM @@ -622,11 +635,12 @@ class VitessceWidget(anywidget.AnyWidget): # Widget properties are defined as traitlets. Any property tagged with `sync=True` # is automatically synced to the frontend *any* time it changes in Python. # It is synced back to Python from the frontend *any* time the model is touched. - _config = Dict({}).tag(sync=True) - """dict: Dictionary representation of the Vitessce JSON configuration. Synced via traitlets upon interactions.""" + + #: Dictionary representation of the Vitessce JSON configuration. Synced via traitlets upon interactions. + _config = Dict({}).tag(sync=True) # type: dict - config = None - """VitessceConfig: The VitessceConfig instance used to create this widget. Not synced upon interactions.""" + #: The VitessceConfig instance used to create this widget. Not synced upon interactions. + config = None # type: vitessce.config.VitessceConfig height = Int(600).tag(sync=True) theme = Unicode('auto').tag(sync=True) @@ -650,9 +664,10 @@ 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.6.12', js_dev_mode=False, custom_js_url='', plugins=None, remount_on_uid_change=True, prefer_local=True, invoke_timeout=300000, invoke_batched=True, page_mode=False, page_esm=None, prevent_scroll=True): + """ """ """ - Construct a new Vitessce widget. - + Construct a new Vitessce widget. Not intended to be instantiated directly; instead, use ``VitessceConfig.widget``. + :param config: A view config instance. :type config: VitessceConfig :param str theme: The theme name, either "light" or "dark". By default, "auto", which selects light or dark based on operating system preferences. @@ -671,14 +686,7 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= :param str page_esm: The ES module string for the page component creation function. Optional. :param bool prevent_scroll: Should mouseover in the Vitessce widget prevent disable the scrolling of the notebook? By default, True. - .. code-block:: python - :emphasize-lines: 4 - - from vitessce import VitessceConfig, VitessceWidget - - vc = VitessceConfig.from_object(my_scanpy_object) - vw = vc.widget() - vw + Note: these parameter docstrings need to be manually kept in sync with the VitessceConfig.widget docstring. """ base_url, use_port, VitessceWidget.next_port = get_base_url_and_port( @@ -817,7 +825,7 @@ def ipython_display(config, height=600, theme='auto', base_url=None, host_name=N "has_host_name": host_name is not None, "height": height, "theme": theme, - "config": config_dict, + "_config": config_dict, "store_urls": [], } From d4273ebc12faa0942b136718a86455675fc9c7e3 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:16:38 -0400 Subject: [PATCH 4/7] Lint --- src/vitessce/widget.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vitessce/widget.py b/src/vitessce/widget.py index ef2723a6..59b88c12 100644 --- a/src/vitessce/widget.py +++ b/src/vitessce/widget.py @@ -599,10 +599,10 @@ class VitesscePlugin: """ #: The ES module string for the plugin. - plugin_esm = DEFAULT_PLUGIN_ESM # type: str + plugin_esm = DEFAULT_PLUGIN_ESM # type: str #: A dictionary mapping command name strings to functions. Functions should take two arguments (message, buffers) and return a tuple (response, buffers). - commands = {} # type: dict + commands = {} # type: dict def on_config_change(self, new_config): """ @@ -635,12 +635,12 @@ class VitessceWidget(anywidget.AnyWidget): # Widget properties are defined as traitlets. Any property tagged with `sync=True` # is automatically synced to the frontend *any* time it changes in Python. # It is synced back to Python from the frontend *any* time the model is touched. - + #: Dictionary representation of the Vitessce JSON configuration. Synced via traitlets upon interactions. - _config = Dict({}).tag(sync=True) # type: dict + _config = Dict({}).tag(sync=True) # type: dict #: The VitessceConfig instance used to create this widget. Not synced upon interactions. - config = None # type: vitessce.config.VitessceConfig + config = None # type: vitessce.config.VitessceConfig height = Int(600).tag(sync=True) theme = Unicode('auto').tag(sync=True) @@ -667,7 +667,7 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy= """ """ """ Construct a new Vitessce widget. Not intended to be instantiated directly; instead, use ``VitessceConfig.widget``. - + :param config: A view config instance. :type config: VitessceConfig :param str theme: The theme name, either "light" or "dark". By default, "auto", which selects light or dark based on operating system preferences. From 96522b8b1ec2df5bb633f3b18cac1eade9f8631b Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:18:46 -0400 Subject: [PATCH 5/7] Docs --- src/vitessce/config.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vitessce/config.py b/src/vitessce/config.py index 3882c6b8..c90c5e81 100644 --- a/src/vitessce/config.py +++ b/src/vitessce/config.py @@ -925,6 +925,19 @@ def stop_server(self, port): del self.background_servers[port] def stop_all_servers(self): + """ + Stop all background servers associated with this config instance. + + .. code-block:: python + :emphasize-lines: 5 + + from vitessce import VitessceConfig + + vc = VitessceConfig(schema_version="1.0.18", name='My Config') + vw = vc.widget() + # ... do something with the widget ... + vc.stop_all_servers() + """ for server in self.background_servers.values(): server.stop() self.background_servers = {} From f20b5eb4f6b1252f06f18eceac5aeecbec60f5d9 Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:20:07 -0400 Subject: [PATCH 6/7] Update marimo nb --- docs/notebooks/marimo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/marimo.py b/docs/notebooks/marimo.py index 6d850404..4ce47a6b 100644 --- a/docs/notebooks/marimo.py +++ b/docs/notebooks/marimo.py @@ -33,13 +33,13 @@ def _(mo): @app.cell def _(vw): - vw.config + vw._config return @app.cell def _(vw): - vw.config["coordinationSpace"]["embeddingZoom"] + vw._config["coordinationSpace"]["embeddingZoom"] return From 4b6d30fdceac0901b951adfeacb79d146457060e Mon Sep 17 00:00:00 2001 From: Mark Keller <7525285+keller-mark@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:29:38 -0400 Subject: [PATCH 7/7] Version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ba7da9b..2eec62e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "vitessce" -version = "3.6.9" +version = "3.7.0" authors = [ { name="Mark Keller", email="mark_keller@hms.harvard.edu" }, ]