nwb-extensions / ndx-template

A template for creating Neurodata Extensions for the NWB neurophysiology data standard
https://nwb.org/
Other
5 stars 8 forks source link

Add extension function for NWBWidgets #73

Closed alejoe91 closed 5 months ago

alejoe91 commented 1 year ago

Hi guys,

After discussing with @bendichter, we came up with a possible solution to standardize how an extension could automatically define a custom widget.

Basically, an extension could define a show_widget() function of the new class (the name should be standardized so that nwbwidgets can automatically discover custom functions).

In the pynwb.extension_name.__init.__.py:

# Make them accessible at the package level
MyExtensionClass = get_class("MyExtensionClass", "ndx-my-extension")
MyExtensionClass.show_widget = my_show_widget_function

Then, [nwbwidgets.nwb2widget]() could automatically discover and dynamically extend the neurodata_vis_spec as follows:

def nwb2widget(node, neurodata_vis_spec=default_neurodata_vis_spec):
    # pseudo-code - could be recursive
    for key, entry in node.items():
        if hasattr(entry, "show_widget"):
            neurodata_vis_spec[type(entry)] = entry.show_widget
    return nwb2widget_base(node, neurodata_vis_spec)

This is just an idea, happy to discuss other options.

What do you guys think? @rly @oruebel @bendichter

bendichter commented 1 year ago

@alejoe91 let's make the attribute just .widget and let it be either a class or a dict that would support defining multiple widgets for a single class. In this case the keys are str names and the values are widget classes.

alejoe91 commented 1 year ago

Good idea! What would be a use case with multiple widgets for a single class?

bendichter commented 1 year ago

There are a bunch. See all of the dictionaries e.g. here: https://github.com/NeurodataWithoutBorders/nwbwidgets/blob/d62bed037e24c75f8a9215b8f0ab81e975763700/nwbwidgets/view.py#L66-L75

Also, let's keep the neurodata_vis_spec as first priority and not change it automatically so a user can modify behavior by modifying that dictionary. So it would be something like changing:

https://github.com/NeurodataWithoutBorders/nwbwidgets/blob/d62bed037e24c75f8a9215b8f0ab81e975763700/nwbwidgets/base.py#L197-L209

to

def nwb2widget(node, neurodata_vis_spec: dict, **pass_kwargs) -> widgets.Widget:
    for ndtype in type(node).__mro__:
        if ndtype in neurodata_vis_spec or hasattr(ndtype, "widget"):
            spec = neurodata_vis_spec.get(ndtype, ndtype.widget)
            if isinstance(spec, dict):
                return lazy_tabs(spec, node)
            elif callable(spec):
                visualization = spec(node, neurodata_vis_spec=neurodata_vis_spec, **pass_kwargs)
                return vis2widget(visualization)
    out1 = widgets.Output()
    with out1:
        print(node)  # Is this necessary?
    return out1
rly commented 10 months ago

I think this is a good idea. I implemented an example in the template in #77. Please let me know what you think.