napari-spatialdata icon indicating copy to clipboard operation
napari-spatialdata copied to clipboard

API for programmatically loading single elements into Napari

Open aeisenbarth opened this issue 1 year ago • 3 comments

Use case

  • As a plugin developer, I want to programmatically display a single SpatialData image so that my plugin can support SpatialData as storage format and provide interaction specific to some element(s).

Workarounds

Currently, I have these options:

  • ✓ Instruct the user to open the GUI of napari-spatialdata, double-click on (the only) coordinate system, double-click on the image name.
  • Request Napari to use the plugin for opening the SpatialData path:
    viewer.open(sdata_path, plugin="napari-spatialdata")
    
    Here, I cannot pass the element name. **kwargs are passed to Napari's add_layer method, so there is no way to extend this. Also, napari-spatialdata returns layer data [(None,)] which is a sentinal that tells Napari that the plugin successfully read the path, but loaded no layer.
  • Request Napari to use the plugin for opening the element path:
    viewer.open(sdata_path / "images" / image_name, plugin="napari-spatialdata")
    
    This is an invalid command, the plugin only accepts valid SpatialData paths, not subpaths to contained elements.
  • Reimplement everything on my own. Use spatialdata.read_zarr(sdata_path).images[image_name] and pass the array and transformation to Napari. However, I have to handle both SpatialImage/MultiscaleSpatialImage, extract the scale levels to a plain list, order the axes, convert the transformation to a plain matrix… basically rebuilding the private function _adjust_channels_order (which I cannot savely import) and add_sdata_image.

It remains for discussion whether such an API should:

  • add the element to the viewer
  • or just read and convert the SpatialData element to arguments that can be passed to viewer.add_image(…) so that the user/developer has further control over passing them to the viewer.

Requirements:

  • Given a SpatialData path, element names (and optional coordinate system), read and convert the element to be compatible with Napari, for example as layer data tuple Tuple[DataType, Metadata, LayerName].
  • If adding the element to the viewer, return a reference to the layer.
  • Allow loading without GUI because it takes a lot of screen space and can hinder using other Napari plugins at the same time.

aeisenbarth avatar Dec 12 '23 20:12 aeisenbarth

Thank you @aeisenbarth for reaching out and for the feature request. I agree that it's an important use case and it would facilitate the interaction between our and other napari plugins.

We also needed similar functions, to be able to instantiate specific elements to the viewer programmatically (to be used during testing) https://github.com/scverse/napari-spatialdata/pull/170/files and in a headless mode https://github.com/scverse/napari-spatialdata/pull/140/files or to fire an instance with already a coordinate system selected https://github.com/scverse/napari-spatialdata/pull/151/files.

Nevertheless, the mentioned PRs don't cover your use case, so it would need to be addressed in a separate PR.

I am currently focusing on merging old PRs in spatialdata, but after that I am happy to work more on this repository.

LucaMarconato avatar Jan 17 '24 16:01 LucaMarconato

Hey @aeisenbarth! Does what @LucaMarconato stated in his comment suit your needs? If so we can close this issue.

melonora avatar Mar 13 '24 17:03 melonora

I should have a look again into the Interactive class. I am actually not using that, but SdataWidget directly.

I have the impression, Interactive is considered as the public API and should be used, but it did not fit my needs, because:

  • In the old times, it fired up a Napari instance, instead of living nicely together with plugins in an existing Napari viewer. (Solve with #140)
  • It is not itself registered as a dock widget in Napari, but its SdataWidget attribute. That means Interactive is not an alive object while using Napari, but it gets garbage-collected immediately after all contained dock widgets are instantiated and registered in Napari. Currently, I programmatically access the plugin through the registered dock widget.
  • I only need the element selection, the other docks (range slider, colorbar, AnnData obs/X selector) are not useful but take all space so that other plugins cannot be used anymore.

The original API proposed in this was to convert SpatialData elements directly to Napari layers without GUI, so that me as user would add the layers to Napari.

In contrast to that, I would also be fine with a different API for GUI automation:

  • [ ] get_spatialdata_interactive(): Static function to get a reference to the existing controler (Interactive), or instantiate a new one. This could be achieved by making Interactive a singleton and having a static (module-level) reference to it. Or add a back-reference from SdataWidget→Interactive to allow accessing the API when you have a dock reference.
  • [ ] set_spatialdata(sdata) or add_spatialdata(sdata): Add a given SpatialData instance to the SdataWidget dock so that its coordinate systems and elements are listed in the widgets. Currently, it tries to support multiple SpatialData elements in an evented list SdataWidget._sdata. If this is too complex to maintain, I would much rather have a single SpatialData. Also, the coordinate systems widget only shows coordinates systems of the first loaded SpatialData and does not update when another one is added to the evented list afterward.
  • [x] add_element(element, element_coordinate_system): Add an element to the viewer and show it selected in the elements widget. Return the corresponding Napari layers so I don't have to guess them with heuristics (like subtracting layers after - before).
  • [x] switch_coordinate_system(coordinate_system): Set a coordinate systems to elements in the viewer and show it selected in the widget.
  • [ ] Add a function or parameters to the constructor to control which docks to show or hide (SdataWidget, QtAdataViewWidget)

aeisenbarth avatar Mar 13 '24 20:03 aeisenbarth