Tutorial 11 - Complex Data Node in Python

This node fills on the remainder of the (CPU for now) data types available through Python. It combines the progressive introduction in C++ of Tutorial 4 - Tuple Data Node, Tutorial 5 - Array Data Node, Tutorial 6 - Array of Tuples, and Tutorial 7 - Role-Based Data Node.

Rather than providing an exhaustive set of attribute types there will be one chosen from each of the aforementioned categories of types. See the section Pythonic Complex Attribute Type Access for details on how to access the representative types.

OgnTutorialComplexDataPy.ogn

The ogn file shows the implementation of a node named “omni.graph.tutorials.ComplexDataPy”, which has one input and one output attribute of each complex (arrays, tuples, roles) type.

 1{
 2    "ComplexDataPy": {
 3        "version": 1,
 4        "categories": "tutorials",
 5        "description": ["This is a tutorial node written in Python. It will compute the point3f array by multiplying",
 6                        "each element of the float array by the three element vector in the multiplier."
 7        ],
 8        "language": "python",
 9        "metadata":
10        {
11           "uiName": "Tutorial Python Node: Attributes With Arrays of Tuples"
12        },
13        "inputs": {
14            "a_inputArray": {
15                "description": "Input array",
16                "type": "float[]",
17                "default": []
18            },
19            "a_vectorMultiplier": {
20                "description": "Vector multiplier",
21                "type": "float[3]",
22                "default": [1.0, 2.0, 3.0]
23            }
24        },
25        "outputs": {
26            "a_productArray": {
27                "description": "Output array",
28                "type": "pointf[3][]",
29                "default": []
30            },
31            "a_tokenArray": {
32                "description": "String representations of the input array",
33                "type": "token[]"
34            }
35        },
36        "tests": [
37            {
38                "$comment": "Always a good idea to test the edge cases, here an empty/default input",
39                "outputs:a_productArray": []
40            },
41            {
42                "$comment": "Multiplication of a float[5] by float[3] yielding a point3f[5], equivalent to float[3][5]",
43                "inputs:a_inputArray": [1.0, 2.0, 3.0, 4.0, 5.0],
44                "inputs:a_vectorMultiplier": [6.0, 7.0, 8.0],
45                "outputs:a_productArray": [[6.0, 7.0, 8.0], [12.0, 14.0, 16.0], [18.0, 21.0, 24.0], [24.0, 28.0, 32.0], [30.0, 35.0, 40.0]],
46                "outputs:a_tokenArray": ["1.0", "2.0", "3.0", "4.0", "5.0"]
47            }
48        ]
49    }
50}

OgnTutorialComplexDataPy.py

The py file contains the implementation of the compute method, which modifies each of the inputs in a simple way to create outputs that have different values.

 1"""
 2Implementation of a node handling complex attribute data
 3"""
 4
 5# This class exercises access to the DataModel through the generated database class for a representative set of
 6# complex data types, including tuples, arrays, arrays of tuples, and role-based attributes. More details on
 7# individual type definitions can be found in the earlier C++ tutorial nodes where each of those types are
 8# explored in detail.
 9
10# Any Python node with array attributes will receive its data wrapped in a numpy array for efficiency.
11# Unlike C++ includes, a Python import is not transitive so this has to be explicitly imported here.
12import numpy
13import omni.graph.core as og
14
15
16class OgnTutorialComplexDataPy:
17    """Exercise a sample of complex data types through a Python OmniGraph node"""
18
19    @staticmethod
20    def compute(db) -> bool:
21        """
22        Multiply a float array by a float[3] to yield a float[3] array, using the point3f role.
23        Practically speaking the data in the role-based attributes is no different than the underlying raw data
24        types. The role only helps you understand what the intention behind the data is, e.g. to differentiate
25        surface normals and colours, both of which might have float[3] types.
26        """
27
28        # Verify that the output array was correctly set up to have a "point" role
29        assert db.role.outputs.a_productArray == og.AttributeRole.POSITION
30
31        multiplier = db.inputs.a_vectorMultiplier
32        input_array = db.inputs.a_inputArray
33        input_array_size = len(db.inputs.a_inputArray)
34
35        # The output array should have the same number of elements as the input array.
36        # Setting the size informs fabric that when it retrieves the data it should allocate this much space.
37        db.outputs.a_productArray_size = input_array_size
38
39        # The assertions illustrate the type of data that should have been received for inputs and set for outputs
40        assert isinstance(multiplier, numpy.ndarray)  # numpy.ndarray is the underlying type of tuples
41        assert multiplier.shape == (3,)
42        assert isinstance(input_array, numpy.ndarray)  # numpy.ndarray is the underlying type of simple arrays
43        assert input_array.shape == (input_array_size,)
44
45        # If the input array is empty then the output is empty and does not need any computing
46        if input_array.shape[0] == 0:
47            db.outputs.a_productArray = []
48            assert db.outputs.a_productArray.shape == (0, 3)
49            return True
50
51        # numpy has a nice little method for replicating the multiplier vector the number of times required
52        # by the size of the input array.
53        #   e.g. numpy.tile( [1, 2], (3, 1) ) yields [[1, 2], [1, 2], [1, 2]]
54        product = numpy.tile(multiplier, (input_array_size, 1))
55
56        # Multiply each of the tiled vectors by the corresponding constant in the input array
57        for i in range(0, product.shape[0]):
58            product[i] = product[i] * input_array[i]
59        db.outputs.a_productArray = product
60
61        # Make sure the correct type of array was produced
62        assert db.outputs.a_productArray.shape == (input_array_size, 3)
63
64        # Create the output token array by copying the input array with the elements changed to strings
65        db.outputs.a_tokenArray = numpy.array([str(x) for x in input_array])
66
67        # Note that at the time of this writing you are not able to assign token arrays element-by-element as you
68        # might do for other types of arrays. So this coding method, usable for other types of arrays such as float[],
69        # would issue a warning and then fail to set the values on the output:
70        #    db.outputs.a_tokenArray_size = input_array_size
71        #    for index, element in enumerate(input_array):
72        #        db.outputs.a_tokenArray[index] = str(element)
73
74        return True

Note how the attribute values are available through the OgnTutorialComplexDataPyDatabase class. The generated interface creates access methods for every attribute, named for the attribute itself. They are all implemented as Python properties, where inputs only have get methods and outputs have both get and set methods.

Pythonic Complex Attribute Type Access

Complex data in Python takes advantage of the numpy library to handle arrays so you should always include this line at the top of your node if you have array data:

import numpy

Database Property

Representative Type

Returned Type

inputs.a_float3

Tuple

[float, float, float]

inputs.a_floatArray

Array

numpy.ndarray[float, 1]

inputs.a_point3Array

Role-Based

numpy.ndarray[float, 3]

As with simple data, the values returned are all references to the real data in the Fabric, our managed memory store, pointing to the correct location at evaluation time.

Python Role Information

The attribute roles can be checked in Python similar to C++ by using the role() method on the generated database class.

def compute(db) -> bool:
    """Run my algorithm"""
    if db.role(db.outputs.a_pointArray) == db.ROLE_POINT:
        print("Hey, I did get the correct role")

This table shows the list of Python role names and the corresponding attribute types that match them:

Python Role

Attribute Types

ROLE_COLOR

colord, colorf, colorh

ROLE_FRAME

frame

ROLE_NORMAL

normald, normalf, normalh

ROLE_POSITION

positiond, positionf, positionh

ROLE_QUATERNION

quatd, quatf, quath

ROLE_TEXCOORD

texcoordd, texcoordf, texcoordh

ROLE_TIMECODE

timecode

ROLE_TRANSFORM

transform

ROLE_VECTOR

vectord, vectorf, vectorh