Code Monkey home page Code Monkey logo

magicgui's Introduction

magicgui

magicgui is released under the MIT license. magicgui on PyPI magicgui on conda-forge magicgui python version support

magicgui build status magicgui code coverage cite magicgui

build GUIs from type annotations, using magic.

๐Ÿ“– Docs

https://pyapp-kit.github.io/magicgui/

Installation

magicgui uses qtpy to support both pyside2 and pyqt5 backends. However, you must have one of those installed for magicgui to work.

install with pip

pip install magicgui[pyqt5]
# or
pip install magicgui[pyside2]

or with conda:

conda install -c conda-forge magicgui pyqt  # or pyside2 instead of pyqt

โ„น๏ธ If you'd like to help us extend support to a different backend, please open an issue.

Basic usage

from magicgui import magicgui
from enum import Enum

class Medium(Enum):
    Glass = 1.520
    Oil = 1.515
    Water = 1.333
    Air = 1.0003

# decorate your function with the @magicgui decorator
@magicgui(call_button="calculate", result_widget=True)
def snells_law(aoi=30.0, n1=Medium.Glass, n2=Medium.Water, degrees=True):
    import math

    aoi = math.radians(aoi) if degrees else aoi
    try:
        result = math.asin(n1.value * math.sin(aoi) / n2.value)
        return math.degrees(result) if degrees else result
    except ValueError:
        return "Total internal reflection!"

# your function is now capable of showing a GUI
snells_law.show(run=True)

snells

But that's just the beginning! Please see Documentation for many more details and usage examples.

Contributing

Contributions are welcome!

See contributing guide here.

magicgui's People

Contributors

aeisenbarth avatar aganders3 avatar brisvag avatar czaki avatar dependabot[bot] avatar dstansby avatar genevievebuckley avatar glyg avatar gselzer avatar haesleinhuepf avatar hagaihargil avatar hanjinliu avatar hmaarrfk avatar jaimergp avatar jamesyan-git avatar jni avatar kianmeng avatar maweigert avatar mbphys avatar pre-commit-ci[bot] avatar psobolewskiphd avatar qin-yu avatar sofroniewn avatar tlambert03 avatar uschmidt83 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

magicgui's Issues

v0.2.6 release summary

v0.2.6 is a significant feature release, introducing a number of new widgets and APIs:

  • New Table Widget allowing easy creation and modification of Table UIs using a variety of pure python types as input (#61)
  • Tooltips for each widget in a @magicgui are now automatically taken from docstrings (numpy, google, and sphinx-rst format accepted)(#100)
  • New ProgressBar widget (#104) and magicgui.tqdm wrapper (#105) allow both manual and automatically-added progress bars to long-running iterator-based functions. magicgui.tqdm.tqdm acts as a drop-in replacement for tqdm.tqdm that will fall back to the standard (console output) behavior if used outside of a magicgui function, or inside of a magicgui widget that is not yet visible.
  • New MainWindow/MainFunctionGui subclasses allow creating top level "application" windows, with a basic API for adding items to the application menubar (#110).
  • The new @magic_factory decorator creates a callable that, when called, returns a FunctionGui instance (as opposed to @magicgui which immediately creates the FunctionGui instance. Think of this as returning a "class" as opposed to returning an "instance":
    @magic_factory(call_button=True)
    def my_factory(x: int, y = 'hi'):
        ...
    
    # can add to or override original factory arguments
    widget = my_factory(main_window=True)
    widget.show()
  • "vertical" is now the default layout for Containers and magicgui widgets.

Progress bar widget for long-running computations

Have you thought much about whether it might be possible to add a progress bar that updates with long-running computations?

Most of the current examples (snell's law, etc.) are fast by design but I imagine many real-word cases might be much slower, and it might be good for users to have some kind of indication that things are running. This might get complicated because you'd need analysis functions to have a way to communicate back to the magicgui dockable widget. I wanted to get your thoughts given the work you did on two way communication with threads in napari.

Relevant links:

Removal of docks: are widgets singletons?

I am writing an app that requires switching between two widgets, depending on the value of a checkbox.

Here's a snippet that I thought would do the trick:

with napari.gui_qt():
    gui_state = {
        'latest': None
    }

    @magicgui(call_button='Recreate dock')
    def horizontal_widget(horizontal: bool = True, label: str = 'Horizontal'):
        """Adds, subtracts, multiplies, or divides two image layers of similar shape."""
        if not horizontal:
            viewer.window.remove_dock_widget(gui_state['latest'])
            vertical = vertical_widget.Gui()
            gui_state['latest'] = viewer.window.add_dock_widget(vertical)

        print("Fired: horizontal", horizontal)

    @magicgui(call_button='Recreate dock')
    def vertical_widget(horizontal: bool = False, label: str = 'Vertical'):
        """Adds, subtracts, multiplies, or divides two image layers of similar shape."""
        if horizontal:
            viewer.window.remove_dock_widget(gui_state['latest'])
            horizontal = horizontal_widget.Gui()
            gui_state['latest'] = viewer.window.add_dock_widget(horizontal)

        print("Fired: vertical", horizontal)

    viewer = napari.Viewer()
    viewer.add_image(image)
    gui_state['latest'] = viewer.window.add_dock_widget(horizontal_widget.Gui())

However, it looks like the Gui instances may be singletons, since I see the following segfault:

WARNING: Traceback (most recent call last):
  File "/home/stefan/envs/py38/lib/python3.8/site-packages/magicgui/core.py", line 199, in <lambda>
    self.call_button.clicked.connect(lambda checked: self.__call__())
  File "/home/stefan/envs/py38/lib/python3.8/site-packages/magicgui/core.py", line 527, in __call__
    self.called.emit(value)
RuntimeError: wrapped C/C++ object of type MagicGui has been deleted

I also noticed a discrepancy in the docs:

    def add_dock_widget(
...
        Parameters
        ----------
        widget : QWidget
            `widget` will be added as QDockWidget's main widget.
    def remove_dock_widget(self, widget):
        """Removes specified dock widget.

        Parameters
        ----------
            widget : QWidget | str
                If widget == 'all', all docked widgets will be removed.
        """

So, it looks like add_dock_widget and remove_dock_widget can take the same object as input. But, instead, add_dock_widget manufactures a new widget that has to be provided as input to remove_dock_widget. Perhaps it is worth clarifying in the docstring exactly which object should be used.

Sorry if I filed this in the wrong place: it seems to straddle Napari and magicgui.

Side question: I would love for this to trigger on clicking the checkbox, but looks like currently a button is needed to fire an action? I couldn't find anything in the docs about "instant" updates.

Versions: napari (master), magicgui (master)

QTreeWidget from list of path like strings

I was about to code something up as a one off (and probably still wise for me to start there) but then I thought it might be ultimately a nice re-usable piece of Qt code we'd want to provide to plugin devs. I have a list of paths (they happen to come from inside a zip file from a url, but really they could also come from anywhere) - a small snapshot looks like

['RxRx19a/',
 'RxRx19a/LICENSE',
 'RxRx19a/images/',
 'RxRx19a/images/VERO-1/',
 'RxRx19a/images/VERO-1/Plate1/',
 'RxRx19a/images/VERO-1/Plate1/AC41_s4_w3.png',
 'RxRx19a/images/VERO-1/Plate1/G38_s3_w1.png',....]

and I'd like to use a QTreeWidget to browse them as described in posts like this one on stackoverflow.

When i select a path with a png I'd then like to trigger some calls that will lead to a new image being displayed in the viewer - i.e. turning napari into a little file browser for this remote zip. It might also be nice to have similar browser capability for local files too.

Curious what you think of this @tlambert03 or if you have any tips before I got going? I'd say this is more of a hobby use of napari rather than a top development priority, but always fun to be trying to push it in new ways :-)

Need consensus on "layout" vs "orientation"

Howdie @jni, @sofroniewn, @haesleinhuepf

Just released 0.2.0, but then realized I still need to resolve the "layout" vs "orientation" wording from #54.

I need some input! "orientation" makes sense for an "HBox" or "VBox"-like layout, but I would like to eventually add support for a grid layout ... So "layout" seems more flexible. I like it slightly less for non-gridded containers, but would like to avoid having both orientation and layout words floating around. I need someone to tell me what to do :)

conda recipe

Hello!

Magicgui looks pretty neat! Would you guys mind to make this available via conda-forge? I have a recipe ready that I could make a PR with conda-forge anytime.

Cheers!

v0.2.3 release summary

v0.2.3 adds two new widgets DateEdit and TimeEdit (for datetime.date and datetime.time types respectively), in addition to the existing DateTimeEdit widget. It also continues to improve warnings and deprecation messages from the v0.2.0 release.

Recognize widget types as strings

Similar to #46, I'd like to be able to pass widget types as strings to avoid additional dependencies, for example from the magic gui parameter sweep example https://magicgui.readthedocs.io/en/latest/examples/napari_parameter_sweep/

    @magicgui(
        auto_call=True,
        sigma={"widget_type": 'magicgui._qt.widgets.QDoubleSlider', "maximum": 6, "fixedWidth": 400},
        mode={"choices": ["reflect", "constant", "nearest", "mirror", "wrap"]},
    )

but right now I get the following error

  File "/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/core.py", line 652, in __init__
    **param_options,
  File "/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/core.py", line 190, in __init__
    dtype=(None if param.annotation is param.empty else param.annotation),
  File "/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/core.py", line 360, in set_widget
    widget = api.make_widget(WidgetType, name=name, parent=self, **_options)
  File "/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/_qt/_qt.py", line 188, in make_widget
    widget = WidgetType(parent=parent)
TypeError: 'str' object is not callable

Additionally I'm not sure if this will be changed in #43, but it would be nice not to have to use a private import or a Q specific syntax to get a DoubleSlider.

Support pydantic types

As @sofroniewn suggested, pydantic's constrained types make for a natural way to express things like ranges and widget parameters as type annotations. We should detect and support pydantic types

Add support for Annotated type

Pulling out this great tip by @VolkerH from https://github.com/napari/magicgui/issues/20#issuecomment-647888706 into a new issue. We can use the Annotated type (PEP 593) available through typing_extensions for parameter-specific metadata.

so, as demonstrated by @jni in https://github.com/napari/magicgui/issues/20#issuecomment-648504543, we could put argument-specific options directly in the type hint:

@magicgui(arg={'minimum': 10, 'maximum': 100})
def my_func(arg=50): ...

# could become this:
@magicgui
def my_func(arg: Annotated[int, {'minimum': 10, 'maximum': 100}] = 50): ...

v0.2.1 release summary

v0.2.1 fixes some issues with the 0.2.0 release. ForwardRef annotations are now resolved automatically on both parameter and return type annotations. And the orientation parameter on Container widgets (such as those returned by the magicgui decorator) has been renamed back to layout as in <v0.2.0. Test coverage is also improved.

Pull signature parsing code from MagicGuiBase

To better reflect the fact that magicgui is basically just a mapping between types and widgets, I'm thinking that this code that inspects the function signature should be pulled out of the MagicGuiBase class:

https://github.com/napari/magicgui/blob/db4c8164915bf63d53cafa476564ec4e5ab78848/magicgui/core.py#L184-L192

and turned into a helper function that accepts a function and returns a dict of parameter names and types. The base class then can be cleaner, and more of a widget constructor. This would also make #7 easier, as the base class would just directly accept the types (and it needn't be backed by a function at all).

Automagically add labels per field

I love MagicGUI, thanks for making it! What do you think of automatically adding labels next to each field? In your canonical example from the docs, this means that each field would have a little label with the name of the parameter its referencing (n1, n2, etc), right above or to the left of it.

This could obviously be optional (and off by default), and there can also be some simple heuristic to transform snake_case variable names to native-English-looking words.

Contrib module

Thinking about the try: from colour import Color line, and also the _magicgui.py file in napari ... neither pattern feels quite right.

I'm thinking both of these belong in some sort of magicgui.contrib module...

  • we shouldn't try to import every potentially supported package, but rather search for a specific type annotation through a known list of supported namespaces in the contrib module. For instance, a mapping of namespace to a register_type-like function...
  • users can still use the register_type function for one-off functionality, or third party packages can use it if they don't want to contribute it to magicgui... but a contrib module would collect this functionality and hopefully make it so that neither package needs to be imported until a user has annotated something in a decorated function as a "contrib-supported" type.
  • some of these (like color) might be useful enough that we would support them as extras pip install magicgui[color], but they are still imported lazily.

thoughts @GenevieveBuckley? @jni? ... suggestions for how that should be implemented in terms of package structure?

Label option for boolean parameters has no effect

Describe the bug
When setting a custom label attribute for a boolean parameter in a magicgui decorated function, the label of the object in the GUI doesn't update accordingly.

To Reproduce

from magicgui import magicgui

@magicgui(check={'label': 'ABC'})
def test(check: bool):
    pass


if __name__ == '__main__':
    test.show(run=True)  # the label is 'check', not 'ABC'.

Expected behavior
The shown label should be 'ABC'.

Environment (please complete the following information):

  • OS: Linux
  • backend: Qt
  • magicgui version: 0.2.6

Best way to add general docs and help menu?

Hi :)

My main use case for magicgui is to replace command line applications with friendlier, GUI-based tools. This is very helpful for my non-coder colleagues, and magicgui has been invaluable on that front. One thing that I "miss" from CLI tools is documentation for the available parameters and more general "help documentation" which explains the use case that this current app is here to solve. In CLI tools these bits of information can usually be invoked with a --help flag which is obviously missing here.

I was wondering whether you think it would be possible to add text box for each parameter of the GUI which appears following a hover "event". This text box may pull its information from the docstring of the main annotated function (moar magik!). If you think it's doable I might try to dip my toes into this magical swamp.

In addition, a more traditional "Help" context menu or button can be optionally activated by adding a "help_button: True" entry to the main magicgui dictionary. This button can again get its information from the main body of the function's docstring.

How does it sound to you? I believe some docstring magic is already happening in order to decipher each type's designated widget.

Proposal: don't instantiate widgets when no widget is defined for specific type

I've encountered a few situations where I wanted to make a widget for a particular function that takes various arguments of types unknown to magicgui. This was fine because I only wanted to set one of the arguments with the gui, and the rest I wanted to set programmatically โ€” only I couldn't, because magicgui defaults to literal_eval when it doesn't know the arguments, and this is very limiting. (Yes I am aware that I proposed this. ๐Ÿ˜)

Instead, I propose that magicgui only produces the widgets it knows. The ones it doesn't should be set programmatically by other means. An error is raised if a required argument's value is not set at call time.

Adding a stretch to the bottom of widgets

Thinking about #111 a little more, I might also propose that we also add a stretch by default to the bottom of the widget. I personally would find that more visually appealing (we could think about making it a kwarg if people wanted control, but that wouldn't be needed for me). Curious how others feel and if they have a similar personal preference or not

Originally posted by @sofroniewn in https://github.com/napari/magicgui/issues/121#issuecomment-765813271

v0.2.0 release summary

v0.2.0 includes a complete rewrite of magicgui. The primary goals were as follows:

  • make a clean separation between the Qt backend and the end-use API, clarifying the interface that a backend must implement in order to work with magicgui
  • create a "direct API" that enables procedural widget creation, with the potential for subclassing and custom widget creation
  • create a more direct link between an individual widget and an inspect.Parameter object, and a collection or layout of widgets and an inspect.Signature object.

See PR #43 for full details of the rewrite.

Deprecations and possible breaking changes!

Some of the API has been deprecated or changed, though an attempt was made to make the pre-0.2.0 API still work (with warnings). Please see the v0.2.0 migration guide for details.

Lastly, we have new documentation, using the amazing jupyter-book project! Note the new url at https://napari.org/magicgui

Feature request: support for QFileDialog (file and directory choosers)

One very common component of GUIs I build for users is a file or directory chooser. The use case is usually:

  • select the input data (might be a folder full or images, or sometimes a single input file)
  • select a folder where you want to save the output results
  • tell me what other parameters you want to use for the analysis

There is QFileDialog, which I think would fulfill this use case.

Applying successives filters / function on the same layer

โ“ Questions and Help

First of all, thank you for magicgui, just started playing with it and it's pretty awesome. I have a pretty simple question:

If I take the example from the documentation:

@magicgui(
        auto_call=True,
        sigma={"widget_type": QDoubleSlider, "maximum": 6, "fixedWidth": 400},
        mode={"choices": ["reflect", "constant", "nearest", "mirror", "wrap"]},
    )
    def gaussian_blur(layer: Image, sigma: float = 1.0, mode="nearest") -> Image:
        """Apply a gaussian blur to ``layer``."""
        if layer:
            return skimage.filters.gaussian(layer.data, sigma=sigma, mode=mode)

But let say I wanted to apply skimage.filters.median as well to the same layer image while being able to adjust the selem size, what would be the best and cleanest way to go about it? I try few things but ended work on 2 separates layers.

[ ] I have searched the documentation

Enable magicgui decorator on class member functions

Hi Talley @tlambert03 ,

thanks again for your support recently on the forum. I think I have a related feature request. To simplify my code, it would be nice if one could use magicgui on class member functions. In this way, the function could have access to a broader context, which is not handled in the magicgui.

To illustrate what I mean, take a look at this code:

import napari

from magicgui import magicgui
from napari.layers import Image


class MyObject:
    def __init__(self):
        self.counter = 0

    @magicgui(auto_call=True)
    def process_image(self, image : Image, sigma: float = 5):
        self.counter = self.counter + sigma
        print(self)

# load data
from skimage.io import imread
image = imread('https://samples.fiji.sc/blobs.png')

# start up napari
with napari.gui_qt():
    viewer = napari.Viewer()
    viewer.add_image(image, name='blobs')

    # generate a Graphical User Interface from the function above magically
    gui = MyObject().process_image.Gui()
    viewer.window.add_dock_widget(gui)

When executing it and increasing the sigma in the magic user interface, this error is thrown:

ERROR:root:Unhandled exception:
Traceback (most recent call last):
  File "C:\Users\rober\miniconda3\lib\site-packages\magicgui\core.py", line 532, in __call__
    value = self.func(**_kwargs)
  File "C:/structure/code/pyclesperanto_prototype/demo/napari_gui/napari_magicgui.py", line 17, in process_image
    self.counter = self.counter + sigma
AttributeError: 'str' object has no attribute 'counter'

Thus, the self variable is not correctly set in such a context. Do you think this could be implemented? I'm also open to other solutions if you see some.

Thanks!

Cheers,
Robert

Display slider current value and range min/max in the magicgui widget

I've been doing some work involving magicgui, and have noticed that sliders don't show any indication of what value currently is selected.

For a minimal working example, in napari_parameter_sweep.py the image updates but the slider doesn't show any indication of what the current selected value is, or for that matter the min & max of the slider range (you'd have to go digging in the code for that). This is less than ideal for a parameter sweep, where you want to be able to make a note of what the best values were at the end.

Unable to visualize QBoxlayout nor QTable

Hi!

I have been testing the new version of napari and magicgui and was able to get the button example working.

On the other hand when I tried to include additional qt widgets, I notice that this ones where not showing on the napari window. When I remove the @magicgui, and call the functions directly this problem was solved, but I am unable to adjust properly the window specs of, for example, the QBoxLayout.

Cloud you provide some guidance on how to improve this code?

image

import os
os.environ["DLClight"]="True"
import napari
import math
from enum import Enum
from magicgui import magicgui
import deeplabcut as dlc
from skimage import data
from magicgui import register_type
from qtpy.QtWidgets import (QTableWidget, QWidget, QBoxLayout, QPushButton,
                            QTableWidgetItem, QLabel, QLineEdit)
from qtpy import QtCore

with napari.gui_qt():
    viewer = napari.view_image(data.astronaut(), rgb=True)

    table_roi = QTableWidget()
    table_roi.setFixedWidth(300)
    table_roi.setColumnCount(1)
    table_roi.setRowCount(15)
    table_roi.setItem(0, 0, QTableWidgetItem(cfg['Task']))
    table_roi.setItem(0, 1, QTableWidgetItem("Cell (1,2)"))
    table_roi.setItem(1, 0, QTableWidgetItem("Cell (2,1)"))
    table_roi.setItem(1, 1, QTableWidgetItem("Cell (2,2)"))

    viewer.window.add_dock_widget(table_roi, area='bottom')

    @magicgui(label_selection={'widget_type': QBoxLayout})
    def label_selection():
        lbl1 = QLabel()
        lbl1.move(15, 1)
        lbl1.setText('test')
        box1 = QLineEdit()
        box1.move(1, 2)

    label_gui = label_selection.Gui(show=True)

    viewer.window.add_dock_widget(label_gui, area='right')

v0.2.5 release summary

v0.2.5 greatly improves support for binding a value or a callback to a function parameter, and fixes a bug in recursively updating categorical widgets nested deeply inside of a container.

Improve callable type (and example)

in the callable example:

def f(x: int, y="a string") -> str:
    return f"{y} {x}"

def g(x: int = 6, y="another string") -> str:
    return f"{y} asdfsdf {x}"

@magicgui(call_button=True, func={"choices": ["f", "g"]})
def example(func="f"):
    pass

@jni pointed out in https://github.com/napari/magicgui/pull/43#discussion_r539015715:

Could this not be "choices": [f, g] and def example(func=f):? (with a bit of extra magic to grab f.__name__ if f is a callable)?

This needs to be followed up on.

napari examples in the docs are broken

I just tried to go through the example napari integrations and it seems like they're currently broken - I guess this is a result of the 0.2.0 update

The decorated functions are returned as FunctionGui objects which can't be added with the Viewer.add_dock_widget() method, adding the native property of the FunctionGui instead added the widget to the viewer but it wasn't aware of the napari side of things.

What is the best way to integrate with the napari viewer at the moment? I didn't find an obvious replacement method in the window of the viewer. Once I know I'd be happy to update the docs accordingly

Hope you're enjoying the holidays!

"Layout" parameter doesn't work anymore in recent dev version

Describe the bug
Hi Talley @tlambert03 ,

this is related to our quick try of the recent master branch as discussed in #53 , the magicgui layout='vertical' parameter doesn't work anymore. If this is intentional, no big deal. I can work in the meantime. I just switched back to the former released version.

To Reproduce

Expected behavior
It should not bring an error in the following line

@magicgui(auto_call=True, layout='vertical')
def filter(input1: Image, operation: Filter = Filter.please_select, x: float = 1, y: float = 1, z: float = 0):

However, it crashes with this error:

C:\Users\rober\miniconda3\python.exe C:/structure/code/pyclesperanto_prototype/demo/napari_gui/particle_analyser.py
Traceback (most recent call last):
  File "C:/structure/code/pyclesperanto_prototype/demo/napari_gui/particle_analyser.py", line 65, in <module>
    def filter(input1: Image, operation: Filter = Filter.please_select, x: float = 1, y: float = 1, z: float = 0):
  File "c:\structure\code\magicgui\magicgui\function_gui.py", line 280, in inner_func
    func_gui = FunctionGui(
  File "c:\structure\code\magicgui\magicgui\function_gui.py", line 75, in __init__
    sig = magic_signature(function, gui_options=param_options)
  File "c:\structure\code\magicgui\magicgui\signature.py", line 269, in magic_signature
    raise ValueError(
ValueError: keyword arguments (gui_options) MUST match parameters in the decorated function.
Got extra keys: {'layout'}

Environment (please complete the following information):

  • OS: Windows
  • magicgui version 0.1.7-dev7
  • napari 0.4.3-dev2
  • PtQt5 5.15.1

Tooltips

Widgets should be able to have tooltips. My first thought is to parse the docstring and take part (or all?) of the parameter description for each parameter, along with, of course, accepting a tooltip: str= .. parameter directly on the widget.

annotating an argument as magicgui.types.PathLike does not create a files widget

Describe the bug

Annotating a function argument as having type magicgui.types.PathLike does not map to a files widget. I would have expected to do so. Instead I get the error:

/Users/jni/conda/envs/all2/lib/python3.9/site-packages/magicgui/widgets/_bases/widget.py:66: FutureWarning:

EmptyWidget.__init__() got unexpected keyword arguments {'mode'}.
In the future this will raise an exception

To Reproduce
Steps to reproduce the behavior:

from magicgui import magicgui, types


@magicgui(fn={'mode': 'r'})
def widget(fn: types.PathLike):
    print(fn)


widget.show(run=True)

Expected behavior

I expected this to have roughly equivalent behaviour to:

import pathlib
from magicgui import magicgui


@magicgui(fn={'mode': 'r'})
def widget(fn: pathlib.Path):
    print(fn)


widget.show(run=True)

I tried using PathLike because I wanted to express that my function does take a Path but can also take a string representing a Path.

Screenshots
If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

  • OS: [e.g. Windows, Mac] Mac
  • backend: [e.g. Qt 5.13.0] Qt something or other =P
  • magicgui version [e.g. 0.1.0] 0.2.6

Move `gui_only` from `Widget` to `FunctionGui`

The gui_only parameter to Widget really only makes sense the context of a FunctionGui, where we'd be trying to add a widget to the GUI that doesn't need to represent a parameter in the function. It should be moved off of Widget and that logic contained to FunctionGui

Decide how to deal with empty string vs None

When generating a magicgui for skimage.filters.sato, because we don't yet try to parse docstrings for "choices" in a dropdown menu, the "mode" parameter is rendered as a plain text edit.
If the user doesn't enter anything, then it gets delivered to the function as the empty string instead of None, and skimage raises RuntimeError: boundary mode not supported.
We could have the standard setter convert the empty string to None ... but then the empty string is inaccessible as a value...

GUI mechanism for editing widget parameters

I saw in the meeting yesterday that you're working around the idea of using Annotated to provide extra information needed for widget generation, which is perfect for programmatic use but does require that a user knows what's being generated requires this extra info and how to supply it.

def gaussian(
    image : Image,
    sigma : Annotated[float, FloatRange(1, 100)],
) -> Image:

With provide_functions from napari/napari#2080 there was some discussion about requiring this annotation system being an extra burden on users. I think users on the napari side are less likely to know about the specific requirements of magicgui for annotating this extra info.

Maybe this burden could be alleviated a bit by providing a GUI mechanism for editing widget parameters from the generated GUI. In terms of UI, I'm imagining a small arrow like the one on the right here, for a menu.
image
From the menu you could open a floating window with little boxes for each of the editable parameters.

The presence of this little arrow would default to off and probably only show up in cases like when magicgui is being used inside another system like napari. Another possible benefit of this system is the ability to optimise things like slider ranges 'on-the-fly'.
Rather than going through the generate magicgui -> play with slider -> update function annotation loop as many times as is needed, someone developing an application could just modify on the fly and update the annotation once.

Would love to hear your thoughts, totally possible that what I'm proposing isn't really useful. If you think this is useful I'd love to develop it myself but may need some guidance!

Error running examples

When trying to run the sample code for image arithmetic the script is giving the following error

return choices(self, self._arg_types[name])
TypeError: get_layers() takes 1 positional argument but 2 were given

QTimeEdit widgets

Hey @tlambert03

first of all: Happy New Year! ๐Ÿฅณ

I just programmed a magicgui-annotated function that has a time parameter (hh:mm:ss). When starting up the GUI, I receive this warning:

UserWarning: Unable to find the appropriate widget for function "_clearcontrol_loader", arg "time", type "<class 'datetime.time'>". Falling back to LiteralEvalEdit widget.
  warnings.warn(

When entering a time then in the fallback QLineEdit field, this error shows up:

ERROR:root:Unhandled exception:
Traceback (most recent call last):
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\core.py", line 526, in __call__
    _kwargs = self.current_kwargs
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\core.py", line 500, in current_kwargs
    return {param: getattr(self, param) for param in self.param_names}
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\core.py", line 500, in <dictcomp>
    return {param: getattr(self, param) for param in self.param_names}
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\core.py", line 96, in __get__
    return api.getter_setter_onchange(widget).getter()
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\_qt\widgets\eval_lineedit.py", line 12, in text
    return literal_eval(super().text())
  File "C:\Users\rober\miniconda3\envs\clustering\lib\ast.py", line 59, in literal_eval
    node_or_string = parse(node_or_string, mode='eval')
  File "C:\Users\rober\miniconda3\envs\clustering\lib\ast.py", line 47, in parse
    return compile(source, filename, mode, flags,
  File "<unknown>", line 1
    1:00:00
     ^
SyntaxError: invalid syntax

My use case is quite specific: I'd like to select a defined timepoints from a larger dataset for loading and we image over multiple days with variable temporal resolution.

Nevertheless, I think supporting QTimeEdit as widget type would be cool.

Let me know if I can help making this happen.

No hurry btw. I do have a functional workaround for now.

Cheers,
Robert

Enable drag/drop on table columns

After #61, it would be nice to enable drag-and-drop to reorder table columns. This is possible (in qt) with self._qwidget.horizontalHeader().setSectionsMovable(True) but it's a bit tricky because the internal table model in the widget doesn't actually reorder the column headers, it simply updates the horizontalHeader().visualIndex() for each column index. Doable, but better off in a separate PR

Widget label editable

I create this feature request like @tlambert03 have suggested here https://forum.image.sc/t/magicgui-and-docked-widget-questions/44983

It would be fine, add a "display_name" parameter on the @magicgui decorator to personalize the string showed left to the widget.

For instance, this fragment:

import napari
from magicgui import magicgui

@magicgui()
def test_gui(var_name: int = 0):
    pass

with napari.gui_qt():
    widget = test_gui.Gui(show=True)

it has this output, where "var_name" label is not editable

Screenshot_2020-11-11_19-10-21

@jni suggested using a "display_name" option on the @magicgui decorator like this:

@magicgui(var_name={"display_name": "some editable string"})
def test_gui(var_name: int = 0):
    pass

Cheers!

Provide more direct "autowidget generation" without requiring function body.

in using magicgui to create a single widget in napari/napari#1069, even though they didn't a function with logic in the body, there was no choice but to do:

@magicgui(label={'choices': labels})
def label_selection(label):
    return label

while the original conception of magicgui was to provide a GUI for simple functions, this makes me think that we should also offer one-off widgets that still abstract away the widget backend (one shouldn't need to know any Qt methods if they don't want to), while having a bit more direct access to Type->Widget lookups. something like:

label_selection = magicgui.make_widget(choices=labels)

call_button not responding

Describe the bug
Playing with the snells_law.py to understand how events are triggered.
The calculate button can only be clicked once. Any operation later are ignored and without error message

To Reproduce
Run the snell_law.py -> click the calculate -> get one textfield added -> change aoi -> click calculate -> textfield not updated correspondingly

Expected behavior
the textfield should update the values corrspondingly.

To note, change call_button to auto_call=True produces the expected behaviour (but in an automated way that is not what was expected...)

Screenshots
If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

  • OS: [Mac]
  • backend: [Qt 5.15.0]
  • magicgui version [0.1.6]

Recognize napari layer types as strings

I'd like to be able to pass functions with napari layer types as strings, so

# Define our function.
def image_arithmetic(layerA: 'napari.layers.Image', operation: Operation, layerB: 'napari.layers.Image') -> 'napari.layers.Image':
    """Adds, subtracts, multiplies, or divides two image layers of similar shape."""
    return operation.value(layerA.data, layerB.data)

Right now I get the following warnings

/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/core.py:338: UserWarning: Unable to find the appropriate widget for function "image_arithmetic", arg "layerA", type "napari.layers.Image". Falling back to LiteralEvalEdit widget.
  f"{msg} Falling back to {api.FALLBACK_WIDGET.__name__} widget."
/Users/nsofroniew/opt/anaconda3/lib/python3.7/site-packages/magicgui/core.py:338: UserWarning: Unable to find the appropriate widget for function "image_arithmetic", arg "layerB", type "napari.layers.Image". Falling back to LiteralEvalEdit widget.
  f"{msg} Falling back to {api.FALLBACK_WIDGET.__name__} widget."

but the following works as expected

# Define our function.
def image_arithmetic(layerA: napari.layers.Image, operation: Operation, layerB: napari.layers.Image) -> napari.layers.Image:
    """Adds, subtracts, multiplies, or divides two image layers of similar shape."""
    return operation.value(layerA.data, layerB.data)

Adding stretch to a widget's native layout causes a crash

Describe the bug

When I try to add Qt (native) stretch to a magicgui widget using func.native_layout.addStretch(), I get a crash with the following traceback:

 $ python -m affinder.affinder
WARNING: Traceback (most recent call last):
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/widgets/_bases.py", line 245, in _emit_parent
    self.parent_changed(value=self.parent)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/events.py", line 603, in __call__
    self._invoke_callback(cb, event)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/events.py", line 621, in _invoke_callback
    _handle_exception(
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/events.py", line 619, in _invoke_callback
    cb(event)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/widgets/_bases.py", line 1102, in reset_choices
    for widget in self:
  File "/Users/jni/conda/envs/all/lib/python3.8/_collections_abc.py", line 874, in __iter__
    v = self[i]
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/widgets/_bases.py", line 996, in __getitem__
    item = self._widget._mgui_get_index(key)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/magicgui/backends/_qtpy/widgets.py", line 276, in _mgui_get_index
    return item.widget()._magic_widget
AttributeError: 'NoneType' object has no attribute '_magic_widget'
Abort trap: 6
/Users/jni/conda/envs/all/lib/python3.8/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown
  warnings.warn('resource_tracker: There appear to be %d '

To Reproduce

pip install napari[all] git+https://github.com/jni/affinder@layout-stretch
python -m affinder.affinder

The commit causing issues is here:

jni/affinder@b45a97c

Expected behavior
stretch would be added to the bottom of the widget to make the layout prettier. ๐Ÿ˜Š

Screenshots
If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

  • OS: [e.g. Windows, Mac] Mac
  • backend: [e.g. Qt 5.13.0] PyQt5: 5.14.2
  • magicgui version [e.g. 0.1.0] 0.2.5

magicgui.widgets.CategoricalWidget not found in magicgui 0.2.1

Describe the bug
Hi Talley @tlambert03,

tl;dr: Is there example code available for using choices in magicgui 0.2.1? I get error messages while attempting to use CategoricalWidget.

I'm just updating the magicgui dependency in my experimental napari plugin and receive this error message in the context of having a choice:

As of magicgui 0.2.0, a `choices` callable may accept only a single positional
argument (an instance of `magicgui.widgets.CategoricalWidget`), and must return
an iterable (the choices to show). Function 'get_layers' accepts 2 arguments.
In the future, this will raise an exception.

However, the mentioned class magicgui.widgets.CategoricalWidget does not exist. If I import it, I receive this error message:

(clustering) C:\structure\code\napari_pyclesperanto_assistant\docs\demo>python start_assistant.py
C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\_qt\widgets.py:13: FutureWarning:

The 'magicgui._qt.widgets' module has been removed.
For 'QDataComboBox', please use 'magicgui.widgets.ComboBox' directly.
(Or just use the string `widget_type='ComboBox'`)
In the future this will raise an exception.

  warnings.warn(
I'm asked:  C:\structure\code\napari_pyclesperanto_assistant\napari_pyclesperanto_assistant\data\Lund_000500_resampled-cropped.tif
ERROR:root:Unhandled exception:
Traceback (most recent call last):
  File "C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\napari\_qt\event_loop.py", line 79, in gui_qt
    yield app
  File "start_assistant.py", line 12, in <module>
    assistant_gui = napari_pyclesperanto_assistant.napari_plugin(viewer)
  File "c:\structure\code\napari_pyclesperanto_assistant\napari_pyclesperanto_assistant\_gui\_napari_plugin.py", line 6, in napari_plugin
    gui = AssistantGUI(viewer)
  File "c:\structure\code\napari_pyclesperanto_assistant\napari_pyclesperanto_assistant\_gui\_AssistantGui.py", line 26, in __init__
    self._init_gui()
  File "c:\structure\code\napari_pyclesperanto_assistant\napari_pyclesperanto_assistant\_gui\_AssistantGui.py", line 37, in _init_gui
    from .._operations._operations import denoise, background_removal, filter, binarize, combine, label, label_processing, map, mesh, measure
  File "c:\structure\code\napari_pyclesperanto_assistant\napari_pyclesperanto_assistant\_operations\_operations.py", line 6, in <module>
    from magicgui.widgets import CategoricalWidget
ImportError: cannot import name 'CategoricalWidget' from 'magicgui.widgets' (C:\Users\rober\miniconda3\envs\clustering\lib\site-packages\magicgui\widgets\__init__.py)

To Reproduce
Steps to reproduce the behavior:

pip install magicgui==0.2.1

git clone https://github.com/clEsperanto/napari_pyclesperanto_assistant

cd napari_pyclesperanto_assistant/docs/demo

python start_assistant.py

for the second error message, add from magicgui.widgets import CategoricalWidget to this file

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots

Environment (please complete the following information):

  • Windows
  • magicgui version 0.2.1
(clustering) C:\structure\code\napari_pyclesperanto_assistant\docs\demo>conda list
# packages in environment at C:\Users\rober\miniconda3\envs\clustering:
#
# Name                    Version                   Build  Channel
alabaster                 0.7.12                   pypi_0    pypi
appdirs                   1.4.4                    pypi_0    pypi
babel                     2.9.0                    pypi_0    pypi
backcall                  0.2.0                    pypi_0    pypi
blas                      1.0                         mkl
ca-certificates           2020.12.8            haa95532_0
cachey                    0.2.1                    pypi_0    pypi
certifi                   2020.12.5        py38haa95532_0
chardet                   4.0.0                    pypi_0    pypi
colorama                  0.4.4                    pypi_0    pypi
cudatoolkit               11.0.221             h74a9793_0
cycler                    0.10.0                   pypi_0    pypi
dask                      2020.12.0                pypi_0    pypi
decorator                 4.4.2                    pypi_0    pypi
docutils                  0.16                     pypi_0    pypi
freetype-py               2.2.0                    pypi_0    pypi
heapdict                  1.0.1                    pypi_0    pypi
idna                      2.10                     pypi_0    pypi
imageio                   2.9.0                    pypi_0    pypi
imagesize                 1.2.0                    pypi_0    pypi
intel-openmp              2020.2                      254
ipykernel                 5.4.2                    pypi_0    pypi
ipython                   7.19.0                   pypi_0    pypi
ipython-genutils          0.2.0                    pypi_0    pypi
jedi                      0.18.0                   pypi_0    pypi
jinja2                    2.11.2                   pypi_0    pypi
joblib                    1.0.0                    pypi_0    pypi
jupyter-client            6.1.7                    pypi_0    pypi
jupyter-core              4.7.0                    pypi_0    pypi
kiwisolver                1.3.1                    pypi_0    pypi
libuv                     1.40.0               he774522_0
magicgui                  0.2.1                    pypi_0    pypi
markupsafe                1.1.1                    pypi_0    pypi
matplotlib                3.3.3                    pypi_0    pypi
mkl                       2020.2                      256
mkl-service               2.3.0            py38h196d8e1_0
mkl_fft                   1.2.0            py38h45dec08_0
mkl_random                1.1.1            py38h47e9c7a_0
napari                    0.4.2                    pypi_0    pypi
napari-plugin-engine      0.1.8                    pypi_0    pypi
napari-pyclesperanto-assistant 0.2.1                     dev_0    <develop>
napari-svg                0.1.4                    pypi_0    pypi
networkx                  2.5                      pypi_0    pypi
ninja                     1.10.2           py38h6d14046_0
numpy                     1.19.3                   pypi_0    pypi
numpydoc                  1.1.0                    pypi_0    pypi
openssl                   1.1.1i               h2bbff1b_0
packaging                 20.8                     pypi_0    pypi
parso                     0.8.1                    pypi_0    pypi
pickleshare               0.7.5                    pypi_0    pypi
pillow                    8.0.1                    pypi_0    pypi
pip                       20.3.1           py38haa95532_0
prompt-toolkit            3.0.8                    pypi_0    pypi
psutil                    5.8.0                    pypi_0    pypi
pyclesperanto-prototype   0.6.0                     dev_0    <develop>
pygments                  2.7.3                    pypi_0    pypi
pyopencl                  2020.3.1+cl12            pypi_0    pypi
pyopengl                  3.1.5                    pypi_0    pypi
pyparsing                 2.4.7                    pypi_0    pypi
pyperclip                 1.8.1                    pypi_0    pypi
pyqt5                     5.15.2                   pypi_0    pypi
pyqt5-sip                 12.8.1                   pypi_0    pypi
python                    3.8.5                h5fd99cc_1
python-dateutil           2.8.1                    pypi_0    pypi
pytools                   2020.4.4                 pypi_0    pypi
pytorch                   1.7.1           py3.8_cuda110_cudnn8_0    pytorch
pytz                      2020.5                   pypi_0    pypi
pywavelets                1.1.1                    pypi_0    pypi
pywin32                   300                      pypi_0    pypi
pyyaml                    5.3.1                    pypi_0    pypi
pyzmq                     20.0.0                   pypi_0    pypi
qtconsole                 5.0.1                    pypi_0    pypi
qtpy                      1.9.0                    pypi_0    pypi
requests                  2.25.1                   pypi_0    pypi
scikit-image              0.18.0                   pypi_0    pypi
scikit-learn              0.24.0                   pypi_0    pypi
scipy                     1.5.4                    pypi_0    pypi
setuptools                51.0.0           py38haa95532_2
six                       1.15.0           py38haa95532_0
snowballstemmer           2.0.0                    pypi_0    pypi
sphinx                    3.4.1                    pypi_0    pypi
sphinxcontrib-applehelp   1.0.2                    pypi_0    pypi
sphinxcontrib-devhelp     1.0.2                    pypi_0    pypi
sphinxcontrib-htmlhelp    1.0.3                    pypi_0    pypi
sphinxcontrib-jsmath      1.0.1                    pypi_0    pypi
sphinxcontrib-qthelp      1.0.3                    pypi_0    pypi
sphinxcontrib-serializinghtml 1.1.4                    pypi_0    pypi
sqlite                    3.33.0               h2a8f88b_0
threadpoolctl             2.1.0                    pypi_0    pypi
tifffile                  2020.12.8                pypi_0    pypi
toolz                     0.11.1                   pypi_0    pypi
tornado                   6.1                      pypi_0    pypi
traitlets                 5.0.5                    pypi_0    pypi
typing_extensions         3.7.4.3                    py_0
urllib3                   1.26.2                   pypi_0    pypi
vc                        14.2                 h21ff451_1
vispy                     0.6.6                    pypi_0    pypi
vs2015_runtime            14.27.29016          h5e58377_2
wcwidth                   0.2.5                    pypi_0    pypi
wheel                     0.36.1             pyhd3eb1b0_0
wincertstore              0.2                      py38_0
wrapt                     1.12.1                   pypi_0    pypi
zlib                      1.2.11               h62dcd97_4

Supporting `Union`

quick thought: plenty of functions accept multiple types for a given argument using typing.Union. It would be nice if we could somehow support that. Perhaps if a Union is specified, the widget itself could be "swappable" (i.e. right click on it and you can select from the various types in the Union)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.