How it Works

Typhon has three major building blocks that combine into the final display seen by the operator:

  • TyphonSuite : The overall view for a Typhon window. It allows the operator to view all of the loaded components and tools.

  • TyphonDeviceDisplay : This is the widget created for a standard ophyd.Device. Signals are organized based on their Kind and description.

  • typhon.tools : These are widgets that interface with external applications. While you may have other GUIs for these systems, typhon.tools are built especially to handle the handshaking between all the information stored on your device and the tool you are interfacing with. This saves your operator clicks and ensures consistency in use.

All three of the widgets listed above share a similar API for creation. Instantiating the object by itself handles loading the container widgets and placing them in the correct place, but these do not accept ophyd.Device arguments. The reason for this is to ensure that we can use all of the typhon screens as templates, and regardless or not of whether you have an ophyd.Device you can always populate the screens by hand. If you do in fact have an ophyd.Device every class has an add_device method and alternatively and be constructed using the from_device classmethod.

class typhon.utils.TyphonBase(*args, **kwargs)[source]

Base widget for all Typhon widgets that interface with devices

add_device(device)[source]

Add a new device to the widget

Parameters

device (ophyd.Device) –

classmethod from_device(device, parent=None, **kwargs)[source]

Create a new instance of the widget for a Device

Shortcut for:

tool = TyphonBase(parent=parent)
tool.add_device(device)
Parameters
  • device (ophyd.Device) –

  • parent (QWidget) –

paintEvent(self, QPaintEvent)[source]

Interpreting a Device

Typhon interprets the internal structure of the ophyd.Device to create the PyDM user interface, so the most intuitive way to configure the created display is to include components on the device itself. This also has the advantage of keeping your Python API and display in sync, making the transition from using screens to using an IPython shell seamless.

For the following applications we’ll use the motor simulation contained within ophyd itself. We also need to create a QApplication before we create any widgets:

In [1]: from qtpy.QtWidgets import QApplication

In [2]: app = QApplication([])

Using Happi

happi is not a requirement for using typhon. However, it is certainly recommended. For more information, visit the GitHub repository. The main purpose of the package is to store information on our Ophyd objects so that we can load them in a variety of contexts. If you do not use happi you will need to create your objects and displays in the same process.

Here is a quick example if you wanted to get a feel for what typhon looks like with happi`

import happi
from typhon.plugins import register_client

# Initialize a new JSON based client
client = happi.Client(path='db.json', initialize=True)
# Register this with typhon
register_client(client)
# Add a device to our new database
device = happi.Device(device_class='ophyd.sim.SynAxis',
                      prefix='Tst:Mtr', args=[], kwargs='{{name}}',
                      name='my_motor', beamline='TST')
client.add_device(device)

In practice, it is not necessary to call register_client() if you have configured the $HAPPI_CFG environment variable such that happi.Client.from_config yields the desired client.

We can now check that we can load the complete SynAxis object.

motor = client.load_device(name='my_motor')

Display Signals

The first thing we’ll talk about is showing a group of signals associated with our motor object in a basic form called a `TyphonSignalPanel. Simply inspecting the device reveals a few signals for us to display

In [3]: motor.component_names
Out[3]: ('readback', 'setpoint', 'velocity', 'acceleration', 'unused')

It is crucial that we understand the importance of these signals to the operator. In order to glean this information from the object the kind attributes are inspected. For more information see the ophyd documentation. A quick inspection of the various attributes allows us to see how our signals are organized.

# Most important signal(s)
In [4]: motor.hints
Out[4]: {'fields': ['motor']}

# Important signals, all hints will be found here as well
In [5]: motor.read()
Out[5]: 
OrderedDict([('motor', {'value': 0, 'timestamp': 1568827100.1241915}),
             ('motor_setpoint',
              {'value': 0, 'timestamp': 1568827100.1241906})])

# Configuration information
In [6]: motor.read_configuration()
Out[6]: 
OrderedDict([('motor_velocity', {'value': 1, 'timestamp': 1568827100.1245646}),
             ('motor_acceleration',
              {'value': 1, 'timestamp': 1568827100.1246157})])

The TyphonSignalPanel will render these, allowing us to select a subset of the signals to display based on their kind. Below both the QtDesigner using happi and the corresponding Python code is shown as well:

In [7]: from typhon import TyphonSignalPanel

In [8]: panel = TyphonSignalPanel.from_device(motor)
_images/kind_panel.gif

Now, at first glance it may not be obvious, but there is a lot of information here! We know which of these signals an operator will want to control and which ones are purely meant to be read back. We also have these signals grouped by their importance to operation, each with a terse human-readable description of what the Signal represents.

Filling Templates

Taking this concept further, instead of filling a single panel TyphonDeviceDisplay allows a template to be created with a multitude of widgets and panels. Typhon will find widgets that accept devices, but do not have any devices already. Typhon comes with some default templates, and you can cycle between them by changing the display_type

Once again, both the Python code and the QtDesigner use cases are shown:

In [9]: from typhon import TyphonDeviceDisplay

In [10]: display = TyphonDeviceDisplay.from_device(motor)
_images/device_display.gif

The TyphonSuite

A complete application can be made by loading the TyphonSuite. Below is the complete code from start to finish required to create the suite. Look at the TyphonSuited.default_tools to control which typhon.tools are loaded.

from ophyd.sim import motor
from qtpy.QtWidgets import QApplication
import typhon

# Create our application
app = QApplication([])
typhon.use_stylesheet()  # Optional
suite = typhon.TyphonSuite.from_device(motor)

# Launch
suite.show()
app.exec_()

Using the StyleSheet

While it is no means a requirement, Typhon ships with two stylesheets to improve the look of the widgets. By default this isn’t activated, but can be configured with typhon.use_stylesheet(). The operator can elect whether to use the “light” or “dark” stylesheets by using the optional dark keyword. This method also handles setting the “Fusion” QStyle which helps make the interface have an operating system independent look and feel.