Developer guide

This guide is intended for anyone that wants to write code that interacts with the UI. This can be relevant for tasks from gaining access to the loaded signals in the console, to writing plugins that extended the functionality of the UI.

One of the main components of the API is the MainWindow class. This is the class of the main UI window, and can be considered the hub to reach all the loaded content, including signals and models, or Available actions, Widgets and Tools.

Internal Console

The internal console is a qtconsole widget running IPython, and can run any code that IPython can. To facilitate the typical use of the console, numpy is pre-loaded as np, and hyperspy.api as hs. The current MainWindow instance is also exported as ui, and the list of currently loaded hyperspy Signal instances is exported as siglist.

If we want to inspect the original metadata of the currently selected signal, we can then run:

In [1]: s = ui.get_selected_signal()

In [2]: s.original_metadata
├── FORMAT = EMSA/MAS Spectral Data File
├── NCOLUMNS = 1.0
├── NPOINTS = 2048.0
├── OFFSET = 360.0
├── OWNER =
├── TITLE =
├── VERSION = 1.0
├── XPERCHAN = 0.2
├── XUNITS = eV
└── YUNITS = Counts

Code editor widget

Code editor widgets allow you to edit Python code directly within the application. This can be useful for Recording code, or for creating and modifying Plugins on the fly. An editor widget is opened by the New editor action, when a python script is opened, or when recording is started via the Recorder widget.

Recording code

Most if not all actions that the UI perform can be recorded. To start recording, press the ‘Start’ button in the Recorder widget. This will open up a new blank Code editor widget, that will be populated with code as actions are performed. The code can be edited while recording; the new code is simply appended at the end. Closing the widget will automatically stop the recording, but stopping the recording will leave the editor open.

One of the most useful ways to use the recorder, is to perform an action on one signal, and then select another signal and perform those same actions on it as well. This can be very useful if you e.g. need to crop both a core-loss and a low-loss spectrum, as they will be cropped to exactly the same region.


While most of the code that is recorded tries to infer as much about its context as possible, it is not magical, so some manual adaptation of the recorded code might be needed. Typically, signal names need to be adjusted so that the right signals are used for different steps in a multi-step process.


Once a sequence of steps have been recorded and adjusted, it can be saved as a python script which can then be loaded later for future use. However, if this is something that is used often, and does not change from session to session, this might become rather cumbersome. The easiest step then is to turn the code into a UI action, either as a menu item, or toolbar button. This is done by creating a new plugin. To ease the creation of a plugin, the Code editor widget has a button ‘Make Plugin’.

For example, here we’ve created a rather simple exmple for code to substract one signal from another (a little naive, as it doesn’t take data type into account):

Raw code:

a, b = ui.select_x_signals(2, ['A', 'B'])
s_sub = a.signal - b.signal

In editor:


Clicking the ‘Make plugin’ button, we’re presented with a dialog, asking what the plugin should be called, whether we want to add it as a menu entry and/or toolbar button, which catgory it should go in (read: which menu and toolbar it goes into), and if it should have an icon (recommended for toolbar buttons):


After deciding what we want, we click ‘OK’, and a new editor appears with the following code:

Raw code:

from hyperspyui.plugins.plugin import Plugin
import numpy as np
import hyperspy.api as hs

class SimpleSubstraction(Plugin):
    name = "Simple substraction"

    def create_actions(self):
        self.add_action( + '.default',, self.default,

    def create_menu(self):
        self.add_menuitem('Math', self.ui.actions[ + '.default'])

    def create_toolbars(self):
            'Math', self.ui.actions[
       + '.default'])

    def default(self):
        ui = self.ui
        siglist = ui.hspy_signals
        a, b = ui.select_x_signals(2, ['A', 'B'])
        s_sub = a.signal - b.signal

In editor:


As can be seen, it has created a bare-bones plugin class, with a single action called default. It also imports hyperspy and numpy, and sets the variables ui and siglist such that the code works directly as in the console. Our action is crated in create_actions(), added as a menu item in create_menu(), and as a toolbar button in create_toolbars().

So now that we have the code for a plugin, all that remains is to register it. This we do by clicking the button ‘Register Plugin’ which has now replaced the buttons ‘Run’ and ‘Make Plugin’ (since they don’t make sense for a plugin). Now, as we have yet to save our code, we will at this point be asked to save the plugin script, as only saved files can be loaded as plugins. If we only want this plugin for the current session, we can save it anywhere. The next time we start the application, it will then not be loaded unless we manually register it again. However, if we want it to be loaded automatically every time we start the application, we need to save it in the ‘plugins’ directory (default location when the ‘Register Plugin’ button is pressed).


If you are on the development version of HyperSpyUI, and don’t want to commit your plugin, you can also save it to the sub folder ‘user_plugins’, which is added to the .gitignore file.

Once the plugin has been registered, it will automatically be loaded, and so we should directly be able to find our new menu entry like this:


Plugin manager widget

The plugin manager widget shows a list of all the discovered plugins (and any that have been added manually), their locations, and allows to set whether they should be loaded or not. There’s also an ‘Edit’ button which will open up the source code of the plugin in a new Code editor widget.

Unchecking a plugin will attempt to unload it, but the success of this depends on the plugin correctly implementing its unload() routine, which might not always be the case.


The unloading will only be partial if references to its content remain elsewhere in the program, so for this reason many core plugins will fail to unload fully.