def test_forward_refs_return_annotation(): """Test that forward refs return annotations get resolved.""" @magicgui def testA() -> int: return 1 @magicgui def testB() -> "tests.MyInt": # type: ignore # noqa return 1 from tests import MyInt results = [] register_type(MyInt, return_callback=lambda *x: results.append(x)) testA() assert not results testB() gui, result, return_annotation = results[0] assert isinstance(gui, widgets.FunctionGui) assert result == 1 # the forward ref has been resolved assert return_annotation is MyInt
def test_register_return_callback(): """Test that registering a return callback works.""" def check_value(gui, value, rettype): assert value == 1 class Base: pass class Sub(Base): pass register_type(int, return_callback=check_value) register_type(Base, return_callback=check_value) @magicgui def func(a=1) -> int: return a func() with pytest.raises(AssertionError): func(3) @magicgui def func2(a=1) -> Sub: return a func2()
def register_types_with_magicgui(): """Register napari types with magicgui. Parameter Annotations -> Widgets: napari.layers.Layer, will be rendered as a ComboBox. if a parameter is annotated as a subclass Layer type, then the combobox options will be limited to that layer type. napari.Viewer, will be rendered as a ComboBox, with the current viewer as the only choice. Return Annotations -> Widgets: napari.layers.Layer will add a new layer to the Viewer. if a return is annotated as a subclass of Layer, then the corresponding layer type will be added. As of 0.4.3, the user must return an actual layer instance see `add_layer_to_viewer` for detail napari.types.<layer_type>Data will add a new layer to the Viewer. using a bare data array (e.g. numpy array) as a return value. napari.types.LayerDataTuple will add a new layer to the Viewer. and expects the user to return a single layer data tuple List[napari.types.LayerDataTuple] will add multiple new layer to the Viewer. And expects the user to return a list of layer data tuples. """ # only run this function once if getattr(register_types_with_magicgui, '_called', False): return register_types_with_magicgui._called = True from magicgui.widgets import FunctionGui # the widget field in `_source.py` was defined with a forward reference # to avoid having to import magicgui when we define the layer `Source` obj. # Now that we know we have imported magicgui, we update that forward ref # https://pydantic-docs.helpmanual.io/usage/postponed_annotations/ Source.update_forward_refs(FunctionGui=FunctionGui) register_type(layers.Layer, choices=get_layers, return_callback=add_layer_to_viewer) register_type(Viewer, bind=find_viewer_ancestor) register_type( types.LayerDataTuple, return_callback=add_layer_data_tuples_to_viewer, ) register_type( List[types.LayerDataTuple], return_callback=add_layer_data_tuples_to_viewer, ) for layer_name in layers.NAMES: data_type = getattr(types, f'{layer_name.title()}Data') register_type( data_type, choices=get_layers_data, return_callback=add_layer_data_to_viewer, )
def _register_types_with_magicgui(): """Register ``napari.types`` objects with magicgui.""" import sys from concurrent.futures import Future from magicgui import register_type from . import layers from .utils import _magicgui as _mgui for _type in (LayerDataTuple, List[LayerDataTuple]): register_type( _type, return_callback=_mgui.add_layer_data_tuples_to_viewer, ) if sys.version_info >= (3, 9): future_type = Future[_type] # type: ignore register_type(future_type, return_callback=_mgui.add_future_data) for layer_name in layers.NAMES: data_type = globals().get(f'{layer_name.title()}Data') register_type( data_type, choices=_mgui.get_layers_data, return_callback=_mgui.add_layer_data_to_viewer, ) if sys.version_info >= (3, 9): register_type( Future[data_type], # type: ignore choices=_mgui.get_layers_data, return_callback=partial( _mgui.add_future_data, _from_tuple=False ), )
def register_types_with_magicgui(): """Register napari types with magicgui. Parameter Annotations -> Widgets: napari.layers.Layer, will be rendered as a ComboBox. if a parameter is annotated as a subclass Layer type, then the combobox options will be limited to that layer type. Return Annotations -> Widgets: napari.layers.Layer will add a new layer to the Viewer. if a return is annotated as a subclass of Layer, then the corresponding layer type will be added. see `show_layer_result` for detail """ register_type(Layer, choices=get_layers, return_callback=show_layer_result) register_type(Viewer, choices=get_viewers)
def _register(): from functools import partial import magicgui from .. import layers, types from ..types import LayerDataTuple from ..utils import _magicgui as _mgui for _type in (LayerDataTuple, List[LayerDataTuple]): magicgui.register_type(FunctionWorker[_type], return_callback=_mgui.add_worker_data) for layer_name in layers.NAMES: _type = getattr(types, f'{layer_name.title()}Data') magicgui.register_type( FunctionWorker[_type], return_callback=partial(_mgui.add_worker_data, _from_tuple=False), )
def register_types_with_magicgui(): """Register napari types with magicgui. Parameter Annotations -> Widgets: napari.layers.Layer, will be rendered as a ComboBox. if a parameter is annotated as a subclass Layer type, then the combobox options will be limited to that layer type. napari.Viewer, will be rendered as a ComboBox, with the current viewer as the only choice. Return Annotations -> Widgets: napari.layers.Layer will add a new layer to the Viewer. if a return is annotated as a subclass of Layer, then the corresponding layer type will be added. As of 0.4.3, the user must return an actual layer instance see `add_layer_to_viewer` for detail napari.types.<layer_type>Data will add a new layer to the Viewer. using a bare data array (e.g. numpy array) as a return value. napari.types.LayerDataTuple will add a new layer to the Viewer. and expects the user to return a single layer data tuple List[napari.types.LayerDataTuple] will add multiple new layer to the Viewer. And expects the user to return a list of layer data tuples. """ register_type(layers.Layer, choices=get_layers, return_callback=add_layer_to_viewer) register_type(Viewer, bind=find_viewer_ancestor) register_type( types.LayerDataTuple, return_callback=add_layer_data_tuples_to_viewer, ) register_type( List[types.LayerDataTuple], return_callback=add_layer_data_tuples_to_viewer, ) for layer_name in layers.NAMES: data_type = getattr(types, f'{layer_name.title()}Data') register_type( data_type, choices=get_layers_data, return_callback=add_layer_data_to_viewer, )
def test_register_types(qtbot): """Test that we can register custom widget classes for certain types.""" # must provide a non-None choices or widget_type with pytest.raises(ValueError): register_type(str, choices=None) register_type(int, widget_type=QtW.QLineEdit) # this works, but choices overrides widget_type, and warns the user with pytest.warns(UserWarning): register_type(str, choices=["works", "cool", "huh"], widget_type=QtW.QLineEdit) class Main: pass class Sub(Main): pass class Main2: pass class Sub2(Main2): pass register_type(Main, choices=[1, 2, 3]) register_type(Main2, widget_type=QtW.QLineEdit) @magicgui def func(a: str = "works", b: int = 3, c: Sub = None, d: Sub2 = None): return a gui = func.Gui() assert isinstance(gui.get_widget("a"), QtW.QComboBox) assert isinstance(gui.get_widget("b"), QtW.QLineEdit) assert isinstance(gui.get_widget("c"), QtW.QComboBox) assert isinstance(gui.get_widget("d"), QtW.QLineEdit) core.reset_type(str) core.reset_type(int)
def test_register_types(): """Test that we can register custom widget classes for certain types.""" # must provide a non-None choices or widget_type with pytest.raises(ValueError): register_type(str, choices=None) register_type(int, widget_type="LineEdit") # this works, but choices overrides widget_type, and warns the user with pytest.warns(UserWarning): register_type(str, choices=["works", "cool", "huh"], widget_type="LineEdit") class Main: pass class Sub(Main): pass class Main2: pass class Sub2(Main2): pass register_type(Main, choices=[1, 2, 3]) register_type(Main2, widget_type="LineEdit") @magicgui def func(a: str = "works", b: int = 3, c: Sub = None, d: Sub2 = None): return a assert isinstance(func.a, widgets.ComboBox) assert isinstance(func.b, widgets.LineEdit) assert isinstance(func.c, widgets.ComboBox) assert isinstance(func.d, widgets.LineEdit) del type_map._TYPE_DEFS[str] del type_map._TYPE_DEFS[int]
def register_type(type_: type, **kwargs) -> type: """Decorator to register a type with magicgui.""" import magicgui magicgui.register_type(type_, **kwargs) return type_
import skimage.filters import napari from magicgui import magicgui, register_type def get_viewers(gui, *args): """Get the viewer that the magicwidget is in.""" try: return (gui.parent().qt_viewer.viewer, ) except AttributeError: return tuple(v for v in globals().values() if isinstance(v, napari.Viewer)) register_type(napari.Viewer, choices=get_viewers) with napari.gui_qt(): # create a viewer and add some images viewer = napari.Viewer(title="Viewer A") viewer.add_image(skimage.data.astronaut(), name="astronaut") @magicgui(call_button="call", viewer={"visible": False}) def takes_viewer(viewer: napari.Viewer): """Just print the viewer.""" print(viewer) # instantiate the widget gui = takes_viewer.Gui() # Add it to the napari viewer viewer.window.add_dock_widget(gui)
paint_new_label, _save_labels) # # additional gui elements # def get_viewers(gui, *args): try: viewer = gui.parent().qt_viewer.viewer return (viewer, ) except AttributeError: return tuple(v for v in globals().values() if isinstance(v, Viewer)) register_type(Viewer, choices=get_viewers) # TODO expose the partial parameter to the gui @magicgui(call_button='save annotations [shfit + s]', viewer={'visible': False}) def save_gui(viewer: Viewer): _save_labels(viewer) @magicgui(call_button='update layers [u]', viewer={"visible": False}) def update_layers_gui(viewer: Viewer): update_layers(viewer) @magicgui(call_button='hide annotated cells [h]', viewer={"visible": False})