Python Plugins

AlliGator supports user-provided Python functions which appear as right-click menu items for the corresponding object or menu.

Currently, only two objects support plugins: the Decay Graph and the Source Image. In addition, FLI Dataset plugins are supported as sub-menus of the Analysis:FLI Dataset menu. Since objects have associated menus (sometimes empty) in the Analysis menu, plugins can also be inserted in the Analysis:Decay Graph and Analysis:Source Image menus.

This page does not describe how to develop Python plugins for AlliGator, but provides an overview of the design principle, followed by a description of the mandatory features for a plugin. The last section show an example of plugin for the Decay Graph graph. Example of plugins for the different supported objects can be found in the example scripts provided by default with AlliGator.

Details on how to implement an AlliGator Python plugin can be found in the AlliGator Python Plugins API page.

Design Principle

LabVIEW has some basic built-in support for communication with Python in the form of the Python Node. The Python Node requires spawning an instance of Python (the path and version of which can be specified by the user in the Settings:Plugins window), which is done when AlliGator starts, and passing it a script path and function name within that script, together with parameters. The functions then returns a result or possibly an error code and message, in which case this is displayed in the Notebook window.

The type of data that is passed to a plugin function is in part specified in the function’s body using AlliGator plugin ‘statements’ (described in subsequent sections), but some of the data also depends on the target of that plugin and does not need to be specified within the function (in other words, it is handled automatically).

For instance, a plugin for the Decay Graph will always pass a list of plots to the function (the so-called graph_data_in), the content of which depends on whether the plugin acts on all plots, selected plots or only the plot from which the function was invoked.

The exact nature of the default data passed to a function is described in the AlliGator Python Plugins API page.

The non-default data passed to the function is specified by the developer in the Input Parameters Definition section, and is passed to the Plugin function as a dictionary (see below and AlliGator Python Plugins API page for details). Some of this data is provided by the user in a dialog window, which is automatically opened by AlliGator before executing the plugin function. The rest is specifically requested internal AlliGator data and does not require user input.

The data returned by the function is, as the input data, separated into two parts: the default data type for the function’s target, and the plugin-specific data, specified by the user in an AlliGator Output Value Type & Destination section. As the input parameters specidied in the Input Parameters Definition section, it is passed as a dictionary (see below and AlliGator Python Plugins API page for details).

As for the default input data, the default output data depends on the function’s target and is described in the AlliGator Python Plugins API page.

Both input and output definition sections are discussed below.

Plugins Folder

Plugins consists of functions saved within Python script files (.py extension).

These script files need to be saved within the Python Plugins folder located in the AlliGator installation folder (generally in C:Users\User_Name\AppData\Local\AlliGator, unless specified otherwise during installation.

Each file can be saved within a subfolder hierarchy within the Plugins Folder.

Plugin Files

Each .py plugin file will give its name to a submenu in the right-click menu of the object it is targeting (see below to learn how to specify a plugin’s target) or the Analysis submenu it is added to (same remark). For instance, if a script file name UCLA_Plot_Functions.py is added and targeted to the Decay Graph, the functions within will be added to the Decay Graph context menu in a submenu named UCLA Plot Functions.

For instance, if there are two files in Plugins folder named plugins 1.py and plugins 2.py, both targeted to the Decay Graph, two submenus will be added to the right-click menu of the Decay Graph, respectively called plugins 1 and plugins 2.

If a submenu already exists, the plugin functions within the .py file will be added to it.

If a function with the same name already exists within the submenu, it will be added with the same name, but will be recognized as distinct by AlliGator. Because that could be confusing for a user, it is recommended to name .py files (and thus plugin submenus) in a way that is fairly unique, for instance by including a lab name in it (as in the example above).

Each plugin submenu is separated from the previous one by an horizontal divider line.

Each plugin file can contain an arbitrary number of Python functions, with valid names for Python, say function_1, function_2,…, function_n. The resulting submenu will contain as many items as there are functions, their names being the names of the functions. For instance, if plugins_1.py contains n functions: function_1, function_2,…, function_n, the corresponding submenu will look like:

  • plugins 1

    • function 1

    • function 2

    • function n

The next section will describe naming conventions for plugin functions, and how to distinguish them from helper functions.

Plugin Function Names

Plugin functions appear in their corresponding submenus as they are named in the script file, with the following transformations:

  • a single underscore at the beginning of the function name is interpreted as meaning that an horizontal separator will precede that function in the menu.

  • double underscores are alternatively replaced by a left and right parenthesis , respectively preceded and followed by a space.

  • single underscores are replaced by single spaces.

For instance, a function named this_is_a_simple_function(*args, **kparams) will appear as this is a simple function in the submenu.

Likewise, a function named simple_operation__selected_plots__(*args,**kparams) will appear as simple operation (selected plots) in the submenu.

Plugin Functions Special Syntax

Plugin functions need to contain some simple additional syntax to be usable by AlliGator. The additional elements appear within comments bracketed between triple hash tags (###) and should therefore not interfere with any Python-specific syntax.

These syntaxic elements are as follows:

  • import statements

  • Destination

  • Plugin Flag

  • Input Parameters Definition

  • Output Value Type & Destination

A plugin function may require special modules to perform its operations. These are typically imported at the beginning of the script. In addition to any plugin-specific module import statement, any AlliGator Python Plugin script requires the following additional two import statements:

import json
import alligator

The first is used to convert the input parameters string from JSON to a dictionary, and the output parameters dictionary to a JSON string, while the second refers to the alligoator.py script provided in the Python Plugins folder, and contains type definitions that are useful to format input and output data for a plugin.

In addition to these two import statements, the other 4 elements to include are briefly reviewed next.

The first element (Destination) tells AlliGator where the plugin functions need to be inserted (in which menu or object’s context menu). This is common to all functions in the script, therefore if plugins for different targets are developed (e.g. one function for the Decay Graph, and another for the Source Image), they will need to be in different script files.

The second element (Plugin Flag) specifies whether the function is actually a plugin function or an helper function (in other words, helper functions are unmodified Python functions and do not need any of the modifications discussed here).

The third element (Input Parameters Definition) is used to inform AlliGator about the parameters needed by the function (some parameters are passed by default, depending on the function’s target, as discussed below). If no parameter is needed, this section can be ignored.

The last element (Output Value Type & Destination) is used by AlliGator to decide where to send the function’s output. If no output is returned, this section can be ignored.

The syntax of these different elements is discussed in the following sections.

Note: In addition to these 4 mandatory elements, it is recommended to include a Python doc string to provide a description of what the function is doing, as well as information on whether or not and what user-provided parameters may be required. This doc string can be sent to the Notebook window by holding the H key down while selecting the Python plugin function in the corresponding menu (the function will not be executed).

Destination

To instruct AlliGator to insert a script’s functions into a specific menu, the following statement needs to be inserted before the different functions:

### AlliGatorTarget = AlliGator/Object/Object_Name ###

and/or

### AlliGatorTarget = AlliGator/Menu/Object_Name ###

where Object_Name is the name of the target (e.g. Decay Graph, Source Image or FLI Dataset).

For instance, to insert all plugin functions within a script into the Analysis:Decay Graph menu, the following statement will be needed:

### AlliGatorTarget = AlliGator/Menu/Decay_Graph ###

Plugin Flag

To tell AlliGator that the function is a Plugin function, the following statement needs to be included after the doc string (if a doc string is provided) and before the Input Parameters Definition statement:

### IsAlliGatorPythonPlugin ###

If a function does not contain this statement, it will not be included in any AlliGator menu and will therefore be invisible to an AlliGator user. This will typically be the case of helper functions called by the Plugin functions.

Input Parameters Definition

Some plugins do not require any input parameters. For instance, if a Decay Graph plugin computes the mean of a plot, the only required input is the plot itself, which would be passed automatically as default data for a Graph plugin (see Python Plugins API). In that case, the remainder of this section can be ignored.

Other plugins will require either user input parameters (e.g. a scaling factor) or AlliGator parameters (e.g. the phasor frequency). Both types of parameters are declared at the beginning of the function in a custom comment section with the general structure shown below.

### AlliGator Input Parameters Definitions ###
### parameter_1:type_1 # description 1
...
### parameter_n:type_n # description n
### End of AlliGator Input Parameters Definitions ###

The first and last line of this (triple) commented section are used by AlliGator to find it and should be reproduced as shown above.

Single (or double) commented lines (or empty lines) in between these two lines will be ignored.

Input parameter declarations follow the model indicated above, that is, a triple comment symbol (###) followed a single parameter declaration per line.

A parameter declaration consists of a unique name (at least in a given function) , followed by a semicolon, followed by the parameter type (from the limited list of supported types (see Python Plugins API for details), including AlliGator, which indicates an internal AlliGator parameter. For a user-provided input parameter, a short description is recommended and should be provided as a single commented string (# description n being replaced by something more… descriptive!). It will appear next to the name and value of the parameter in a dialog box when the plugin is called. An example of such a dialog box (corresponding to the example shown at the bottom of this page) is provided below.

_images/AlliGator-Python-Pugin-Input-Parameters-Dialog.png

AlliGator Python Pugin Input Parameters Dialog

To expose which internal AlliGator parameters can be passed as input parameters, use the Send button in the Settings:Plugins panel. This will copy a list of these parameters in the clipboard, which can then be pasted in any text editor.

Check the Parameter Names only checkbox in the Settings:Plugins panel to get a JSON-formatted list of exposed internal AlliGator parameters without their current value.

As mentioned before, if no input parameter is needed by the function, this section can be omitted.

Output Value Type & Destination

Python plugins ouputs are of four general kinds (for details, see Python Plugins API):

  • error code

  • output message

  • output sent to internal AlliGator variables

  • output sent to objects

Error codes are internal to LabVIEW and will be formatted and sent to the Notebook if the Verbose Error Mode setting is on (Settings:Miscellaneous). Errors are generated by the Python script and are in general useful to debug a plugin. For this reason, it is recommended to turn on Verbose Error Mode when developing and testing a plugin.

The output message is an optional string generated by the plugin function. It is sent to the Notebook, and can therefore be used to display formatted results or information about the plugin’s action (including a non-Python error message).

Output sent to internal AlliGator variables are a way to modify the internal state of AlliGator as a result of executing a Plugin. For instance, a plugin could be change a Settings flag, or the phasor frequency. Use with moderation, as this could have unintended consequences if done improperly.

All previous three types of output do not require any special syntax to be added to the plugin.

The only type of output parameter that requires inclusion of an Output Value Type & Destination comment section are values destined to one of AlliGator’s displayed objects (Graph and Image). The syntax of this section, which should follow the Input Parameters Definition comment section is as follows:

### AlliGator Output Value Type & Destination ###
### value_type:destination
### End of AlliGator Output Value Type & Destination ###

The first and last line of this (triple) commented section are used by AlliGator to find it and should be reproduced as shown above.

Single (or double) commented lines (or empty lines) in between these two lines will be ignored.

Output value and destination statements follow the model indicated above, that is, a triple comment symbol (###) followed a single declaration per line.

Valid value_type are specified in the Python Plugins API page.

Valid destination statements depend on the value_type and the plugin’s declared destination (so for instance, a plots value type cannot be sent to an image destination such as Mask Image but could be sent to Decay Graph).

If no output needs to be sent to a displayed object, this section can be omitted.

To better understand these different syntactic elements and how they fit into a Python function used as a plugin, it is easiest to look at the (not very useful as an AlliGator plugin) example installed with AlliGator and appearing as submenu item of Analysis:Decay Graph and as a context menu item of the Decay Graph.

Decay Graph example

The following example illustrates the principles outlined above. The same example with additional comments is installed in the Plugins Folder by default.

# Decay_Graph_Plugin_Example.py
# Example AlliGator Decay Graph Python Plugin

### AlliGatorTarget = AlliGator/Object/Decay Graph ###
### AlliGatorTarget = AlliGator/Menu/Decay Graph ###

import json
import alligator

def Plot_Scaled_Sum_and_Difference__Selected_Plots__test(
        graph_data_in, params_in_json, addtl_params_out_json_list):

    """Scaled Sum & Difference:

    Acts on the first two selected plots
    Expects one float parameter (k: float64)
    The resulting k*Sum and k*Difference plots are added to the Decay Graph
    """

    ### IsAlliGatorPythonPlugin ###

    ### AlliGator Input Parameters Definitions ###
    ### k:float64 # scaling parameter

    ### Phasor Frequency:AlliGator
    ### Reference Decay:AlliGator
    ### End of AlliGator Input Parameters Definitions ###

    ### AlliGator Output Value Type & Destination ###
    ### Plots:Decay Graph
    ### End of AlliGator Output Value Type & Destination ###

    message = 'Scaled Sum of Selected Plots and Phasor Frequency Update'
    exception_type = "None"
    exception_message = ""

    # decode the parameter string

    params = json.loads(params_in_json)
    k = params['k']
    f = params['Phasor Frequency']

    # decode the graph data named tuple

    graph_name = graph_data_in.Graph_Name
    plots = graph_data_in.Plots
    nplots = len(plots)

    # Adds and subtracts the first 2 plots if they have the same length
    # otherwise returns an error

    if nplots < 2:
        exception_type = "Error"
        exception_message = "Not enough selected plots!"
        graph_data_out = alligator.graph_plugin_data(
            Graph_Name = graph_name,
            Plots = [],
            Reference_Decay = alligator.empty_plot)
    else:
        plot_data1 = plots[0]
        name1 = plot_data1.Plot_Name
        x1 = plot_data1.X_Array
        y1 = plot_data1.Y_Array
        plot_data2 = plots[1]
        name2 = plot_data2.Plot_Name
        x2 = plot_data2.X_Array
        y2 = plot_data2.Y_Array

        ref_decay_data = graph_data_in.Reference_Decay
        ref_decay_name = ref_decay_data.Plot_Name
        ref_decay_x = ref_decay_data.X_Array
        ref_decay_y = ref_decay_data.Y_Array

        # processing of the incoming data

        if (len(x1) != len(x2)):
            exception_type = "Error"
            exception_message = "Plots do not have the same length!"
            plots_out = []
        else:
            sumy = []
            for i in range(len(y1)):
                sumy.append(y2[i] + y1[i])
            diffy = []
            for i in range(len(y1)):
                diffy.append(y2[i] - y1[i])

            # we need to repackage those plots into a list of named tuples
            # (same structure as the input)

            plot_data1_out = alligator.plot_plugin_data(
                Plot_Name = 'Scaled Sum of Plots',
                X_Array = x1,
                Y_Array = sumy
            )
            plot_data2_out = alligator.plot_plugin_data(
                Plot_Name = 'Scaled Difference of Plots',
                X_Array = x1,
                Y_Array = diffy
            )
            plots_out = [plot_data1_out, plot_data2_out]
            message = 'Scaled Sum of Selected Plots (scaling factor: ' +\
                str(k) + ', '+name1 + ', '+name2+') and Phasor Frequency Update'
        graph_data_out = alligator.graph_plugin_data(
            Graph_Name = graph_name,
            Plots = plots_out,
            Reference_Decay = alligator.empty_plot)

        # Finally, we can send back information on the function outcome
        # and can also set AlliGator Parameters
        # all this packaged in a dictionary, converted to json and
        # appended to the (generally) empty string list
        # addtl_params_out_json_list

    info_out_dict = {
    "Notebook Message" : message,
    "Exception Type" : exception_type,
    "Exception Message" : exception_message,
    "AlliGator:Phasor Frequency" : f
    }

    # conversion to JSON string and string is appended to the incoming
    # addtl_params_out_json_list

    addtl_params_out_json_list.append(json.dumps(info_out_dict))

    # return results to AlliGator

    return(graph_data_out)