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

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
"""
Implementation of a node handling complex attribute data
"""
# This class exercises access to the DataModel through the generated database class for a representative set of
# complex data types, including tuples, arrays, arrays of tuples, and role-based attributes. More details on
# individual type definitions can be found in the earlier C++ tutorial nodes where each of those types are
# explored in detail.

# Any Python node with array attributes will receive its data wrapped in a numpy array for efficiency.
# Unlike C++ includes, a Python import is not transitive so this has to be explicitly imported here.
import numpy


class OgnTutorialComplexDataPy:
    """Exercise a sample of complex data types through a Python OmniGraph node"""

    @staticmethod
    def compute(db) -> bool:
        """
        Multiply a float array by a float[3] to yield a float[3] array, using the point3f role.
        Practically speaking the data in the role-based attributes is no different than the underlying raw data
        types. The role only helps you understand what the intention behind the data is, e.g. to differentiate
        surface normals and colours, both of which might have float[3] types.
        """

        # Verify that the output array was correctly set up to have a "point" role
        assert db.role.outputs.a_productArray == db.ROLE_POINT

        multiplier = db.inputs.a_vectorMultiplier
        input_array = db.inputs.a_inputArray
        input_array_size = len(db.inputs.a_inputArray)

        # The output array should have the same number of elements as the input array.
        # Setting the size informs flatcache that when it retrieves the data it should allocate this much space.
        db.outputs.a_productArray_size = input_array_size

        # The assertions illustrate the type of data that should have been received for inputs and set for outputs
        assert isinstance(multiplier, numpy.ndarray)  # numpy.ndarray is the underlying type of tuples
        assert multiplier.shape == (3,)
        assert isinstance(input_array, numpy.ndarray)  # numpy.ndarray is the underlying type of simple arrays
        assert input_array.shape == (input_array_size,)

        # If the input array is empty then the output is empty and does not need any computing
        if input_array.shape[0] == 0:
            db.outputs.a_productArray = []
            assert db.outputs.a_productArray.shape == (0, 3)
            return True

        # numpy has a nice little method for replicating the multiplier vector the number of times required
        # by the size of the input array.
        #   e.g. numpy.tile( [1, 2], (3, 1) ) yields [[1, 2], [1, 2], [1, 2]]
        product = numpy.tile(multiplier, (input_array_size, 1))

        # Multiply each of the tiled vectors by the corresponding constant in the input array
        for i in range(0, product.shape[0]):
            product[i] = product[i] * input_array[i]
        db.outputs.a_productArray = product

        # Make sure the correct type of array was produced
        assert db.outputs.a_productArray.shape == (input_array_size, 3)

        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 FlatCache, 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