pyapp-kit / magicgui Goto Github PK
View Code? Open in Web Editor NEWbuild GUIs from type annotations
Home Page: https://pyapp-kit.github.io/magicgui/
License: MIT License
build GUIs from type annotations
Home Page: https://pyapp-kit.github.io/magicgui/
License: MIT License
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.
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
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:
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]
anddef example(func=f)
:? (with a bit of extra magic to grabf.__name__
if f is a callable)?
This needs to be followed up on.
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.
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)
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.
register_type
(or a new function) should also be allowed to register callbacks when that type is defined as a return annotation. See napari/napari#981 (comment)
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.
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!
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
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
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.
Describe the bug
Re-running a long-running function (e.g. by clicking the provided Run
button) will trigger a RuntimeError: EventEmitter loop detected!
in events.py
. Haven't yet investigated what can be done about that.
see https://github.com/napari/magicgui/pull/105#discussion_r563204464
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.
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!
As mentioned by @HagaiHargil in https://github.com/napari/magicgui/issues/107#issuecomment-762935341, there should be an API for editing the widget window title (or at the very least, it should be set as the Container widget name
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)
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.
@jni has proposed that we make layout='vertical'
be default for magicgui widgets. Curious if anyone has any objections to that? (you can, of course, still use layout='horizontal'
.
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:
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):
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
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): ...
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):
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
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.
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
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):
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...
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):
One very common component of GUIs I build for users is a file or directory chooser. The use case is usually:
There is QFileDialog, which I think would fulfill this use case.
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)
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 :)
Describe the bug
napari/napari#2044
will follow up soon
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!
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):
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)
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
@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!
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 :-)
v0.2.0 includes a complete rewrite of magicgui. The primary goals were as follows:
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
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
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?
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')
Many magic typing failures happen for simple arguments (tuple, range, list, (small) dict...) that could actually be easy for users to type in. Rather than fail, magicgui could present a text box that gets eval
'd.
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.
In napari/napari#1856 (comment), @sofroniewn shows an issue in which napari's callback function fails when a ForwardRef is used as a type annotation. While #52 takes care of this internally... currently, it's up to all dependent libraries to do it for themselves. It would probably be easy enough to make sure that widget.annotation
is always a resolved ForwardRef?
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
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.
v0.2.6 is a significant feature release, introducing a number of new widgets and APIs:
Table
Widget allowing easy creation and modification of Table UIs using a variety of pure python types as input (#61)@magicgui
are now automatically taken from docstrings (numpy, google, and sphinx-rst format accepted)(#100)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.MainWindow/MainFunctionGui
subclasses allow creating top level "application" windows, with a basic API for adding items to the application menubar (#110).@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()
Containers
and magicgui widgets.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:
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).
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...
contrib
module. For instance, a mapping of namespace to a register_type
-like function...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.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?
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):
(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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.