Napari: Auto-updating dock widget min+max properties (made with magicgui)

Hi there,

This is my first question on image.sc, please let me know if I don’t provide enough information.

First off, I’ve been having a great time playing with Napari for visualizing several in house binary data formats - great work. Magicgui has also been instrumental. The speed at which a custom GUI/Viewer can be made is really powerful for collaborations.

One thing I haven’t been able to find out rapidly, is how to update a dock widget beyond the refresh_choices() functionality that magicgui provides. ie. for the updating of minimum and maximum properties for a slider widget.

More explicity:
I am memory mapping a binary dataset using a slider to scroll through frames. When I initialize the widget, I put the maximum slider value to the maximum number of frames. In another dock, I allow the user to change the file by changing the path. When this happens, I would like the slider widget range to update accordingly. Can you elaborate on where the widget information is stored in the viewer instance?

mini-code snippet
@magicgui(auto_call=True,
frame={‘widget_type’: QSlider,
‘minimum’: 1, ‘maximum’: data.numFrames,
‘orientation’: Qt.Horizontal},
frames2Avg = {‘widget_type’: QLineEdit,
‘minimum’: 1, ‘maximum’: data.numFrames,
‘orientation’: Qt.Horizontal})
def scrollFrames(layer: Image, frame: int = 1, frames2Avg: int = 1):

1 Like

hi @ddepaoli, welcome to image.sc! Glad you’ve been finding napari and magicgui useful.

Sorry that this information hasn’t made it into the magicgui docs yet… there’s a lot left to do :slight_smile:… here’s a few things that should hopefully help:

to answer your main question, the actual widget objects are stored on the magic_widget instance (the thing returned by calling scrollFrames.Gui()) and they are named <parameter_name>_widget. For instance:

from magicgui import magicgui
from napari.layers import Image
from qtpy.QtWidgets import QSlider


@magicgui(
    auto_call=True, frame={"widget_type": QSlider, "minimum": 1, "maximum": 10},
)
def scrollFrames(frame: int = 1):
    print(frame)

wdg = scrollFrames.Gui()
print(wdg.frame_widget)  # <PyQt5.QtWidgets.QSlider object at 0x129821dc0>

The eventual goal of magicgui is to abstract many of these standard operations (like setting the range of a slider) so that you needn’t ever directly use the API of the gui backend (i.e. Qt). But until we expand the magicgui API to support more of these things, you’ll have to directly use the Qt functions to update existing widgets. So, in this case, to change the slider maximum, use QSlider.setMaximum():

wdg.frame_widget.setMaximum(100) 

To have it update when the user changes the path, you can connect a callback to the _changed signal of the filepath parameter (every parameter in the signature will have a <parameter_name>_changed signal to which you can connect callbacks. here’s an example (taking advantage of magicgui’s support for pathlib.Path objects to open a native dialog to select a file):

from pathlib import Path

from magicgui import magicgui
from qtpy.QtWidgets import QSlider
from skimage.io import imread

@magicgui(auto_call=True, frame={"widget_type": QSlider, "minimum": 1, "maximum": 10})
def scrollFrames(fpath: Path, frame: int = 1):
    print(frame)

wdg = scrollFrames.Gui()

def update_slider(fpath):
    # probably don't want to read then throw this array away...
    # but just an example
    array = imread(fpath)
    wdg.frame_widget.setMaximum(len(array))

wdg.fpath_changed.connect(update_slider)

side question: why are you using QLineEdit on frames2Avg? … Just want to point out that needing widget_type=QSlider is a bit of a unique case for overriding the default widget… and it will ultimately be converted to some sort of internal API like widget_type='slider' … (no Qt import necessary). In almost all other cases, the intention of magicgui is for you to not have to specify the widget explicitly unless you want to override the default. If you really want a text widget for that integer frames2Avg parameter (instead of the default SpinBox widget), consider declaring frames2Avg as a string… then doing your conversion internally:

@magicgui
def scrollFrames(frames2Avg: str):
    to_avg = int(frames2Avg)  # will raise if not a valid integer.
2 Likes

Hi @talley ,

Thanks so much for the quick advice. Your solution worked perfectly.

As for the side question, you’re right, I did not need a QLineEdit in this case. I have a few sections that require strings - but not here!

2 Likes