# Building plug-in tools for SICK Nova

SICK AppSpace apps built on SICK Nova can be extended with new tools developed as Nova plug-ins. This page describes how to use the plug-in API to develop tools for SICK Nova releases with API version 2.9.0.

For the detailed API reference, see Tool API 2.9.0.

Note

The tool plug-in API may be subject to change and complete forward compatibility with future releases is not guaranteed.

## Requirements

- A SICK Nova variant.

- SICK AppStudio (>= 3.0.0) with valid license, for plug-in tool development.

- SICK AppManager (>= 1.4.0), for deployment of plug-in tools and SICK Nova.

## Overview

Plug-in tools are implemented as individual SICK AppSpace apps. Communication between the SICK Nova SensorApp, the plug-in tool and its user interface is abstracted by the tool plug-in API, which is made available to the plug-in tool at run-time.

There is a collection of sample tools available on SICK AppPool. The sample tools are intended as examples on how to use the tool API. The Sample Blob Counter-tool will serve as reference in this document.

For viewing the source code of the sample tools:

1. Deploy one or several Nova Sample Tools on the device (or emulator) in SICK AppManager.

2. Open SICK AppStudio and connect to the device.

3. Transfer the sample apps to your working directory in AppStudio (the apps are shown under the Device tab).

4. Access the script component of the app to view the source code.

When creating your own tool, you can generate a template for it using the Tool generator.

## App content

Hint

“MyTool” in this documentation is a placeholder for the app name of your tool.

App content in AppStudio

The tool plug-in API is enabled by the module `Nova.Tool`, which should be loaded by the tool script file. This is described in MyTool file.

Tool plug-in apps have the following components:

- resources: includes icon, language file and help text for user interface. See Resources.

- scripts: include the source code of the plug-in tool. See Scripts.

- project file: Specifies App metadata. See Project file.

### Project file

The project file, at `"MyTool/project.mf.xml"` is automatically generated when an app is created in AppStudio, but must be adapted for Nova.

The project-file contains meta-information about the app, such as its protection levels, version, entry point and its served functions and events. For your plug-in tool to work, the `"project.mf.xml"` must have specific content which is similar for all tools.

Hint

When using the Tool generator, the project.mf.xml will be created with the correct information

This is a template for the project-file for a Nova tool:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <manifest>
        <application name="MyTool">
            <crown name="MyTool">
                <desc></desc>
                <serves>
                    <event name="ToolEvent">
                        <desc></desc>
                        <param name="arg" type="string" desc=""/>
                    </event>
                <function name="command">
                    <desc></desc>
                    <param name="cmd" type="string" desc=""/>
                    <param name="arg" type="auto" multiplicity="?" desc=""/>
                    <return name="result" type="auto" multiplicity="?" desc=""/>
                </function>
                <function name="ui">
                    <desc></desc>
                    <param name="cmd" type="string" desc=""/>
                    <param name="arg" type="auto" desc=""/>
                    <return name="result" type="string" desc=""/>
                </function>
                </serves>
            </crown>
            <meta key="author">TemplateAuthor</meta>
            <meta key="version">0.0.0</meta>
            <meta key="priority">low</meta>
            <meta key="read-protected">false</meta>
            <meta key="copy-protected">false</meta>
            <meta key="LuaLoadAllEngineAPI">true</meta>
            <entry path="scripts" default="Bootstrap.lua"/>
        </application>
    </manifest>

Follow these steps to get a correct project file for your tool (or use the Tool generator):

1. Open the “MyTool/project.mf.xml” created by AppStudio in a text editor (notepad, Visual Studio Code, etc.)

2. Copy the content from the `<meta key="author">...</meta>` text and paste it somewhere so you can retrieve it later

3. Replace the “project.mf.xml” with the template content above

4. Replace all instances of “MyTool” in the template with the name of your app.

5. Replace “TemplateAuthor” with the original author from step 2.

Note

Replacing the project file with the template above will set read protection and copy protection to false. Make sure to set protection levels according to your preferences after replacing the project file.

Note

Served events and functions are identical for all tools and do not need to be modified by the tool developer.

### Resources

Resources include help text, user interface labels and tool icon, which make your tool easier to understand and to use.

#### Help Text resources

The help text is a description of the tool and its parameters and results which is accessible directly in the user interface. Multiple versions of the help text for a tool can be provided, one for each language supported by SICK Nova.

If a help text-file for the chosen language is provided, the help text will be accessible in the tool panel in the user interface as in this image:

To add help text add a folder “resources/help/” with help text files with the correct names according to this table:

Language | Help text file
--- | ---
English | en.html
French | fr.html
German | de.html
Italian | it.html
Chinese | zh.html
Spanish | es.html
Japanese | ja.html
Korean | ko.html

Hint

When using the Tool generator, a blank English help text-file `"resources/help/en.html"` will be added automatically. This file can be copied and modified for each language you wish to support.

#### Language resource files

The SICK Nova user interface can be viewed in different languages. Plug-in tools can use resource files for translations and to specify readable names for tool features (like parameters). See Localization for details.

Hint

The Tool generator creates the English user interface labels file `"resources/lang/en.json"`. The translations should be filled in by the tool developer.

#### Icon resource file

Add an icon to your tool by adding an image file in png or svg format to `resources/`, with the same name as your tool. The icon size should be 48x48 pixels with transparent background. The Tool generator generates unique example icons for tools.

### Scripts

The `scripts/` folder for a plug-in should have the files described in this section.

#### Bootstrap file

The file “scripts/Bootstrap.lua” is used for the start-up procedure of SICK Nova and is identical for all tool apps.

It should have the following content:

>     if _G["NovaMain"] == nil then
>       _G["NovaMain"] = require("API.NovaMain")
>     end

>     _G["Script"].register("NovaMain.InitApps", function()
>       load(_G["NovaMain"].bootstrap(), "bootstrap", "t")() --luacheck:ignore 113
>       _G["_nova_bootstrap_run_app"](_APPNAME, 2)
>     end)

Hint

The Tool generator creates a bootstrap file with the correct content.

Note

> The “`Bootstrap.lua`” file should be the the main file of the app:

Setting “`Bootstrap.lua`” as the main file.

This is specified by `<entry path="scripts" default="Bootstrap.lua"/>` in the Project file.

#### MyTool file

Add your own tool file “scripts/MyTool.lua”. The name of this file should be the same as the name of the app, and with the extension “.lua”. All mentioned script content in this documentation should go into this file.

Hint

Using the Tool generator, a template for “scripts/MyTool.lua” is added automatically.

##### Tool registration

Tools register themselves to provide the SICK Nova SensorApp with the necessary information about them.

Add the following line at the top of “MyTool.lua” to include the `Nova.Tool` module:

    local Tool = require("Nova.Tool")

Register the tool by calling `Tool.register` with the following structure:

    local _MyTool = Tool.register{
        name="My tool",
        category="Analysis",
        regions=true,
        hasOverlays=true,
        parameters={},
        results={},
        thresholds={},
    }

Note

The call to `Tool.register` is made with curly brackets: {}.

Here are some commonly used registration fields for `Tool.register`. See Tool Registration Arguments for the full list.

Registration field | Description | Type | Required
--- | --- | --- | ---
`name` | A `label` for the name of this tool for the user interface (see Localization) | `string` | No, defaults to App name
`category` | The tool category. This should be “Analysis” as plug-in support is not established for other tool types. | `string` | Yes
`regions` | Defines if the tool supports regions, and if so which types. Setting this to `true` will allow an unlimited number of 2D regions of any shapes. When `true`, or with a `regions definition` that is not limited to one region of a fixed type, the tool will have a UI control for specifying regions to operate on. | `bool` or `regions definition` | No, defaults to `false`
`hasOverlays` | Allow the tool to produce visualization overlays shown in the viewer. | `bool` | No, defaults to `false`
`parameters` | The parameters that control the tool. Described in section Parameters. | Parameter list `table` | No
`results` | The results produced by the tool. Described in section Results and thresholds. | Result list `table` | No
`thresholds` | Thresholds for results with user-configurable pass/fail ranges. Described in section Results and thresholds. | Threshold list `table` | No

The returned item from `Tool.register` is the custom tool class for the tool.

##### Parameters

Parameters control the tool execution. Depending on the type of parameter, a suitable control is included in the user interface settings pane of the tool to allow for configuration by the user.

Parameters are instance-specific. This means that if you add two instances of a tool in the user interface, changing a parameter for one of them will not change the corresponding parameter of the other instance.

Parameter values are saved in exported configurations so that tools are reconstructed with the same values when a configuration is imported.

A parameter is added as a dictionary-like Lua-table. The possible entries are given below:

Entry | Required | Entry type
--- | --- | ---
`name` | Yes | string
`type` | Yes | `string`, see Parameter types
`default` | Depends on `type` | Depends on `type`
`range` | Depends on `type` | Depends on `type`
`ui` | Depends on `type` | Depends on `type`
`values` | Depends on `type` | Depends on `type`
`optional` | No | Table, see Optional parameters

See also:

- Parameter types for the supported values for the “type” entry

- Parameter groups for grouping parameters within a collapsible section in the user interface

- Optional parameters for parameters that have an enable/disable toggle controlling if they have an effect

###### Parameter section example

Below is an example of a parameter section and the corresponding appearance in the user interface:

    parameters={
        {name="IntensityThreshold", type="intensity8range", default={0, 200}},
        {name="PostProcess", type="bool", default=false},
        {name="Filter", type="enum", values={"No", "Gaussian", "Median"}, default="No"},
        {name="Iterations", type="int", range={2, 10}, ui={style="slider"}}

See the sample tools and Parameter types for more examples of how parameters are specified.

###### Results and thresholds

For each execution step, tools produce results. Like parameters, the result values are instance specific and different results can have different types. The results of the tool can also be accessed by other tools and communicated to external devices. Certain results can also be visualized with overlays in the image view using the presentation API described in Presentation

Results are declared in the results-section of `Tool.register`. A result entry has the following fields:

Field | Required | Description
--- | --- | ---
name | Yes | The label for the result
type | Yes | The data type for the result
ui | No | Options for user-interface representation

Values for all results specified in the result-section of Tool.register should be set in the execute-function using `output:add`, see SampleBlobCounter execute.

Each tool must have a `Pass` result. This is of type `bool` and can be interpreted as the overall result of the tool — a combination of the other results. To allow for this, numerical results can have a corresponding threshold that returns `true` if the result is within the specified interval.

Note

The threshold for a result must have the same name as the result.

Below is an example of a result and a corresponding threshold section. The result of `Pass` for this example is written to depend on whether the result `Score` is within its corresponding thresholds.

    results={
        {name="Score", type="float"},
        {name="Pass", type="bool"}},
    thresholds={
        {name="Score", type="percentrange", default={80.0, 100.0}}

The images below show the user interface appearance in the two cases of `"Pass" = true` and `"Pass" = false`.

It is not necessary to have a corresponding threshold for a result. Without a threshold the result will be shown in the user interface as a text label with the value, regardless of its type. An example of this is shown with the code snippet and result section below.

    results={
        {name="Score", type='int'},
        {name="Pass", type='bool'}},
    thresholds={}

A result can be omitted from the user interface (but still available for dependent tools) by declaring it with `ui=false`:

    {name="SomeResult", type="float", ui=false}

See also

- Localization for readable names or translations for parameters, thresholds and results in the user interface

- Result types for the available result types

- Threshold types for the available threshold types

##### MyTool.execute

The `execute()` method implements the core functionality of a tool. It is called for each instance of the tool every time a new image acquired or a parameter is changed.

The execute method should perform the tool evaluation using the configured parameters. It should communicate results and, if meaningful, add overlay graphics.

A tool must have an `execute()`-method. A template implementation is added automatically if the Tool generator is used.

The `execute` method should have this signature:

    function _MyTool:execute(input, output)

- `input` contains the image and other parameters needed for the execution of the plug-in tool

- `output` is where the results of the plug-in tool are added.

Besides using the Nova interfaces `input` and `output`, tools are implemented using the SICK AppSpace CROWN API. This means that most CROWNs available in the used firmware of the device can be used within the `execute` method.

For details on the SICK AppSpace CROWN API, see the documentation included in firmware releases on https://support.sick.com/

The subset of Crowns that are used in the Nova interface itself are documented here: AppSpace Crown API.

##### Accessing provider data components

Different devices can produce different kinds of data, for instance height data, intensity data, or color (RGB) data.

To access different data components, first add the following line at the top of “MyTool.lua” to include the `Nova.Types` module:

    local Types = require("Nova.Types")

The example below shows how to check if a component is present and then access it from the `execute` method:

    if not input:hasProviderComponent(Types.DataComponent.Intensity) then
      output.log.warning("ErrorIncorrectTypeImage")
      return setResults()
    end
    local intensityImage = input:getProviderData(Types.DataComponent.Intensity)

It is recommended to check for existing data components and directly fail the tool if no usable component is available.

See `Nova.Types.DataComponent` for a full list of components available for different Nova products. Note that for some products it is possible to enable/disable components in the Acquisition settings.

Currently all calls to `input:getProviderData` will return an `Image`.

For backwards compatibility, `input:getImage` can be called to get the intensity data component.

##### SampleBlobCounter execute

Below is a walkthrough of `_SampleBlobCounter.execute()`. Please see the code for full details and context.

1. Extract the `presentation` interface:

       local presentation = output:getPresentation()

2. Extract the image from `input`

       local image = input:getImage()

3. Declare initial values for the results

       local numBlobs = 0
       local pass = false

4. Create a local function `setResults()` for setting the results to `output`

       local function setResults()
           output:add("NumBlobs", numBlobs)
           output:add("Pass", pass)
       end

5. Get the region (possibly transformed by the parent tool) from `input`. Abort execution if the net region is empty

       local region, onlyNegative = input:getTransformedRegion()
       presentation:addRegion(region)
       if onlyNegative then
         return setResults()
       end

6. Perform tool specific image processing

       local roiPixelRegion = region:toPixelRegion(image)
       -- ...

7. Add objects to be overlaid in the viewer to the `presentation` interface

       presentation:add(blobs)
       presentation:add(cogs)

8. Set results locally, using `thresholds` to determine if the result is successful

       numBlobs = #blobs
       pass = self.thresholds.NumBlobs:contains(numBlobs)

9. Add local results to `output`

       setResults()

##### Presentation

Tools can add overlays to the viewer using the `presentation` interface. The presentation interface is retrieved from `output` as in step 1 in SampleBlobCounter execute.

The overlays can be text, regions or shapes. The tool region (if any) should be added to allow editing it, as in step 5 in SampleBlobCounter execute.

For the list of functions and a usage example, see the reference documentation for the `presentation` interface.

#### Use of self for tool instances

Lua provides object-orientation support much like for example C++ or Java. Tool instances can therefore be viewed as different instances of the same tool class with associated variables and functions (members). The keyword in Lua for accessing members associated with an instance is `self`. `self` is a Lua-table and members can be accessed accordingly.

To begin with, all entries provided in the tool registration gets associated with each instance.

For example:

    local _MyTool = Tool.register{
        name ="MyTool",
    --  ...
        parameters={
        {name="MyBool", type='bool', default=true}
        },
    --  ...
    }

    local _MyTool:execute(input, output)
        print(self.name) -- "MyTool"
    end

Parameters can be accessed similarly:

    local localBool = self.parameters.MyBool
    -- localBool now holds the current value of the parameter "MyBool" for the particular instance.

#### Constructor and destructor

A `constructor` and a `destructor` method can be defined for a tool. The constructor is called automatically each time a new instance of the tool is created. The destructor is called automatically each time an instance is destroyed. Instances are created and destroyed both when the tool is added or removed by a user as well as during job switch or configuration import.

The constructor is useful if there are instance parameters that should not be shown in the user interface, that should not be saved to the configuration and that should persist across executions of the tool. The destructor is useful if there are instance parameters that require additional actions on destruction (e.g. explicit disposal/tear-down).

The constructor and destructor have the following signatures:

    function _MyTool:constructor()
    function _MyTool:destructor()

All members associated with the instance in `Tool.register{}` are available in the constructor and the destructor.

#### Differences between 2D and 3D

The example shown in SampleBlobCounter execute is for a 2D tool. When making a 3D tool there are a few things that should be considered:

- When calling `Tool.register`, use a `regions definition` with `is3D=true`

- Use the API described in Accessing provider data components to access the wanted data components

- When getting the region by calling `input:getTransformedRegion`, the third return value is the region height range

- To show the tool region in the viewer, call `presentation:addRegion` with the height range table as second argument

## Building and deploying a plug-in tool

Plug-in tools should be packaged as `sapk`-files for deployment with SICK AppManager.

1. In SICK AppStudio, package your plug-ins in an .sapk

2. In SICK AppManager, remove all old apps from the device

3. Deploy SICK Nova (.sapk) to the device

4. Deploy your plug-in tools from step 1 to the device

5. Restart all apps

Your plug-in tools should now be visible and usable.