anywidget GUI elements for the HyperSpy framework.
This package provides GUI widgets using anywidget for the HyperSpy scientific data analysis library. It is explicitly supported in Jupyter Notebook / JupyterLab and in Marimo without any frontend build step or bundler.
pip install hyperspy_gui_anywidgetpip install "hyperspy_gui_anywidget[tests]"git clone https://github.com/hyperspy/hyperspy_gui_anywidget.git
cd hyperspy_gui_anywidget
pip install -e ".[dev,tests,doc]"For modern Jupyter environments, installing hyperspy_gui_anywidget is enough. You do not
need to manually enable an anywidget extension in current JupyterLab or Notebook releases,
because anywidget/ipywidgets handle the widget integration.
In normal use, the default display=True behavior is what you want: calling a GUI method
displays the widget inline immediately.
HyperSpy can then use the anywidget toolkit directly:
import hyperspy.api as hs
# Open preferences GUI
hs.preferences.gui(toolkit="anywidget")Widgets are displayed inline using IPython.display.display():
roi = hs.roi.SpanROI(left=0, right=10)
roi.gui(toolkit="anywidget")Marimo follows the same default behavior: with display=True (the default), the widget is
displayed inline for you.
If you need the raw widget object for manual embedding with mo.ui.anywidget(), pass
display=False and use the returned dictionary instead:
import marimo as mo
import hyperspy.api as hs
roi = hs.roi.SpanROI(left=0, right=10)
res = roi.gui(toolkit="anywidget", display=False)
widget = res["anywidget"]["widget"]
mo.ui.anywidget(widget)The add_display_arg decorator detects Marimo at runtime (via sys.modules) and, when
needed, flattens VBox/HBox containers before displaying them. Use display=False only
when you need the raw {"widget": ..., "wdict": ...} dictionary for embedding,
inspection, or tests.
This project is explicitly tested and documented for:
- Jupyter Notebook / JupyterLab
- Marimo
Because the leaf widgets are implemented with anywidget, they may also work in other
frontends that support Jupyter widgets / anywidget. However, this repository does not
explicitly test or guarantee behavior in environments such as VS Code notebooks, Google
Colab, JupyterLite, or Voila.
This package is built on a few key design decisions:
-
AnyWidget subclasses with inline
_esmJavaScript — Every widget is a Python class extendinganywidget.AnyWidgetwith an inline JavaScript string. There is no npm, webpack, or build step. The JS renders standard HTML elements (<input>,<select>,<button>) and syncs state via traitlets. -
link_traitsfor bidirectional sync — Enthought Traits (used by HyperSpy) and traitlets (used by anywidget) are bridged withlink_traits.link(). Changes in the GUI propagate to the HyperSpy object and vice versa. -
ContainerWidget bridges Jupyter and Marimo — On the Jupyter path,
ContainerWidgetdeliberately uses realipywidgetscontainers such asVBox,HBox,Tab, andAccordionso the GUI integrates with the native widget stack. On the Marimo path, those layouts are flattened intoFlatContainerconfigs rendered by anywidget. -
Environment-aware display — The
@add_display_argdecorator handles the difference between Jupyter and Marimo. In Marimo it flattensVBox/HBoxroots before display when needed, anddisplay=Falsealways returns the raw{"widget": ..., "wdict": ...}dictionary. -
33 widget functions — All GUI functions return a dictionary with
"widget"(the root AnyWidget) and"wdict"(a mapping of named sub-widgets). This pattern is consistent across ROI widgets, axis widgets, model widgets, tool widgets, and preference widgets.
pytest is required to run the tests.
pip install "hyperspy_gui_anywidget[tests]"
pytest --pyargs hyperspy_gui_anywidgetSee the examples/ directory:
examples/comparison_notebook.ipynb— cleanipywidgetsvsanywidgetside-by-side comparison for representative HyperSpy GUIs (GitHub, Binder)examples/jupyter_anywidget_example.ipynb— practical Jupyter Notebook / JupyterLab walkthrough using the defaultdisplay=Trueflow (GitHub, Binder)examples/marimo_anywidget_example.py— equivalent Marimo app showing explicitmo.ui.anywidget(...)embedding viadisplay=False(GitHub)
The Jupyter notebook examples link to Binder so they open in a live notebook session instead of a static renderer. The Marimo example is currently linked as source only: this repository does not yet publish a verified dynamic Marimo deployment for that example.
Contributions through pull requests are welcome. Start with CONTRIBUTING.md for this repository's setup, testing, docs, and changelog workflow, and see the HyperSpy Developer Guide for the broader project conventions.
When adding a new widget:
- Create an AnyWidget subclass (or reuse one from
custom_widgets.py) - Use
link_traits.link()to sync with HyperSpy traits - Wrap everything in a
ContainerWidget - Apply
@add_display_argfor environment-aware display - Register the widget in
hyperspy_extension.yaml - Add tests in
hyperspy_gui_anywidget/tests/
ipywidgets is therefore an explicit runtime dependency by design: anywidget provides the custom leaf widgets, while ipywidgets still provides the Jupyter-native container layer that this package bridges to Marimo.
Before opening a pull request, run:
pre-commit run --all-files
pytest --pyargs hyperspy_gui_anywidget
sphinx-build -W -b html docs docs/_build/htmlUser-facing changes should also include a short news fragment in upcoming_changes/.
This project is licensed under the GNU General Public License v3 (GPLv3).