
Xi-CAM Documentation¶
The Xi-CAM
package is a plugin-based framework and graphical user
interface (GUI) application for synchrotron data management, acquisition,
visualization, and analysis. Support for a variety of techniques is provided
through its plugin architecture.
Getting Started¶
Before starting to develop plugins, you will need to install Xi-CAM and some of its dependencies.
Follow the QuickStart guide to get started with installation and exploring an example Xi-CAM plugin (covers GUIPlugin, Workflow, and OperationPlugins).
For more detailed documentation, you may want to visit the following:
GUIPlugin documentation
OperationPlugin documentation
Workflow documentation
The Resources section has useful links to tutorials, examples, and documentation that can help with developing Xi-CAM plugins.
Developer Documentation¶
QuickStart Guide¶
This is a quick-start guide that will help you install Xi-CAM and explore an example plugin that you can experiment with.
This guide does not explore the implementation of the plugin in too much detail. For more in-depth documentation for developing plugins from scratch, see:
GUIPlugin Documentation¶
This documentation provides information on GUIPlugins and GUILayouts to help with designing your own plugins for Xi-CAM. API reference documentation is also included at the bottom.
If you are new to developing Xi-CAM plugins, it is recommended that you follow the quick-start documentation first.
For more general development resources, see the resources documentation.
What Is A GUIPlugin?¶
A GUIPlugin is an interactive user-facing plugin in Xi-CAM. It can be used to visualize and analyze data.
GUIPlugins make use of the qtpy
Python package for interactive GUI components.
See the resources documentation for more information.
Where is GUIPlugin?¶
xicam.plugins.guiplugin
What Does a GUIPlugin Look Like?¶
First, let’s look at what Xi-CAM looks like when you first load it:

Main window of Xi-CAM when running xicam. Note that there are three installed GUIPlugins here; if you haven’t installed any plugins, you won’t see any listed.
As you can see, the main window of Xi-CAM after it has finished loading
shows any installed GUIPlugins, a citation / references widget, a preview widget,
and a data browser widget.
The data browser widget can be used to load data into a GUIPlugin
.
The data preview widget can be used to “preview” data before loading it.
It is important to keep in mind a few concepts for GUIPlugins:
A
GUIPlugin
can have one or morestages
.Each
stage
is defined with aGUILayout
.A
GUILayout
is defined with a widget (or multiple widgets).
These concepts are explored in more detail later in this document.
How Do I Create a GUIPlugin?¶
To create a GUIPlugin
, you will need:
a derived class of
GUIPlugin
a
setup.py
file with axicam.plugins.GUIPlugin
entry point
Although you may structure your plugin’s code and support files as you like,
we recommend using a cookiecutter
template that we have created for Xi-CAM’s GUIPlugin
.
What is cookiecutter?¶
cookiecutter
is a templating tool that can be used to interactively create python project.
For more information, see the cookiecutter documentation.
In your active environment, you will need to pip install cookiecutter
.
Now, in the directory of your choice (the home directory, ~
, should work if you are unsure),
run the following:
cookiecutter https://github.com/Xi-CAM/Xi-cam.templates.GuiPlugin
This will download the template for creating a GUIPlugin
,
then present you with a series of prompts.
A prompt will look like prompt [default value]:
.
If you want to use the default value specified, hit the enter key.
Otherwise, respond to the prompt with the value you would like.
Here are the prompts with their descriptions:
prompt | default value | description |
---|---|---|
package_name | my_plugin | name of the package to create (will be xicam.package_name ) |
plugin_name | MyPlugin | name of the GUIPlugin class to create (name of your gui plugin in code) |
display_name | My Plugin | name of the GUIPlugin as it will appear in the Xi-CAM GUI |
plugin_version | 1.0.0 | current plugin version number |
author_name | author's name | |
author_email | author's email | |
author_url | url for the author/plugin (e.g. plugin GitHub repo url) | |
description | description of the plugin | |
dependencies | packages the plugin depends on | |
keywords | [Xi-cam, plugin] | keywords to tag the plugin with (when packaging) |
This will create a python package with some files and code to get started developing a GUIPlugin. You can always change the names of your plugin, package, etc. later by hand.
The GUIPlugin you created will be implemented in xicam.package_name/xicam/package_name/__init__.py
.
For purposes of this documentation, we will refer to these values by their defaults.
Installing Your GUIPlugin¶
When you create a new plugin package using cookiecutter,
one of the files it generates is setup.py
.
This contains meta-information about the package.
When you run pip install
of your package,
it uses this information to create a disribution.
setup.py
also defines entry points,
which Xi-CAM uses to find plugins.
For more information about entry points in Xi-CAM, see the following documentation.
Navigate to your created package directory and create an editable pip install:
cd xicam.my_plugin
pip install -e .
This tells pip install your file locally by looking at the setup.py
file,
and the -e
allows you to make changes to your code without having to reinstall.
If you change an entry point in setup.py, you must reinstall.
Selecting and Activating a GUIPlugin¶
We can activate any of the installed GUIPlugins by clicking on their name at the top. Let’s click on “My Plugin”:

MyPlugin’s interface.¶
Note that this plugin doesn’t do much yet; it simply displays the text “Stage 1…” You can also click “Stage 2” at the top, and you will see the text “Stage 2…” in the center.
How is MyPlugin Implemented?¶
The code for MyPlugin
is implemented in xicam.package_name/xicam/package_name/__init__.py
.
from qtpy.QtWidgets import QLabel
from xicam.plugins import GUIPlugin, GUILayout
class MyPlugin(GUIPlugin):
# Defines the name of the plugin (how it is displayed in Xi-CAM)
name = "My Plugin"
def __init__(self, *args, **kwargs):
# Insert code here
# Modify stages here
self.stages = {'Stage 1': GUILayout(QLabel("Stage 1...")),
"Stage 2": GUILayout(QLabel("Stage 2..."))}
# Initialize the parent class, GUIPlugin
super(MyPlugin, self).__init__(*args, **kwargs)
Cookiecutter set up this starter code for us.
We have a derived version of GUIPlugin
, which we call MyPlugin
.
It has the name “My Plugin”, which is how it will appear in the Xi-CAM GUI.
We then have an __init__
method to describe how to create a MyPlugin
.
Notice that there is a QLabel
, which is simply text, added into two GUILayouts
.
These layouts are then added to the interface via self.stages
.
Visually, a stage is a stand-alone interface for a GUIPlugin
.
A GUIPlugin
must have at least one stage but may have multiple stages.
With multiple stages, each stage has its own interface
and each stage can be selected in the top bar of Xi-CAM.
Stages for a GUIPlugin
are accessible with self.stages
.
self.stages
is a dictionary where each
key is the name of the stage
value is a
GUILayout
s
For example, we might define two stages as:
self.stages = {"A": GUILayout(QLabel("1")),
"B": GUILayout(QLabel("2"))}
This will look like:

The interface of a plugin named “My Plugin” with multiple stages, “A” and “B”. Note that “A” is currently selected, so we see the label “1” in the middle of the window.
A GUILayout
is a layout used to describe how widgets should be organized in a stage in a GUIPlugin.

The layout corresponds to a 3x3 grid in the Xi-CAM main window, with the names
center, left, right, lefttop, righttop, leftbottom, rightbottom.
These names correspond to the arguments you can pass when creating a GUILayout
.
You must provide at least one widget, which will be the center widget.
QLabel
is a Qt widget provided by the Qt backend Xi-CAM makes use of.
It acts a widget that holds simple text.
For more information on Qt, see Qt for Python Documentation.
How Do I Load Data into My Plugin?¶
In order to load data into a GUIPlugin
, you must:
have access to or configure a databroker catalog
re-implement
appendCatalog
in yourGUIPlugin
derived classthis will need to have access to an internal widget to display the data
have a
GUIPlugin
selected in Xi-CAM
Configuring a Databroker Catalog¶
For purposes of this documentation, we will be using a sample msgpack catalog and a starter catalog.yml file you can download.
For general help about databroker, catalogs, and configuration, there is excellent documentation here: https://nsls-ii.github.io/databroker/v2/index.html. Additional documentation about catalogs can be found here: https://intake.readthedocs.io/en/latest/index.html
Download |
MD5 |
---|---|
3a18341f570b100afbaff1c889e9b4f8 |
|
c14814b4537810f14300f8c8d5949285 |
After downloading these files, we will want to do three things:
Decide where to put our data and move it there
Update our
catalog.yml
paths
to have a path directory the data is inMove our
catalog.yml
to a place it can be discovered
You can choose where you’d like to copy or move your data.
For purposes of this guide,
we will create a new directory in our home called catalogs
and move the downloaded msgpack file there.
Now that we’ve moved / copied our sample catalog msgpack file,
we need to update our catalog.yml
to tell it where it can find that data.
We will want to add a line under paths
in catalog.yml
that is the complete file path to the catalogs
directory we added above.
To know where we can put our catalog.yml
file,
we can run the following in a Python interpreter:
from databroker import catalog_search_path
print(catalog_search_path())
You can move the catalog.yml
file in any of the paths listed.
Note that typically there will be a more user-oriented path
and a more global system-level path for the catalogs to find.
You can copy the catalog.yml
file to either (or both) path
depending on how you want a machine set up.
Implementing appendCatalog¶
Let’s implement the appendCatalog
method in MyPlugin
so we can load the catalog.
We will also be adding a widget to view the loaded catalog.
Inside of the MyPlugin
class (located in xicam/my_plugin/__init__.py
),
add the appendCatalog
as follows:
from qtpy.QtWidgets import QLabel
from xicam.core.msg import logMessage
from xicam.plugins import GUILayout, GUIPlugin
from xicam.gui.widgets.imageviewmixins import CatalogView
class MyPlugin(GUIPlugin):
# Define the name of the plugin (how it is displayed in Xi-CAM)
name = "My Plugin"
def __init__(self, *args, **kwargs):
self._catalog_viewer = CatalogView()
self._stream = "primary"
self._field = "img"
catalog_viewer_layout = GUILayout(self._catalog_viewer)
# Modify stages here
# self.stages = {"Stage 1": GUILayout(QLabel("Stage 1..."))}
self.stages = {"View Catalog": catalog_viewer_layout}
super(MyPlugin, self).__init__(*args, **kwargs)
def appendCatalog(self, catalog):
self._catalog_viewer.setCatalog(catalog, self._stream, self._field)
logMessage(f"Opening catalog with stream {self._stream} and field {self._field}.")
API Reference¶
- class xicam.plugins.guiplugin.GUIPlugin¶
GUIPlugin class for interactive Xi-CAM plugins.
This class represents the fundamental interactive plugin for Xi-CAM.
GUIPlugins are left uninstanciated until all plugins are loaded so that all dependent widgets are loaded before the UI is setup. They DO become singletons.
- appendCatalog(catalog: BlueskyRun, **kwargs)¶
Re-implement to define how to add a catalog to your GUIPlugin.
- Parameters
catalog (BlueskyRun) – Catalog reference that you can use as you wish (corresponds to the opened catalog in DataResourceBrowser).
- property stages: OrderedDict¶
Returns the stages of the GUIPlugin.
A stage is defined by a GUILayout; each stage represents a distinct user-interface in a GUIPlugin.
- class xicam.plugins.guiplugin.GUILayout(center, left=PanelState.Defaulted, right=PanelState.Defaulted, bottom=PanelState.Defaulted, top=PanelState.Defaulted, lefttop=PanelState.Defaulted, righttop=PanelState.Defaulted, leftbottom=PanelState.Defaulted, rightbottom=PanelState.Defaulted)¶
Represents a layout of dockable widgets in a 3x3 grid.
The parameters can either be a PanelState value or a QWidget object. Note that only the center parameter is required; the other parameters default to PanelState.Defaulted. The default behavior of a PanelState.Defaulted widget is to be hidden.
- Parameters
center (Union[QWidget, PanelState]) – The center widget
left (Union[QWidget, PanelState], optional) – The left widget
right (Union[QWidget, PanelState], optional) – The right widget
bottom (Union[QWidget, PanelState], optional) – The bottom widget
top (Union[QWidget, PanelState], optional) – The top widget
lefttop (Union[QWidget, PanelState], optional) – The top-left widget
righttop (Union[QWidget, PanelState], optional) – The top-right widget
leftbottom (Union[QWidget, PanelState], optional) – The bottom-left widget
rightbottom (Union[QWidget, PanelState], optional) – The bottom-right widget
Notes
For an example of how this class can be used, see the xicam.gui XicamMainWindow class.
OperationPlugin Documentation¶
This documentation provides information on the foundational aspects of the OperationPlugin class, as well as a more detailed API reference.
If you are new to developing Xi-CAM plugins, it is recommended that you follow the quick-start documentation first.
For more general development resources, see the Resources page.
What Is an OperationPlugin?¶
An OperationPlugin can be thought of as a function with some extra annotations attached to it. When we want to define an OperationPlugin, we simply need to define a Python function, then add some additional syntax to the function to define things like inputs, outputs, descriptions of inputs/outputs, units, etc.
To achieve this, The OperationClass makes extensive use of Python decorators.
Where Is OperationPlugin?¶
xicam.plugins.operationplugin
What Does an OperationPlugin Look Like?¶
Let’s start off with a simple function that computes the square of its input:
def my_square(n):
return n**2
Now, let’s make this an OperationPlugin:
from xicam.plugins.operationplugin import operation, output_names
@operation
@output_names("square")
def my_square(n):
return n**2
That’s it!
Notice the two decorators here: @operation
and @output_names
.
The @operation
says that this function is now a Xi-CAM OperationPlugin.
Any input arguments for the function will be the input names for the operation.
In this case, our input is n
. (This can actually be overwritten by using a
different decorator, @input_names
, which is described later.)
The @output_names
allows us to name our outputs, in this case, square
.
This will be useful when connecting multiple operations together in a Workflow
.
Default Input Values¶
If you want to provide your operation with default input values, you can use argument defaults in your function:
from xicam.plugins.operationplugin import operation, output_names
@operation
@output_names("square")
def my_square(n = 0):
return n**2
This provides this operation’s n
input with a default value of 0
.
Required and Highly-Used Decorators¶
In order to make a function an operation, the following decorators must be used:
@operation
– allows creation of operations from the function@output_names
– defines the name of the output(s)
Additionally, although not required to for an operation, the following decorators are highly-recommended for use:
@display_name
– the name of the operation@describe_input
– attach a description to the specified input (can be used multiple times)@describe_output
– attach a description to the specified output (can be used multiple times)
Type Hinting (Optional)¶
With Python3 (3.5+), you can add type hinting to your code. In the context of Xi-CAM OperationPlugins, this can be used to make your operation code a little easier to read.
Let’s use the my_square
function we defined earlier in this operation:
from xicam.plugins.operationplugin import operation, output_names
@operation
@output_names("square")
def my_square(n: int) -> int:
return n**2
Note the n: int
and the -> int:
here. These suggest (but do not mandate) that
the input be an integer, and the output expected is an integer.
Again, these are not required, but they can help with readability and debugging your code.
For more information, see Python’s typing module.
Example¶
A simple division operation that returns both the quotient and remainder.
This illustrates the use of multiple input/output descriptions and multiple outputs.
from typing import Tuple
from xicam.plugins.operationplugin import describe_input, describe_output, display_name, operation, output_names
@operation
@output_names("quotient", "remainder")
@display_name("Division with Remainder")
@describe_input("dividend", "The number being divided.")
@describe_input("divisor", "The number to divide by.")
@describe_output("quotient", "The result of the division.")
@describe_output("remainder", "The remaining value.")
def my_divide(dividend: int, divisor: int = 1) -> Tuple[int, int]:
quotient = int(dividend // divisor)
remainder = dividend % divisor
return quotient, remainder
How Do I Use an OperationPlugin?¶
Now that we’ve defined an operation, how do we actually use it?
When we define an operation using the @operation
decorator around a function,
we are defining a new operation class.
We can then create an operation object by using the syntax func()
,
where func
is the name of the function in the operation.
Let’s take our my_square
operation (defined above) and create one:
from xicam.plugins.operationplugin import operation, output_names
@operation
@output_names("square")
def my_square(n):
return n**2
op = my_square()
Now that we have an operation object (instance), op
,
we can use it within a Workflow
.
Let’s create a Workflow
, add our operation to it, then execute it.
from xicam.core.execution import Workflow
from xicam.plugins.operationplugin import operation, output_names
@operation
@output_names("square")
def my_square(n):
return n**2
op = my_square()
workflow = Workflow()
workflow.add_operation(op)
result = workflow.execute(n=11).result()
print(result)
We create a my_square
operation, create a Workflow
, and add the operation to the Workflow
.
Then, we execute the Workflow
, sending in the input n=11
, wait for the result, and print it.
(For purposes of this document, we won’t cover Workflow
in depth.
More information about Workflow
can be found in the Workflow Documentation.)
API Documentation¶
- @xicam.plugins.operationplugin.operation(func: Callable, filled_values: Optional[dict] = None, fixable: Optional[dict] = None, fixed: Optional[dict] = None, input_names: Optional[Tuple[str, ...]] = None, output_names: Optional[Tuple[str, ...]] = None, limits: Optional[dict] = None, opts: Optional[dict] = None, output_shape: Optional[dict] = None, units: Optional[dict] = None, visible: Optional[dict] = None, name: Optional[str] = None, input_descriptions: Optional[dict] = None, output_descriptions: Optional[dict] = None, categories: Optional[Sequence[Union[tuple, str]]] = None) Type[OperationPlugin] ¶
Create a new operation.
When you define a new operation, you must use this decorator (`@operation`) and the `@output_names` decorator.
This function can be used as a decorator to define a new operation type. The operation can then be instantiated by using the () operator on the operation function’s name.
- Parameters
func (Callable) – Function that this operation will call.
filled_values (dict, optional) – Values to fill for the parameters.
fixable (dict, optional) – Indicates which parameters are able to be fixed.
fixed (dict, optional) – Indicates whether or not a parameter is fixed.
limits (dict, optional) – Defines limits for parameters.
opts (dict, optional) – Additional options (kwargs) for the parameter (useful with pyqtgraph’s Parameter/ParameterTree).
output_names (tuple, optional) – Names for the outputs, or returned values, of the operation.
output_shape (dict, optional) – Defines expected shapes for the outputs.
units (dict, optional) – Defines units for the parameters in the operation.
name (str, optional) – The display name to be shown to the user. Defaults to self.__name__
visible (dict, optional) – Indicates if a parameter is visible or not (see pyqtgraph.Parameter).
input_descriptions (dict, optional) – A mapping dict containing descriptions for each named input
output_descriptions (dict, optional) – A mapping dict containing descriptions for each named output
categories (List[Union[tuple, str], optional) – A sequence of categories to associate with this operation.
Example
Create a new operation type and create a new operation instance from it.
>>> from xicam.core.execution import Workflow >>> from xicam.plugins.operationplugin import operation, output_names >>> @operation >>> @output_names("my_output") >>> def my_func(x: float = 0.0) -> float: >>> return x * -1 >>> op = my_func() >>> workflow = Workflow() >>> result = workflow.execute(x=2.5).result() >>> print(result)
- @xicam.plugins.operationplugin.output_names¶
Decorator to define the names of the outputs for an operation.
Defines N-number of output names. These names will be used (in-order) to define any outputs that the operation has.
- Parameters
names (List[str]) – Names for the outputs in the operation.
Example
Define an operation that has the outputs x and y.
>>> @OperationPlugin >>> @output_names("x", "y") >>> def some_operation(a: int, b: int) -> Tuple[int, int]: >>> return a, b
- @xicam.plugins.operationplugin.categories(*categories: Union[tuple, str])¶
Decorator to assign categories to a operation.
These categories will be used to populate the structure of Xi-cam’s menus of OperationPlugins.
- Parameters
categories (Tuple[Union[tuple, str]]) – A sequence of categories. Each item is a tuple or str. If an item is a tuple, each item in the tuple is considered as an additional depth in the menu structure.
Example
Define an operation that is in the following categories:
Generic Functions Simple Math (square operation) Math Functions (square operation)
>>> @OperationPlugin >>> @categories(('Generic Functions', 'Simple Math'), 'Math Functions') >>> def square(x: int = 100) -> int: >>> return x**2
- @xicam.plugins.operationplugin.describe_input(arg_name: str, description: str)¶
Decorator to set the description for input arg_name.
This is useful for annotating the parameter with additional information for users.
These annotations are displayed in GUI representations of the operation.
- Parameters
arg_name (str) – Name of the input to add options for.
description (str) – A human-readable description of the input arg_name
Example
Define an operation and attach a description to its x input argument.
>>> @OperationPlugin >>> @describe_input('x', 'The value to square.') >>> def square(x: int = 100) -> int: >>> return x**2
- @xicam.plugins.operationplugin.describe_output(arg_name: str, description: str)¶
Decorator to set the description for output arg_name.
This is useful for annotating the parameter with additional information for users.
These annotations are displayed in GUI representations of the operation.
- Parameters
arg_name (str) – Name of the input to add options for.
description (str) – A human-readable description of the output arg_name.
Example
Define an operation and attach a description to its square output.
>>> @OperationPlugin >>> @output_names('square') >>> @describe_output('square', 'The squared value of x.') >>> def square(x: int = 100) -> int: >>> return x**2
- @xicam.plugins.operationplugin.display_name¶
Set the display name for the operation.
Display name is how this operation’s name will be displayed in Xi-cam.
- Parameters
name (str) – Name for the operation.
Example
Create an operation whose display name is “Cube Operation.”
>>> @OperationPlugin >>> @display_name('Cube Operation') >>> def cube(n: int = 2) -> int: >>> return n**3
- @xicam.plugins.operationplugin.fixed(arg_name, fix=True)¶
Decorator to set whether or not an input’s value is fixed.
Fixed means that the input’s value is fixed in the context of model fitting.
By default, sets the arg_name input to fixed, meaning its value cannot be changed.
- Parameters
arg_name (str) – Name of the input to change fix-state for.
fix (bool, optional) – Whether or not to fix arg_name (default is True).
example (TODO) –
- @xicam.plugins.operationplugin.input_names¶
Decorator to define input names for the operation.
The number of names provided must match the number of arguments for the operation/function.
If not provided, input names will be determined by examining the names of the arguments to the operation function.
Example
Create an addition operation and use the names “first” and “second” for the input names instead of the function arg names (x and y).
>>> @OperationPlugin >>> @input_names("first", "second") >>> def my_add(x: int, y: int) -> int: >>> return x + y
- @xicam.plugins.operationplugin.limits(arg_name, limit)¶
Decorator to define limits for an input.
Limits restrict the allowable values for the input (inclusive lower-bound, inclusive upper-bound).
- Parameters
arg_name (str) – Name of the input to define limits for.
limit (tuple[float]) – A 2-element sequence representing the lower and upper limit.
Example
Make an operation that has a limit on the x parameter from [0, 100].
>>> @OperationPlugin >>> @limits('x', [0, 100]) >>> def op(x): >>> ...
Make an operation that has a limit on the x parameter from [0.0, 1.0].
>>> @OperationPlugin >>> @limits('x', [0.0, 1.0]) >>> @opts('x', step=0.1) >>> def op(x): >>> ...
- @xicam.plugins.operationplugin.output_shape(arg_name: str, shape: Union[int, Collection[int]])¶
Decorator to set the shape of an output in an operation.”
- Parameters
arg_name (str) – Name of the output to define a shape for.
shape (int or tuple of ints) – N-element tuple representing the shape (dimensions) of the output.
Example
TODO
- @xicam.plugins.operationplugin.opts(arg_name: str, **options)¶
Decorator to set the opts (pyqtgraph Parameter opts) for arg_name.
This is useful for attaching any extra attributes onto an operation input argument.
These options correspond to the optional opts expected by pyqtgraph.Parameter. The options are typically used to add extra configuration to a Parameter.
- Parameters
arg_name (str) – Name of the input to add options for.
options (keyword args) – Keyword arguments that can be used for the rendering backend (pyqtgraph).
Example
Define an operation where the x input is readonly.
>>> @OperationPlugin >>> @opts('x', 'readonly'=True) >>> def op(x: str = 100) -> str: >>> return x
- @xicam.plugins.operationplugin.units(arg_name, unit)¶
Decorator to define units for an input.
Associates a unit of measurement with an input.
- Parameters
arg_name (str) – Name of the input to attach a unit to.
unit (str) – Unit of measurement descriptor to use (e.g. “mm”).
Example
Create an operation where its x parameter has its units defined in microns.
>>> @OperationPlugin >>> @units('x', 'μ'+'m') >>> def op(x: float = -1) -> float: >>> return x *= -1.0
- @xicam.plugins.operationplugin.visible(arg_name: str, is_visible=True)¶
Decorator to set whether an input is visible (shown in GUI) or not.
- Parameters
arg_name (str) – Name of the input to change visibility for.
is_visible (bool, optional) – Whether or not to make the input visible or not (default is True).
Example
Define an operation that makes the data_image invisible to the GUI (when using as_parameter() and pyqtgraph).
>>> @OperationPlugin >>> @visible('data_image') >>> def threshold(data_image: np.ndarray, threshold: float = 0.5) -> np.ndarray: >>> return ...
- class xicam.plugins.operationplugin.OperationPlugin(**filled_values)¶
A plugin that can be used to define an operation, which can be used in a Workflow.
Note: use the @operation decorator to create an operation type.
At its simplest level, an operation can be though of as a function. Any arguments (parameters) defined in the python function are treated as inputs for the operation. An operation’s outputs are defined by the returned values of the python function.
There are various methods available to help with modifying the operation’s parameters.
For more information on the attributes, see the documentation for their respective method (e.g. for more information on limits, see the limits method documentation).
For an easy way to expose parameters in the GUI, use OperationPlugin.as_parameter in conjunction with pyqtgraph.Parameter.create. Note that only input parameters that have type hinting annotations will be included in the return value of OperationPlugin.as_parameter.
- filled_values¶
Keys are the parameter names, values are the current values for the parameter.
- Type
dict
- fixable¶
Keys are the parameter names, values are bools indicating whether or not the parameter is able to be fixed.
- Type
dict
- fixed¶
Keys are the parameter names, values are bools indicating whether or not the parameter is fixed.
- Type
dict
- input_names¶
Names (in order) of the input argument(s) for the operation. Note that if not provided, input names default to the argument names in the function signature.
- Type
Tuple[str, …]
- limits¶
Keys are the parameter names, values are the limits (which are a collection of floats).
- Type
dict
- opts¶
Any additional options (kwargs) to be passed to the parameter (useful with pyqtgraph).
- Type
dict
- output_names¶
Names (in order) of the output(s) for the operation.
- Type
Tuple[str, …]
- output_shape¶
Keys are the output parameter names, values are the expected shape of the output (which are of type list).
- Type
dict
- units¶
Keys are the parameter names, values are units (of type str).
- Type
dict
- visible¶
Keys are the pareameter names, values are bools indicating whehter or not the parameter is visible (when exposed using pyqtgraph).
- Type
dict
- disabled¶
Whether or not the operation is disabled (default is False).
- Type
bool
- display_name¶
The name of the operation as it should be displayed to a user.
- Type
str
- hints¶
- Type
list
- input_descriptions¶
A mapping dict containing descriptions of each named input parameter
- Type
dict
- output_descriptions¶
A mapping dict containing descriptions of each named output parameter
- Type
dict
Notes
This class formally deprecates usage of the ProcessingPlugin API.
Example
Here, we define a function, then wrap it with the OperationPlugin decorator to make it an operation.
>>> @OperationPlugin >>> def my_operation(x: int = 1, y: int = 2): -> int >>> return x + y
Workflow Documentation¶
This documentation provides information on the Worfklow
class and its API reference.
If you are new to developing Xi-CAM plugins, it is recommended that you follow the quick-start documentation first.
For more general development resources, see the Resources page.
Note that the examples in this documentation can be run in a python interpreter outside of Xi-CAM
(for demonstration purposes).
Auxiliary support code to be able to do this is marked with a comment # Only need if not running xicam
.
When developing within Xi-CAM, you will not need the lines of code marked with that comment.
What Is a Workflow?¶
In Xi-CAM, a Workflow
is represents a sequence of one or more OperationPlugins
to execute.
Basically, it allows you to process data through some pipeline of operations.
Multiple operations can be linked together in a Workflow
,
provided that the connection between any two operations is compatible (based on inputs and outputs).
Execution can be performed asynchronously or synchronously.
Where Is Workflow?¶
xicam.core.execution.Workflow
What Does a Workflow Look Like?¶
As mentioned previously, a Workflow can be thought of as a graph-like structure. We can add operations (nodes) and connect them with links (edges).
from xicam.core import execution # Only need if not running xicam
from xicam.core.execution import localexecutor # Only need if not running xicam
from xicam.core.execution import Workflow
from xicam.plugins.operationplugin import operation, output_names
execution.executor = localexecutor.LocalExecutor() # Only need if not running xicam
# Define our operations
@operation
@output_names("sum")
def my_add(x, y):
return x + y
@operation
@output_names("square_root")
def my_sqrt(n):
from math import sqrt
return sqrt(n)
# Instanciate operations
add_op = my_add()
sqrt_op = my_sqrt()
# Create a Workflow and add our operation instances to it
workflow = Workflow()
workflow.add_operations(add_op, sqrt_op)
# Link the "sum" output of add_op to the "n" input of sqrt_op
workflow.add_link(add_op, sqrt_op, "sum", "n")
# Execute the workflow, sending 1 and 3 as initial inputs to add_op (the first operation)
# This should give us sqrt(1 + 3) -> 2.0.
result = workflow.execute_synchronous(x=1, y=3)
print(result) # Should be ({"square_root": 2.0},)
In this example, we use an addition operation and a square root operation in our Workflow. We want to add two numbers, then take the square root of the sum.
First, we instanciate our two operation types.
This gives us an add_op
operation object and a sqrt_op
operation object.
Next, we add our operations to the workflow.
We then want to link the operations together so we first add two numbers,
then take the square root of the result.
We do this by connecting add_op
’s “sum” output to sqrt_op
’s “n” input.
Now that we have added our operations and connected them as we like,
we can run our workflow.
In this case, we will use execute_synchronous
(there are other methods for execution which will be explained later).
However, if we just were to try workflow.execute_synchronous()
,
the workflow wouldn’t know what the “x” and “y” inputs are supposed to be for the first operation, add_op
.
We can either:
pass in data into the first operation(s)’ inputs when we call an execute method on the workflow
have a GUI widget that exposes the operations through the GUI (such as
WorkflowEditor
), which can provide values directly to the operations’ inputs
In this example,
we used option 1
(for an example of option 2,
see the ExamplePlugin
’s use of WorkflowEditor
in the quick-start documentation).
To do this, we passed x=1
and y=3
to our execute_synchronous
call,
which provided values for the invert
operation’s x
and y
input arguments.
Useful Methods for Modifying the Workflow¶
Here is a condensed version of the various ways to modify a Workflow’s operation and links. For more information, see the API Reference.
Adding, Inspecting, and Removing Operations¶
Adding operations:
add_operation
– add an operation to theWorkflow
add_operations
– add multiple operations to theWorkflow
insert_operation
– insert an operation at a specific index in theWorkflow
Inspecting operations:
operations
– get the operations currently in theWorkflow
Removing operations:
remove_operation
– remove an operation from theWorkflow
clear_operations
– remove all operations from theWorkflow
Adding, Inspecting, and Removing Links¶
Adding links:
add_link
– add a link between one operation’s output and another’s inputauto_connect_all
– try to automatically connect all the operations based on input/output names
Inspecting links:
links
– get all links in theWorkflow
operation_links
– get all links connected to a specific operation in theWorkflow
get_inbound_links
– get all incoming links to a specific operation in theWorkflow
get_outbound_links
– get all outgoing links from a specific operation in theWorkflow
Removing links:
remove_link
– remove a link from theWorkflow
clear_operation_links
– remove all links for a specified operation in theWorkflow
clear_links
– remove all links in theWorkflow
Enabling and Disabling an Operation¶
It is possible to enable or disable operations.
By default, all operations added to a Workflow
are enabled.
For more information, see the API Reference.
Executing a Workflow¶
When you execute a Workflow, the operations are executed based on how they are linked together.
There are a few ways to run a Workflow: execute
, execute_synchronous
, and execute_all
.
Synchronous Execution¶
As we saw in our example earlier, we can use execute_synchronous
to run a Workflow as a normal snippet of Python code.
When this method is run, the we wait until we get a result back before the interpreter can continue running code.
Asynchronous Execution (Recommended)¶
The execute
and execute_all
methods are asynchronous, so they run in a separate thread.
This is highly beneficial in a GUI environment like Xi-CAM,
since we don’t want to block Xi-CAM’s UI from responding,
and we could potentially offload execution onto a remote device.
These methods take in several parameters; for now, we will focus on three of these parameters:
callback_slot
– Function to execute when the results of the Workflow are ready. The callback_slot gives you access to these results as a positional argument. This is invoked for each result. For example, let’s say you have a crop operation that takes in an image (array) as an input parameter. You could pass in a list of images to crop toWorkflow.execute_all()
, and the callback_slot will be invoked for each of the images in the passed list. Basically, you will get a cropped image for each image sent into the workflow.finished_slot
– Function to execute when the internal thread in the Workflow has finished its execution (all of the operations are done). This occurs once during a Workflow’s execution.kwargs
– Any additional keyword arguments to pass into the method; these usually correspond with the entry operations’ inputs (as we saw in our example earlier).
The primary difference between Workflow.execute
and Workflow.execute_all
is
that execute_all
will run multiple times for the kwargs
passed in.
This means the kwargs
must have an iterable value.
Let’s look at some examples.
Let’s revisit our addition and square root workflow from earlier but make it asynchronous:
from qtpy.QtWidgets import QApplication # Only need if not running xicam
from xicam.core import execution # Only need if not running xicam
from xicam.core.execution import localexecutor # Only need if not running xicam
from xicam.core.execution import Workflow
from xicam.plugins.operationplugin import operation, output_names
qapp = QApplication([]) # Only need if not running xicam
execution.executor = localexecutor.LocalExecutor() # Only need if not running xicam
# Define our operations
@operation
@output_names("sum")
def my_add(x, y):
return x + y
@operation
@output_names("square_root")
def my_sqrt(n):
from math import sqrt
return sqrt(n)
# Define callback slot (when a result is ready)
def print_result(*results):
print(results)
# Define finished slot (when the workflow is entirely finished)
def finished():
print("Workflow finished.")
# Instanciate operations
add_op = my_add()
sqrt_op = my_sqrt()
# Create a Workflow and add our operation instances to it
workflow = Workflow()
workflow.add_operations(add_op, sqrt_op)
# Link the "sum" output of add_op to the "n" input of sqrt_op
workflow.add_link(add_op, sqrt_op, "sum", "n")
# Execute the workflow, sending 1 and 3 as initial inputs to add_op (the first operation)
# This should give us sqrt(1 + 3) -> 2.0.
workflow.execute(callback_slot=print_result,
finished_slot=finished,
x=1,
y=3)
This will print out:
({'square_root': 2.0},)
Workflow finished.
Notice that we’ve added two new functions for our callback slot and our finished slot.
print_result
will be called when the workflow has finished its execution and the result is ready.
finished
will be called when the workflow has finished execution for all of its input data.
In this case, we have only one set of input data, x=1
and y=3
.
(Also note that we have an additional import and that we are creating a QApplication; this is not needed when working within Xi-CAM).
Now, let’s say we want to do this addition and square root workflow for multiple sets of x and y inputs.
We can use execute_all
to do this:
from qtpy.QtWidgets import QApplication # Only need if not running xicam
from xicam.core import execution # Only need if not running xicam
from xicam.core.execution import localexecutor # Only need if not running xicam
from xicam.core.execution import Workflow
from xicam.plugins.operationplugin import operation, output_names
qapp = QApplication([]) # Only need if not running xicam
execution.executor = localexecutor.LocalExecutor() # Only need if not running xicam
# Define our operations
@operation
@output_names("sum")
def my_add(x, y):
return x + y
@operation
@output_names("square_root")
def my_sqrt(n):
from math import sqrt
return sqrt(n)
# Define callback slot (when a result is ready)
def print_result(*results):
print(results)
# Define finished slot (when the workflow is entirely finished)
def finished():
print("Workflow finished.")
# Instanciate operations
add_op = my_add()
sqrt_op = my_sqrt()
# Create a Workflow and add our operation instances to it
workflow = Workflow()
workflow.add_operations(add_op, sqrt_op)
# Link the "sum" output of add_op to the "n" input of sqrt_op
workflow.add_link(add_op, sqrt_op, "sum", "n")
# Execute the workflow, sending the inputs x=1,y=3; x=10,y=15; x=50,y=50.
# This should give us 2.0, 5.0, and 10.0.
workflow.execute_all(callback_slot=print_result,
finished_slot=finished,
x=[1, 10, 50],
y=[3, 15, 50])
This will print out:
({'square_root': 2.0},)
({'square_root': 5.0},)
({'square_root': 10.0},)
Workflow finished.
Notice that we've just changed `execute` to `execute_all`, and we've modified the `x` and `y` values to be lists.
Now, we will have three executions: `x=1 y=3`, `x=10 y=15`, and `x=50 y=50`.
Each time one of these executions finishes, our callback slot `print_result` is called.
When the workflow is finished executing everything, then our finished slot `finished` is called.
API Reference¶
- class xicam.core.execution.Workflow(name='', operations=None)¶
Bases:
Graph
- add_link(source, dest, source_param, dest_param)¶
Add a link between two operations in the workflow.
Links are defined from an operation’s parameter to another operation’s parameter. This creates a connection between two operations during execution of a workflow.
- Parameters
source (OperationPlugin) – The operation to link from.
dest (OperationPlugin) – The operation to link to.
source_param (str) – Name of the parameter in the source operation to link (source of the data; output).
dest_param (str) – Name of the parameter in the destination operation to link (where the data goes; input).
- add_operation(operation: OperationPlugin)¶
Add a single operation into the workflow.
- add_operations(*operations: OperationPlugin)¶
Add operations into the workflow.
This will add the list of operations to the end of the workflow.
- as_dask_graph()¶
process from end tasks and into all dependent ones
Returns a tuple that represents the graph as a dask-compatible graph for processing. The second element of the tuple identifies the end node ids (i.e. nodes that do not have connected outputs).
- Returns
A tuple with two-elements, the first being the dask graph, the second being the end task ids.
- Return type
tuple
- attach(observer: Callable)¶
Add an observer to the Workflow.
An observer is a callable that is called when the Workflow.notify method is called. In other words, the observer will be called whenever the Workflow state changes; for example, links are modified, operations are removed, etc. When notified, the observer is called.
- Parameters
observer (Callable) – A callable to add from the Workflow.
- auto_connect_all()¶
Attempts to automatically connect operations together by matching output names and input names.
Makes a best-effort to link operations based on the names of their outputs and inputs. If operation A has an output named “image”, and operation B has an input named “image”, then A “image” will link to B “image”. Outputs and inputs that have matching types in addition to matching names will be favored more for the auto-connection.
If there are no outputs with matching inputs (by name), no links will be added.
- clear_links()¶
Remove all links from the workflow, but preserve the operations.
- clear_operation_links(operation, clear_outbound=True, clear_inbound=True)¶
Remove all links for an operation.
- clear_operations()¶
Remove all operations and links from the workflow.
- detach(observer: Callable)¶
Remove an observer from the Workflow.
An observer is a callable that is called when the Workflow.notify method is called. In other words, the observer will be called whenever the Workflow state changes; for example, links are modified, operations are removed, etc. When notified, the observer is called.
- Parameters
observer (Callable) – The callable to remove from the Workflow.
- disabled(operation)¶
Indicate if the operation is disabled in the workflow.
- Parameters
operation (OperationPlugin) – Operation to check if it is disabled or not.
- Returns
Returns True if the operation is disabled in the Workflow; otherwise False.
- Return type
bool
- disabled_operations()¶
Returns the disabled operations (if any) in the workflow.
- enabled(operation)¶
Indicate if the operation is enabled in the workflow.
- Parameters
operation (OperationPlugin) – Operation to check if it is enabled or not.
- Returns
Returns True if the operation is enabled in the Workflow; otherwise False.
- Return type
bool
- execute(executor=None, connection=None, callback_slot=None, finished_slot=None, except_slot=None, default_exhandle=True, lock=None, fill_kwargs=True, threadkey=None, **kwargs)¶
Execute this workflow on the specified host. Connection will be a Connection object (WIP) keeping a connection to a compute resource, include connection.hostname, connection.username…
- Returns
A concurrent.futures-like qthread to monitor status. The future’s callback_slot receives the result.
- Return type
QThreadFuture
- execute_all(connection=None, executor=None, callback_slot=None, finished_slot=None, yield_slot=None, except_slot=None, default_exhandle=True, lock=None, fill_kwargs=True, threadkey=None, **kwargs)¶
Execute this workflow on the specified host. Connection will be a Connection object (WIP) keeping a connection to a compute resource, include connection.hostname, connection.username…
Each kwargs is expected to be an iterable of the same length; these values will be iterated over, zipped, and executed through the workflow.
- Returns
A concurrent.futures-like qthread to monitor status. The future’s callback_slot receives the result.
- Return type
QThreadFuture
- fill_kwargs(**kwargs)¶
Fills in all empty inputs with names matching keys in kwargs.
- get_inbound_links(operation)¶
Returns the links connected to the operation given (linked inputs of the operation).
The returned dict represents all operations that are connected to operation. Links are represented as a list of 2-element tuples, where the first element of the tuple is another operation’s output parameter, and the second element of the tuple is operation’s input parameter.
Using keys() will give all of the operations that connect to operation. Using values() will give all of the links from each operation to operation.
- Parameters
operation (OperationPlugin) – Operation to get incoming links for (some operation -> operation).
- Returns
Returns a dictionary defining all of the links from any connected operations to operation.
- Return type
defaultdict
- get_outbound_links(operation)¶
Returns the links connected from the operation given (linked outputs of the operation).
The returned dict represents all the operations that operation connects to. Links are represented as a list of 2-element tuples, where the first element of the tuple is operation’s output parameter, and the second element of the tuple is another operation’s input parameter.
Using .keys() on the returned dict will give all of the operations that operation connects to. Using .values() on the returned dict will give all of the links from operation to each operation.
- Parameters
operation (OperationPlugin) – Operation to get outgoing links for (operation -> some operation).
- Returns
Returns a dictionary defining all of the links from operation to any connected operations.
- Return type
defaultdict
- insert_operation(index: int, operation: OperationPlugin)¶
Insert an operation at a specific index in the workflow.
- Parameters
index (int) – Index where to insert the operation. 0 will add at the beginning; -1 will add to the end.
operation (OperationPlugin) – Operation to insert.
- links()¶
Returns all the links defined in the workflow.
Returns a list of tuples, each tuple representing a link as follows: source operation, destination operation, source parameter, destination parameter.
Note that the links are shown as outbound links.
- Returns
Returns a list of the links (defined as outbound links) in the workflow.
- Return type
list
- notify()¶
Notify the observers; the observers will be called.
- operation_links(operation: OperationPlugin)¶
Returns the outbound links for an operation.
Returns a list of tuples, each tuple representing a link as follows: operation, destination operation, operation source parameter, destination parameter.
- Returns
Returns a list of the links (defined as outbound links) for operation.
- Return type
list
- property operations¶
Returns the operations of this workflow.
- remove_link(source, dest, source_param, dest_param)¶
Remove a link between two operations.
- Parameters
source (OperationPlugin) – The source operation to remove a link from.
dest (OperationPlugin) – The destination operation to remove a link from.
source_param (str) – Name of the source parameter that is defining the link to remove.
dest_param (str) – Name of the destination parameter that is defining the link to remove.
- remove_operation(operation, remove_orphan_links=True)¶
Remove an operation from the workflow.
- Parameters
operation (OperationPlugin) – Operation to remove from the workflow.
remove_orphan_links (bool) – If True, removes all links that link to the operation to be removed. If False, does not remove any links for the operation and returns the removed operations links dict (default is True).
- Returns
By default (remove_orphan_links is True), returns None. Otherwise, returns the links for the removed operation.
- Return type
defaultdict
- set_disabled(operation: OperationPlugin, value: bool = True, remove_orphan_links: bool = True, auto_connect_all: bool = True)¶
Set an operation’s disabled state in the workflow.
By default when disabling an operation, links connected to the operation will be removed (remove_orphan_links would be True). If value is False (re-enabling an operation), then no links are changed.
- Parameters
operation (OperationPlugin) – The operation whose disabled state is being modified.
value (bool) – Indicates the disabled state (default is True, which disables the operation).
remove_orphan_links (bool) – If True and value is True, removes the links connected to the operation. Otherwise, no links are changed (default is True).
auto_connect_all (bool) – If True, then a best-effort attempt will be made to try to reconnect the operations in the workflow (default is True). See the Graph.auto_connect_all method for more information.
- Returns
Returns a list of any orphaned links for an operation that is set to disabled. Default behavior will return an empty list (when remove_orphan_links is True). If enabling an operation (value is False), then an empty list is returned, as no links are changed.
- Return type
list
- stage(connection)¶
Stages required data resources to the compute resource. Connection will be a Connection object (WIP) keeping a connection to a compute resource, include connection.hostname, connection.username…
- Returns
A concurrent.futures-like qthread to monitor status. Returns True if successful
- Return type
QThreadFuture
- toggle_disabled(operation: OperationPlugin, remove_orphan_links=True, auto_connect_all=True)¶
Toggle the disable state of an operation.
By default, when an operation is toggled to a disabled state, any links connected to the operation will be removed.
- Parameters
operation (OperationPlugin) – The operation to toggle disable state for.
remove_orphan_links (bool) – If True, when the operation’s toggle state is toggled to disabled, any links connected to the operation will be removed (default is True).
- Returns
Returns a list of any orphaned links for an operation. Default behavior will return an empty list. A non-empty list can be returned when remove_orphan_links is False and the connected operation is toggled to disabled.
- Return type
list
- validate()¶
Validate all of: - All required inputs are satisfied. - Connection is active. - ?
- Returns
True if workflow is valid.
- Return type
bool
Install Xi-CAM¶
If you haven’t already installed Xi-CAM, follow the installation instructions for your operating system:
Installing Xi-CAM for Linux¶
Installing Xi-CAM requires a few system components to be installed. After successfully installing these components and Xi-CAM, you will be ready to start developing Xi-CAM plugins!
Install python3¶
First, ensure that you have python3.8 installed on your system.
Consult your specific distribution’s package manager for installing python3.
Create and Activate a Virtual Environment¶
Creating a virtual environment allows you to install and uninstall packages without modifying any packages on your system. This is highly recommended.
There are a couple of ways to create a virtual environment:
via the venv module provided with python3
via conda (you will need to install this from anaconda.org or miniconda.org)
In the commands below, we will create a new environment called xicam in your home directory, and then activate the environment.
Once an environment is activated, any packages installed through pip will be installed into this sequestered xicam environment. (If using conda, you can install either with pip or conda.)
If you would like to create a virtual environment, run the following:
cd ~
python3 -m venv xicam
source xicam/bin/actviate
If you would like to create an environment through conda, run the following:
cd ~
conda create -n xicam python=3.8
conda activate xicam
Install Python Qt Bindings¶
Xi-CAM depends on a GUI application framework called Qt; you will need to install one of the python bindings for Qt (PyQt5 or PySide2) in order to run Xi-CAM.
Make sure that you have activated the xicam environment.
For example, you can install the PyQt5 pip package as follows:
pip install PyQt5
Install the Xi-CAM package¶
Now that we have activated a new xicam environment and installed PyQt5, we can install Xi-CAM:
pip install xicam
To ensure everything is installed correctly, you can run Xi-CAM as follows:
xicam
Where Do I Go from Here?¶
You are now ready to start developing plugins for Xi-CAM!
To learn about developing plugins for Xi-CAM, see the Quick Start Guide.
Installing Xi-CAM for MacOS¶
Installing Xi-CAM requires a few system components to be installed. After successfully installing these components and Xi-CAM, you will be ready to start developing Xi-CAM plugins!
Install python3¶
First, ensure that you have python3.8 installed on your system.
The quickest way to do this is by downloading and running the python.org installer for python3. The python3.8 macOS 64-bit installer can be found here.
Alternatively, you can install XCode and homebrew to manage multiple versions of python on your system.
Create and Activate a Virtual Environment¶
Creating a virtual environment allows you to install and uninstall packages without modifying any packages on your system. This is highly recommended.
There are a couple of ways to create a virtual environment:
via the venv module provided with python3
via conda (you will need to install this from anaconda.org or miniconda.org)
Using the Terminal application, we will create a new environment called xicam in your home directory, and then activate the environment.
Once an environment is activated, any packages installed through pip will be installed into this sequestered xicam environment. (If using conda, you can install either with pip or conda.)
If you would like to create a virtual environment, run the following:
cd ~
python3 -m venv xicam
source xicam/bin/actviate
If you would like to create an environment through conda, run the following:
cd ~
conda create -n xicam python=3.8
conda activate xicam
Install Python Qt Bindings¶
Xi-CAM depends on a GUI application framework called Qt; you will need to install one of the python bindings for Qt (PyQt5 or PySide2) in order to run Xi-CAM.
Make sure that you have activated the xicam environment.
For example, you can install the PyQt5 pip package as follows:
pip install PyQt5
Install the Xi-CAM package¶
Now that we have activated a new xicam environment and installed PyQt5, we can install Xi-CAM:
pip install xicam
To ensure everything is installed correctly, you can run Xi-CAM as follows:
xicam
Where Do I Go from Here?¶
You are now ready to start developing plugins for Xi-CAM!
To learn about developing plugins for Xi-CAM, see the Quick Start Guide.
Installing Xi-CAM for Windows¶
Installing Xi-CAM requires a few system components to be installed. After successfully installing these components and Xi-CAM, you will be ready to start developing Xi-CAM plugins!
Install python3¶
On Windows, a great way to manage python installations and packages is through Anaconda. Follow their Windows installation instructions, which will install the conda package manager, Anaconda, Anaconda Prompt, and Anaconda Navigator.
Anaconda – A package that provides conda and several common python packages
Anaconda Prompt – A command line shell for managing conda environments and installing packages
Anaconda Navigator – A GUI for managing conda environments and installing packages
Open the Anaconda Prompt program.
Then, create a new environment called xicam. This creates a sequestered space on your system to install xicam and its dependencies without modifying any of your system’s libraries.
Next, activate the environment. This tells the system to use the libraries and applications inside the environment.
conda create -n xicam python=3.8
conda activate xicam
Install Python Qt Bindings¶
Xi-CAM depends on a GUI application framework called Qt; you will need to install one of the python bindings for Qt (PyQt5 or PySide2) in order to run Xi-CAM.
Make sure that you have activated the xicam environment.
In your open Anaconda Prompt window, install the pyqt conda package as follows:
conda install pyqt
Install the Xi-CAM package¶
Now that we have activated a new xicam environment and installed pyqt, we can install Xi-CAM using a python package management tool called pip. Run the following in your open Anaconda Prompt.
pip install xicam
To ensure everything is installed correctly, you can run Xi-CAM as follows:
xicam
Where Do I Go from Here?¶
You are now ready to start developing plugins for Xi-CAM!
To learn about developing plugins for Xi-CAM, see the Quick Start Guide.
Copyable Instructions¶
Anaconda Prompt:
cd ~
conda create -n xicam
conda activate xicam
conda install -c conda-forge pyqt
pip install xicam
xicam
Overview¶
In this guide we will:
Explore the main window of Xi-CAM
Download and install an Example Plugin
Configure a sample catalog so we can load data
Explore the Example Plugin
Key Concepts¶
Here is a quick overview of some concepts that will be explored in this guide. Note that more documentation is available for each of these concepts.
We have one GUIPlugin
(ExamplePlugin
) -
this will be a plugin that you will be able to select and see within Xi-CAM.
The layout of the GUIPlugin
is defined by a GUILayout
.
We have a few OperationPlugins
(invert
and random_noise
) -
These plugins are basically functions that take in data
and output derived data.
We also need a way to actually run data through the operations.
To do this, we have a Workflow
(ExampleWorkflow
) -
this contains linked operations to execute (can be thought of like a pipeline).
Looking at Xi-CAM’s Main Window¶
Let’s look at what the main window in Xi-CAM looks like first:

The main window of Xi-CAM after it has finished loading.¶
When Xi-CAM finishes loading, we see the window as shown above. Any installed GUIPlugins will be visible (and selectable) at the top (note that you will probably not have any installed yet).
We can also see some of the default widgets provided:
a welcome widget in the center of the window
a preview widget in the top-left (lefttop) of the window, which shows a sample of selected data in the data browser widget
a data browser widget on the left of the window, which can show available databroker catalogs
Quick GUILayout Overview¶
We mentioned the terms center, lefttop, and left above.
These correspond to positions in a GUILayout
.
Here is a quick overview of how the Xi-CAM main window is organized:

The layout of Xi-CAM’s main window.¶
You can see that the layout of Xi-CAM follows a 3x3 grid, where each section is named according to its orientation in relation to the center of the window.
(Note that any GUIPlugins
you create will have one or more of these GUILayouts
).
Download and Install the ExamplePlugin¶
Now that we have looked at the main window and its layout, let’s download the Example Plugin.
cd ~
git clone https://github.com/Xi-CAM/Xi-CAM.ExamplePlugin
cd Xi-CAM.ExamplePlugin
What’s Inside the ExamplePlugin Repository¶
The repository will contain the following:

The contents of the ExamplePlugin repo when you clone it.¶
At the top there are a few files and directories:
setup.py
- describes how to install this as a python package; also used to register plugins (via entry points).configure
- special directory for this example, helps set up a catalogxicam
- directory that acts as a python namespace package
In xicam
, there is a exampleplugin
subpackage that contains:
__init__.py
- makesexampleplugin
a python package; also exposes theExamplePlugin
classexampleplugin.py
- module that contains theExamplePlugin
GUI pluginoperations.py
- module that contains the exampleOperationPlugins
workflows.py
- module that contains the exampleWorkflows
How Do I Install the Example Plugin?¶
So far, we have only downloaded the Example Plugin - we still need to install it so Xi-CAM can find it and load it.
We can install downloaded plugins using a pip editable install:
pip install -e .
This uses Python’s entry points mechanism to register plugins for Xi-CAM to see.
Exploring the Example Plugin Interface¶
When you run xicam
,
you should now see the Example Plugin available at the top right of the main window.
Select it and you should the Example Plugin layout:

The Example Plugin. Uses a CatalogView, DynImageView, and WorkflowEditor as widgets in its layout.¶
In the center, we have a CatalogView
that will be used to display loaded data.
On the right, there is a WorkflowEditor
that shows the operations in the workflow
and allows for running the workflow.
At the bottom, there is a DynImageView
, which will be used to display the results data.
How Do I Load Data?¶
Now that we have the Example Plugin installed, we need to have data to load into it.
For purposes of this guide, we will be configuring a catalog called “example_catalog.”
For more information, see the Bluesky DataBroker documentation.
Configuring a Catalog¶
There is a configure/
directory in the repository we cloned.
This contains a catalog configuration file, a msgpack catalog, and a script.
Feel free to inspect the script before you run it; it will attempt to set up a msgpack catalog source for Xi-CAM to use:
cd configure
python setup_catalog.py
cd ..
Loading a Catalog from the Data Resource Browser¶
Now that we’ve configured the catalog, let’s make sure that Xi-CAM can see it.
When loading a catalog into Xi-CAM, you must have a GUIPlugin active. Let’s select our “Example Plugin.”
Look at the Data Resource Browser on the left hand side of the window. The Data Resource Browser gives us access to two different types of data browsers by default:
a bluesky browser for catalogs (adapted from work done by NSLS-II)
a local file browser
After configuring our example catalog, the bluesky catalog browser should have the text “example_catalog” in the Catalog drop-down box.
Notice that it also has two text inputs, Since and Until. Our example catalog was created in the beginning of 2020. In order to see the data (catalogs) our “example_catalog” contains, we need to change the Since text input.
Change it’s value to “2020-01-01”. This will now look for any data that was created since the start of 2020. After making this change, the example_catalog will be re-queried for data created within these new dates.
You should see a catalog show up in the table below with the id 349497da. If you single-click the row in the table to highlight it, more information and a preview of the data should be shown as well. You can then open it with the “Open” button.

Here we see catalog 349497da in the DataResourceBrowser. It has one stream (primary) with 10 events in it. A preview shows the first frame of the data, and the opened data appears in the center.¶
You should see Clyde the cat loaded into the center CatalogView
.
Running a Workflow¶
Our Example Plugin has one internal workflow, the ExampleWorkflow
.
The ExampleWorkflow
contains two OperationPlugins
(operations):
invert
- inverts its input imagerandom_noise
- applies random noise to its input image, has a “strength” parameter to define how much noise to apply to the image
This workflow is exposed in the GUI with a WorkflowEditor
on the right side of the layout.
Now that we have loaded some data, let’s run our workflow by clicking the “Run Workflow” button.

The result data after running the workflow. Note that the color lookup table can be changed by right-clicking the gradient bar.¶
You should see an inverted picture with some random noise added to it.
Note that you can adjust the amount of random noise
by selecting the “random_noise” text in the WorkkflowEditor
,
then changing the value of “strength” that shows up in the parameter tree above.
Examining the Code¶
Let’s take a quick look at how the code is implemented for our Example Plugin.
The code for this particular plugin is organized into three modules:
exampleplugin.py
- Defines theExamplePlugin
(the GUIPlugin)operations.py
- Defines twoOperationPlugins
: invert and random_noiseworkflows.py
- Defines anExampleWorkflow
with the invert and random_noise operations
operations.py¶
Here we define OperationPlugins
(or operation)
An operation can be thought of as a function; input data is sent into the operation, and the operation generates some output with the given input.
When defining an OperationPlugin
,
we use Python decorators (the @
seen in the code).
At the very least,
you must provide the @operation
and @output_names
decorators for an operation.
workflows.py¶
Here we define an ExampleWorkflow
.
We add our two operations to the ExampleWorkflow
,
then connect them so that invert
’s “output_image” value is sent to random_noise
’s input image argument.
exampleplugin.py¶
Here we define the gui plugin ExamplePlugin
.
We provide a name
for the plugin,
which will display as “Example Plugin” in Xi-CAM.
We define our widgets, our layout,
and any internal objects we might need (like the workflow) inside of our __init__
method.
We connect the WorkflowEditor
’s sigRunWorkflow
signal
to our run_workflow
method.
This means whenever “Run Workflow” is clicked in the WorkflowEditor,
our ExamplePlugin
’s run_workflow
method will be called.
We also define a results_ready
method
that will be called whenever our workflow has finished executing its operations.
Providing callback_slot=self.results_ready
in our execute
call
sets up this connection for us.
Entry Points¶
An entry point is a mechanism that can be used to make objects discoverable by a common interface / name.
Xi-CAM Entry Points¶
In Xi-CAM, you can define entry points
and then run pip install -e .
in your plugin package directory to register plugins.
This allows Xi-CAM to see your plugins when it loads.
Entry points are defined in setup.py
files, in the entry_points
key.
Let’s look at an example repository and setup.py
:
setup.py
xicam/
myplugin/
__init__.py - defines MyGUIPlugin (also marks this directory as a Python module)
operations/
__init__.py - marks this directory as a Python module
edge_detection.py - contains edge detection operations (laplace and sobel)
workflows/
__init__.py - (marks this directory as a Python module)
myworkflow.py - defines MyWorkflow.py
Here’s what our entry_points
might look like in setup.py
:
entry_points = {
"xicam.plugins.GUIPlugin": ["myguiplugin = xicam.myplugin:MyGUIPlugin"],
"xicam.plugins.OperationPlugin": [
"laplace_operation = xicam.myplugin.operations.edge_detection:laplace",
"sobel_operation = xicam.myplugin.operations.edge_detection:sobel"
],
}
As seen above, entry_points
is a dictionary,
where each key is an entry point and each value is a list of objects / types being registered to that entry point.
The syntax is: "entry point name": ["some_identifier = package.subpackage.module:ClassName"]
.
In this case,
we are registering MyGUIPlugin
to the xicam.plugins.GUIPlugin
entry point.
Similarly,
we are registering the laplace
and sobel
operations to the xicam.plugins.OperationPlugin
entry point.
Note that Workflows
are not registered in this way; they are not Xi-CAM plugins.
Whenever you modify entry points, you must reinstall your package.
You can do this by running pip install -e .
in your package directory.
When Xi-CAM loads,
it will see the xicam.plugins.GUIPlugin
entry point key
and load in MyGUIPlugin
defined (in the value).
Similarly, Xi-CAM will see the xicam.plugins.OperationPlugin
entry point key
and load in the laplace
and sobel
operations.
More Information¶
For more information about entry points, see the following:
https://entrypoints.readthedocs.io/en/latest/
https://packaging.python.org/specifications/entry-points/
https://amir.rachum.com/blog/2017/07/28/python-entry-points/
Data ingestion in Xi-CAM¶
What is an ingestor?¶
The ingestor
design is specified by the Databroker team to
provide an entrypoint for data generated external from the Bluesky environment. An ingestor is a Callable
that accepts
a URI (often a local file path) and yields (name, doc)
pairs. The yielded data follows the Bluesky event-model
structure (see event-model documentation). Synthesizing these
event-model documents is made easier with the RunBuilder
(see Bluesky-Live documentation).
Resources¶
Example Xi-CAM Plugins¶
Xi-CAM CatalogViewer Plugin - Example of a simple single-stage GUIPlugin.
Xi-CAM Log Plugin - Example of another simple single-stage GUIPlugin.
Xi-CAM BSISB Plugin - Example of a multi-stage GUIPlugin with more functionality.
Xi-CAM NCEM Plugin - Another example of a multi-stage GUIPlugin with more functionality.
Git¶
Try GitHub - Landing page for some introductions and resources about git and GitHub.
Git Handbook - An introduction to git and GitHub.
NSLS-II¶
Useful resources about NSLS-II software that Xi-CAM uses.
Databroker Catalog - Describes how to configure and use a databroker catalog.
Event Model - Describes an event-based data model.
Bluesky Documents - Describes what a bluesky document is.
Python¶
Here are a few resources regarding object-oriented programming with Python3. Feel free to look through these or even through resources you find on your own if you are interested.
Qt¶
Qt is a framework written in C++ for developing graphical user interfaces. PySide2 and PyQt5 are two different python bindings to the Qt C++ API. QtPy is a wrapper that allows for writing python Qt code with either PyQt5 or PySide2 installed.
Xi-CAM uses QtPy to interact with different Python bindings to Qt.
QtPy allows you “to write your code as if you were using PySide2 but import Qt modules from qtpy instead of PySide2
(or PyQt5)”.
The references below show PySide2 examples and documentation; when writing a Xi-CAM
plugin, make sure to use the qtpy
modules when importing.
PySide2 Documentation - Documentation for PySide2. Since the QtPy API resembles PySide2, this documentation is helpful for looking up python Qt modules and classes that you may use.
PyQt5 GUI Tutorial - Introductory tutorial for learning the basic concepts of Qt. Note: this tutorial is written for PyQt5, remember to import from
qtpy
instead ofPyQt5
orPySide2
when writing code for Xi-CAM.PySide2 Simple Clickable Button - Short tutorial that describes the concept of signals and slots in Qt and shows how to create a button that responds to clicking.
PyQtGraph - Documentation for the pyqtgraph package, which relies on Qt and provides basic data visualization (plotting) and various widgets (helpful for writing Xi-CAM GUIPlugins).