Config
Config module provides a way to standardize the configuration of Panorama applications via
deploy-time parameters. It can automatically parse deploy-time parameters from the Panorama
application’s input port to a Python dataclass based configuration structure. It also provides
a development tool: a small command-line interface program that can be used to generate JSON
snippets to be pasted into the Panorama application’s graph.json
, the business logic package
definition file package.json
, or even generate markdown documentation about the usage of the
parameters.
This pattern helps you to have a single source of truth about your application parameters: a
ConfigBase
subclass that you implement.
Defining the config structure
You should define the configuration parameters of your application in a
ConfigBase
subclass. You can use primitive parameter types supported
by Panorama SDK (int
, float
, str
and bool
), make ConfigBase parse custom
structures from a string or numeric value, and define cascaded configuration. The following
example showcases these features:
# my_object_detector_config.py
from typing import Sequence
from dataclasses import dataclass, field
from backpack.config import ConfigBase, IntegerListSerDe, cli
@dataclass
class MyObjectDetectorConfig(ConfigBase):
example_string_param: str = field(default='default_value', metadata={
'doc': 'This is an example string parameter.'
})
example_int_param: int = field(default=42, metadata={
'doc': 'This is an example integer parameter.'
})
example_float_param: float = field(default=0.85, metadata={
'doc': 'This is an example float parameter.'
})
example_bool_param: str = field(default=True, metadata={
'doc': 'This is an example bool parameter.'
})
example_integer_list_param: Sequence[int] = field(default=(1, 2, 3, 5, 8, 13), metadata={
'doc': 'A comma-separated list of integers.',
'type': 'string',
'serde': IntegerListSerDe
})
@dataclass
class EmbeddedConfig(ConfigBase):
embedded_param: int = field(default=84, metadata={
'doc': 'This is an embedded parameter.'
})
child_struct: EmbeddedConfig = EmbeddedConfig()
if __name__=='__main__':
cli('my_object_detector', MyObjectDetectorConfig())
The above class defines the config application config structure as a standard Python
dataclass. ConfigBase
searches for several keys in the field
metadata dictionary to support full functionality. The following metadata keys are defined:
doc
: a textual description of the parameter that will be used in the application descriptorgraph.json
as well as in the markdown documentationtype
: ConfigBase will try to infer the Panorama config type (int32
,float32
,string
orboolean
) from the Python type. However, for parsed config types you should manually define the Panorama config type in this field.serde
: SerDes (serializer/deserializer) lets ConfigBase automatically convert a raw string or number parameter value to a complex Python structure. Theexample_integer_list_param
parameter in the above example demonstrates the usage ofIntegerListSerDe
that converts a string containing a comma-separated list of numbers (for example,"1, 1, 2, 3, 5, 8, 13"
) to a proper Python list of integers. You should define the SerDe class to be used with this key.
Using the developer tool
You are encouraged to define the command-line interface (CLI) of your config structure adding
a call to the tool()
method, behind the main module guard. This is
illustrated in the last two lines of the example above. If you saved the config structure in a file
named my_object_detector_config.py
, you can execute the following command from the directory
where the file can be found:
$ python -m my_object_detector_config -h
The CLI help should guide you through the usage of the tool:
usage: config.py [-h] [--code-node CODE_NODE] [--template TEMPLATE]
{nodes,edges,interface,markdown,render}
Configuration snippet generator for my_object_detector application.
This program can generate json and markdown snippets that you can copy-paste to the
metadata and package definitions of your AWS Panorama project. The snippets contain the
definitions of the application parameters in the required format. The following formats
are supported:
- nodes: generates a json snippet to be pasted in the nodeGraph.nodes field of graph.json
- edges: generates a json snippet to be pasted in the nodeGraph.edges field of graph.json.
Specify the code node name.
- interface: generates json a snippet to be pasted in nodePackage.interfaces field of the
package.json of the application code package
- markdown: generates a markdown snippet that you can paste to the README of your project,
or other parts of the documentation.
- render: renders a Jinja2 template. Specify the template filename and the code node name.
positional arguments:
{nodes,edges,interface,markdown,render}
Prints configuration snippets for graph.json nodes, edges,
application interface in package.json, or in markdown format.
optional arguments:
-h, --help show this help message and exit
--code-node CODE_NODE, -c CODE_NODE
Code node name (used in edges snippet)
--template TEMPLATE, -t TEMPLATE
Template file (used in render command)
For example, the following call:
$ python -m my_object_detector_config nodes --code-node my_object_detector_business_logic
will generate the following json snippet, ready to be pasted into graph.json
:
[
{
"name": "example_string_param",
"interface": "string",
"value": "default_value",
"overridable": true,
"decorator": {
"title": "example_string_param",
"description": "This is an example string parameter."
}
},
{
"name": "example_int_param",
"interface": "int32",
"value": 42,
"overridable": true,
"decorator": {
"title": "example_int_param",
"description": "This is an example integer parameter."
}
},
{
"name": "example_float_param",
"interface": "float32",
"value": 0.85,
"overridable": true,
"decorator": {
"title": "example_float_param",
"description": "This is an example float parameter."
}
},
{
"name": "example_bool_param",
"interface": "string",
"value": true,
"overridable": true,
"decorator": {
"title": "example_bool_param",
"description": "This is an example bool parameter."
}
},
{
"name": "example_integer_list_param",
"interface": "string",
"value": "1, 1, 2, 3, 5, 8, 13",
"overridable": true,
"decorator": {
"title": "example_integer_list_param",
"description": "A comma-separated list of integers."
}
},
{
"name": "child_struct_embedded_param",
"interface": "int32",
"value": 84,
"overridable": true,
"decorator": {
"title": "child_struct_embedded_param",
"description": "This is an embedded parameter."
}
}
]
You can also write your graph.json
, application package.json
and markdown documentation
as Jinja2 templates. In this case you can automatize the update of these artifacts after
modifying your ConfigBase
subclass. The config tool CLI offers the
following template variables:
nodes
: list of dictionaries containing the nodes to be placed ingraph.json
edges
: list of dictionaries containing the edges to be placed ingraph.json
interface
: list of dictionaries containing the interface to be placed in application’spackage.json
markdown
: Markdown documentation as string.
For example, you could use the following template graph.json.jinja
for your manifest:
{
"nodeGraph": {
"envelopeVersion": "2021-01-01",
"packages": [
{
"name": "panorama::abstract_rtsp_media_source",
"version": "1.0"
},
{
"name": "panorama::hdmi_data_sink",
"version": "1.0"
},
{
"name": "123456789012::my_app_logic",
"version": "1.0"
},
{
"name": "123456789012::my_model",
"version": "1.0"
}
],
"nodes": [
{
"name": "camera_input",
"interface": "panorama::abstract_rtsp_media_source.rtsp_v1_interface",
"overridable": true,
"launch": "onAppStart",
"decorator": {
"title": "Camera camera_input",
"description": "Abstract camera input of the application."
}
},
{
"name": "display_output",
"interface": "panorama::hdmi_data_sink.hdmi0",
"overridable": false,
"launch": "onAppStart"
},
{
"name": "my_model_node",
"interface": "123456789012::my_model.interface",
"overridable": false,
"launch": "onAppStart"
},
{
"name": "my_app_logic_node",
"interface": "123456789012::my_app_logic.interface",
"overridable": false,
"launch": "onAppStart"
}{{ "," if nodes|length > 0 else "" }}
{% for node in nodes %}
{{ node|to_pretty_json|indent(width=12) }}{{ "," if not loop.last else "" }}
{% endfor %}
],
"edges": [
{
"producer": "camera_input.video_out",
"consumer": "my_app_logic_node.video_in"
},
{
"producer": "my_app_logic_node.video_out",
"consumer": "display_output.video_in"
}{{ "," if nodes|length > 0 else "" }}
{% for edge in edges %}
{{ edge|to_pretty_json|indent(width=12) }}{{ "," if not loop.last else "" }}
{% endfor %}
]
}
}
Using this template, you can generate your graph.json
with:
$ python -m my_object_detector_config render \
--code-node my_object_detector_business_logic \
--template path_to/graph.json.jinja \
> path_to/graph.json
An example package.json.jinja
template:
{
"nodePackage": {
"envelopeVersion": "2021-01-01",
"name": "my_app_logic",
"version": "1.0",
"description": "Default description for package my_app_logic",
"assets": [
{
"name": "my_app_logic_logic_asset",
"implementations": [
{
"type": "container",
"assetUri": "deadbeaf.tar.gz",
"descriptorUri": "deadbeaf.json"
}
]
}
],
"interfaces": [
{
"name": "interface",
"category": "business_logic",
"asset": "my_app_logic_logic_asset",
"inputs": [
{
"name": "video_in",
"type": "media"
}{{ "," if interface|length > 0 else "" }}
{% for item in interface %}
{{ item|to_pretty_json|indent(width=20) }}{{ "," if not loop.last else "" }}
{% endfor %}
],
"outputs": [
{
"name": "video_out",
"type": "media"
}
]
}
]
}
}