The OpenVX Specification  dba1aa3
Design Overview

Software Landscape

OpenVX is intended to be used either directly by applications or as the acceleration layer for higher-level vision frameworks, engines or platform APIs.

dot_overview.png
OpenVX Usage Overview

Design Objectives

OpenVX is designed as a framework of standardized computer vision functions able to run on a wide variety of platforms and potentially to be accelerated by a vendor's implementation on that platform. OpenVX can improve the performance and efficiency of vision applications by providing an abstraction for commonly-used vision functions and an abstraction for aggregations of functions (a “graph”), thereby providing the implementer the opportunity to minimize the run-time overhead.

The functions in OpenVX are intended to cover common functionality required by many vision applications.

Hardware Optimizations

This specification makes no statements as to which acceleration methodology or techniques may be used in its implementation. Vendors may choose any number of implementation methods such as parallelism and/or specialized hardware offload techniques.

This specification also makes no statement or requirements on a “level of performance” as this may vary significantly across platforms and use cases.

Hardware Limitations

The OpenVX focuses on vision functions that can be significantly accelerated by diverse hardware. Future versions of this specification may adopt additional vision functions into the core standard when hardware acceleration for those functions becomes practical.

Assumptions

Portability

OpenVX has been designed to maximize functional and performance portability wherever possible, while recognizing that the API is intended to be used on a wide diversity of devices with specific constraints and properties. Tradeoffs are made for portability where possible: for example, portable Graphs constructed using this API should work on any OpenVX implementation and return similar results within the precision bounds defined by the OpenVX conformance tests.

Opaqueness

OpenVX is intended to address a very broad range of devices and platforms, from deeply embedded systems to desktop machines and distributed computing architectures. The OpenVX API addresses this range of possible implementations without forcing hardware-specific requirements onto any particular implementation via the use of opaque objects for most program data.

All data, except client-facing structures, are opaque and hidden behind a reference that may be as thin or thick as an implementation needs. Each implementation provides the standardized interfaces for accessing data that takes care of specialized hardware, platform, or allocation requirements. Memory that is imported or shared from other APIs is not subsumed by OpenVX and is still maintained and accessible by the originator.

OpenVX does not dictate any requirements on memory allocation methods or the layout of opaque memory objects and it does not dictate byte packing or alignment for structures on architectures.

Object-Oriented Behaviors

OpenVX objects are both strongly typed at compile-time for safety critical applications and are strongly typed at run-time for dynamic applications. Each object has its typedef'd type and its associated enumerated value in the vx_type_e list. Any object may be down-cast to a vx_reference safely to be used in functions that require this, specifically vxQueryReference, which can be used to get the vx_type_e value using an vx_enum.

OpenVX Framework Objects

This specification defines the following OpenVX framework objects.

  • Object: Context - The OpenVX context is the object domain for all OpenVX objects. All data objects live in the context as well as all framework objects. The OpenVX context keeps reference counts on all objects and must do garbage collection during its deconstruction to free lost references. While multiple clients may connect to the OpenVX context, all data are private in that the references that refer to data objects are given only to the creating party. The results of calling an OpenVX function on data objects created in different contexts are undefined.
  • Object: Kernel - A Kernel in OpenVX is the abstract representation of a computer vision function, such as a “Sobel Gradient” or “Lucas Kanade Feature Tracking”. A vision function may implement many similar or identical features from other functions, but it is still considered a single, unique kernel as long as it is named by the same string and enumeration and conforms to the results specified by OpenVX. Kernels are similar to function signatures in this regard.
  • Object: Parameter - An abstract input, output, or bidirectional data object passed to a computer vision function. This object contains the signature of that parameter's usage from the kernel description. This information includes:
  • Object: Node - A node is an instance of a kernel that will be paired with a specific set of references (the parameters). Nodes are created from and associated with a single graph only. When a vx_parameter is extracted from a Node, an additional attribute can be accessed:
  • Object: Graph - A set of nodes connected in a directed (only goes one-way) acyclic (does not loop back) fashion. A Graph may have sets of Nodes that are unconnected to other sets of Nodes within the same Graph. See Graph Formalisms.

OpenVX Data Objects

Data objects are object that are processed by graphs in nodes.

Error Objects

Error objects are specialized objects that may be returned from other object creator functions when serious platform issue occur (i.e., out of memory or out of handles). These can be checked at the time of creation of these objects, but checking also may be put-off until usage in other APIs or verification time, in which case, the implementation must return appropriate errors to indicate that an invalid object type was used.

vx_<object> obj = vxCreate<Object>(context, ...);
if (status == VX_SUCCESS) {
// object is good
}

Graphs Concepts

The graph is the central computation concept of OpenVX. The purpose of using graphs to express the Computer Vision problem is to allow for the possibility of any implementation to maximize its optimization potential because all the operations of the graph and its dependencies are known ahead of time, before the graph is processed.

Graphs are composed of one or more nodes that are added to the graph through node creation functions. Graphs in OpenVX must be created ahead of processing time and verified by the implementation, after which they can be processed as many times as needed.

Linking Nodes

Graph Nodes are linked together via data dependencies with no explicitly-stated ordering. The same reference may be linked to other nodes. Linking has a limitation, however, in that only one node in a graph may output to any specific data object reference. That is, only a single writer of an object may exist in a given graph. This prevents indeterminate ordering from data dependencies. All writers in a graph shall produce output data before any reader of that data accesses it.

Virtual Data Objects

Graphs in OpenVX depend on data objects to link together nodes. When clients of OpenVX know that they do not need access to these intermediate data objects, they may be created as virtual. Virtual data objects can be used in the same manner as non-virtual data objects to link nodes of a graph together; however, virtual data objects are different in the following respects.

  • Inaccessible - No calls to an Map/Unmap or Copy APIs shall succeed given a reference to an object created through a virtual create function from a Graph external perspective. Calls to Map/Unmap or Copy APIs from within client-defined node that belongs to the same graph as the virtual object will succeed as they are Graph internal.
  • Scoped - Virtual data objects are scoped within the Graph in which they are created; they cannot be shared outside their scope. The live range of the data content of a virtual data object is limited to a single graph execution. In other word, data content of a virtual object is undefined before graph execution and no data of a virtual object should be expected to be preserved across successive graph executions by the application.
  • Intermediates - Virtual data objects should be used only for intermediate operations within Graphs, because they are fundamentally inaccessible to clients of the API.
  • Dimensionless or Formatless - Virtual data objects may have dimensions and formats partially or fully undefined at creation time. For instance, a virtual image can be created with undefined or partially defined dimensions (0x0, Nx0 or 0xN where N is not null) and/or without defined format (VX_DF_IMAGE_VIRT). The undefined property of the virtual object at creation time is undefined with regard to the graph and mutable at graph verification time; it will be automatically adjusted at each graph verification, deduced from the node that outputs the virtual object. Dimensions and format properties that are well defined at virtual object creation time are immutable and can't be adjusted automatically at graph verification time.
  • Attributes - Even if a given Virtual data object does not have its dimensionality or format completely defined, these attributes may still be queried. If queried before the object participates in a graph verification, the attribute value returned is what the user provided (e.g., "0" for the dimension). If queried after graph verification (or re-verification), the attribute value returned will be the value determined by the graph verification rules.
  • The Dimensionless or Formatless aspect of virtual data is a commodity that allows creating graphs generic with regard to dimensions or format, but there are restrictions:
    1. Nodes may require the dimensions and/or the format to be defined for a virtual output object when it can't be deduced from its other parameters. For example, a Scale node requires well defined dimensions for the output image, while ColorConvert and ChannelCombine nodes require a well defined format for the output image.
    2. An image created from ROI must always be well defined (vx_rectangle_t parameter) and can't be created from a dimensionless virtual image.
    3. A ROI of a formatless virtual image shouldn't be a node output.
    4. A tensor created from View must always be well defined and can't be created from a dimensionless virtual tensor.
    5. A view of a formatless virtual tensor shouldn't be a node output.
    6. Levels of a dimensionless or formatless virtual pyramid shouldn't be a node output.
  • Inheritance - A sub-object inherits from the virtual property of its parent. A sub-object also inherits from the Dimensionless or Formatless property of its parent with restrictions:
    1. it is adjusted automatically at graph verification when the parent properties are adjusted (the parent is the output of a node)
    2. it can't be adjusted at graph verification when the sub-object is itself the output of a node.
  • Optimizations - Virtual data objects do not have to be created during Graph validation and execution and therefore may be of zero size.

These restrictions enable vendors the ability to optimize some aspects of the data object or its usage. Some vendors may not allocate such objects, some may create intermediate sub-objects of the object, and some may allocate the object on remote, inaccessible memories. OpenVX does not proscribe which optimization the vendor does, merely that it may happen.

Node Parameters

Parameters to node creation functions are defined as either atomic types, such as vx_int32, vx_enum, or as objects, such as vx_scalar, vx_image. The atomic variables of the Node creation functions shall be converted by the framework into vx_scalar references for use by the Nodes. A node parameter of type vx_scalar can be changed during the graph execution; whereas, a node parameter of an atomic type (vx_int32 etc.) require at least a graph revalidation if changed. All node parameter objects may be modified by retrieving the reference to the vx_parameter via vxGetParameterByIndex, and then passing that to vxQueryParameter to retrieve the reference to the object.

vxQueryParameter(param, VX_PARAMETER_REF, &ref, sizeof(ref));

If the type of the parameter is unknown, it may be retrieved with the same function.

vx_enum type;
vxQueryParameter(param, VX_PARAMETER_TYPE, &type, sizeof(type));
/* cast the ref to the correct vx_<type>. Atomics are now vx_scalar */

Graph Parameters

Parameters may exist on Graphs, as well. These parameters are defined by the author of the Graph and each Graph parameter is defined as a specific parameter from a Node within the Graph using vxAddParameterToGraph. Graph parameters communicate to the implementation that there are specific Node parameters that may be modified by the client between Graph executions. Additionally, they are parameters that the client may set without the reference to the Node but with the reference to the Graph using vxSetGraphParameterByIndex. This allows for the Graph authors to construct Graph Factories. How these factories work falls outside the scope of this document.

See also
Framework: Graph Parameters

Execution Model

Graphs must execute in both:

Asynchronous Mode

In asynchronous mode, Graphs must be single-issue-per-reference. This means that given a constructed graph reference \(G\), it may be scheduled multiple times but only executes sequentially with respect to itself. Multiple graphs references given to the asynchronous graph interface do not have a defined behavior and may execute in parallel or in series based on the behavior or the vendor's implementation.

Graph Formalisms

To use graphs several rules must be put in place to allow deterministic execution of Graphs. The behavior of a processGraph( \(G\)) call is determined by the structure of the Processing Graph \(G\). The Processing Graph is a bipartite graph consisting of a set of Nodes \(N_1 \ldots N_n\) and a set of data objects \(d_1 \ldots d_i\). Each edge ( \(N_x\), \(D_y\)) in the graph represents a data object \(D_y\) that is written by Node \(N_x\) and each edge ( \(D_x\), \(N_y\)) represents a data object \(D_x\) that is read by Node \(N_y\). Each edge \(e\) has a name Name( \(e\)), which gives the parameter name of the node that references the corresponding data object. Each Node Parameter also has a type Type(node, name) in {INPUT, OUTPUT, INOUT}. Some data objects are Virtual, and some data objects are Delay. Delay data objects are just collections of data objects with indexing (like an image list) and known linking points in a graph. A node may be classified as a head node, which has no backward dependency. Alternatively, a node may be a dependent node, which has a backward dependency to the head node. In addition, the Processing Graph has several restrictions:

  1. Output typing - Every output edge ( \(N_x\), \(D_y\)) requires Type( \(N_x\), Name( \(N_x\), \(D_y\))) in {OUTPUT, INOUT}
  2. Input typing - Every input edge ( \(N_x\), \(D_y\)) requires Type( \(N_y\), Name( \(D_x\), \(N_y\))) in {INPUT} or {INOUT}
  3. Single Writer - Every data object is the target of at most one output edge.
  4. Broken Cycles - Every cycle in \(G\) must contain at least input edge ( \(D_x\), \(N_y\)) where \(D_x\) is Delay.
  5. Virtual images must have a source - If \(D_y\) is Virtual, then there is at least one output edge that writes \(D_y\) ( \(N_x\), \(D_y\))
  6. Bidirectional data objects shall not be virtual - If Type( \(N_x\), Name( \(N_x\), \(D_y\))) is INOUT implies \(D_y\) is non-Virtual.
  7. Delay data objects shall not be virtual - If \(D_x\) is Delay then it shall not be Virtual.
  8. A uniform image cannot be output or bidirectional.

The execution of each node in a graph consists of an atomic operation (sometimes referred to as firing) that consumes data representing each input data object, processes it, and produces data representing each output data object. A node may execute when all of its input edges are marked present. Before the graph executes, the following initial marking is used:

  • All input edges ( \(D_x\), \(N_y\)) from non-Virtual objects Dx are marked (parameters must be set).
  • All input edges ( \(D_x\), \(N_y\)) with an output edge ( \(N_z\), \(D_x\)) are unmarked.
  • All input edges ( \(D_x\), \(N_y\)) where \(D_x\) is a Delay data object are marked.


Processing a node results in unmarking all the corresponding input edges and marking all its output edges; marking an output edge ( \(N_x\), \(D_y\)) where \(D_y\) is not a Delay results in marking all of the input edges ( \(D_y\), \(N_z\)). Following these rules, it is possible to statically schedule the nodes in a graph as follows: Construct a precedence graph \(P\), including all the nodes \(N_1 \ldots N_x\), and an edge ( \(N_x\), \(N_z\)) for every pair of edges ( \(N_x\), \(D_y\)) and ( \(D_y\), \(N_z\)) where \(D_y\) is not a Delay. Then unconditionally fire each node according to any topological sort of \(P\).

The following assertions should be verified:

  • \(P\) is a Directed Acyclic Graph (DAG), implied by 4 and the way it is constructed.
  • Every data object has a value when it is executed, implied by 5, 6, 7, and the marking.
  • Execution is deterministic if the nodes are deterministic, implied by 3, 4, and the marking.
  • Every node completes its execution exactly once.

The execution model described here just acts as a formalism. For example, independent processing is allowed across multiple depended and depending nodes and edges, provided that the result is invariant with the execution model described here.

Contained & Overlapping Data Objects

There are cases in which two different data objects referenced by an output parameter of node \(N_1\) and input parameter of node \(N_2\) in a graph induce a dependency between these two nodes: For example, a pyramid and its level images, image and the sub-images created from it by vxCreateImageFromROI or vxCreateImageFromChannel, or overlapping sub-images of the same image. Following figure show examples of this dependency. To simplify subsequent definitions and requirements a limitation is imposed that if a sub-image I' has been created from image I and sub-image I'' has been created from I', then I'' is still considered a sub-image of I and not of I'. In these cases it is expected that although the two nodes reference two different data objects, any change to one data object might be reflected in the other one. Therefore it implies that \(N_1\) comes before \(N_2\) in the graph's topological order. To ensure that, following definitions are introduced.

Overlap_DataObj_Pyramid.png
Overlap_DataObj_Image.png


  1. Containment Set - C(d), the set of recursively contained data objects of d, named Containment Set, is defined as follows:
    • \(C_0\)(d)={d}
    • \(C_1\)(d) is the set of all data objects that are directly contained by d:
      1. If d is an image, all images created from an ROI or channel of d are directly contained by d.
      2. If d is a pyramid, all pyramid levels of d are directly contained by d.
      3. If d is an object array, all elements of d are directly contained by d.
      4. If d is a delay object, all slots of d are directly contained by d.
    • For i>1, \(C_i\)(d) is the set of all data objects that are contained by d at the ith order

      \begin{eqnarray} C_i(d)=\bigcup_{d'\in{C_{i-1}(d)}}C_1(d') \end{eqnarray}

    • C(d) is the set that contains d itself, the data objects contained by d, the data objects that are contained by the data objects contained by d and so on. Formally:

      \begin{eqnarray} C(d)=\bigcup_{i=0}^{\infty}C_i(d) \end{eqnarray}

  2. I(d) is a predicate that equals true if and only if d is an image.
  3. Overlapping Relationship - The overlapping relation \(R_{ov}\) is a relation defined for images, such that if \(i_1\) and \(i_2\) in C(i), i being an image, then \(i_1\) \(R_{ov}\) \(i_2\) is true if and only if \(i_1\) and \(i_2\) overlap, i.e there exists a point (x,y) of i that is contained in both \(i_1\) and \(i_2\) . Note that this relation is reflexive and symmetric, but not transitive: \(i_1\) overlaps \(i_2\) and \(i_2\) overlaps \(i_3\) does not necessarily imply that \(i_1\) overlaps \(i_3\), as illustrated in the following figure:
    Overlap_DataObj_Overlap.png
  4. Dependency Relationship - The dependency relationship \(N_1\) -> \(N_2\), is a relation defined for nodes. \(N_1\) -> \(N_2\) means that \(N_2\) depends on \(N_1\) and then implies that \(N_2\) must be executed after the completion of \(N_1\).
  5. \(N_1\) -> \(N_2\) if \(N_1\) writes to a data object \(d_1\) and \(N_2\) reads from a data object \(d_2\) and:

    \begin{eqnarray} d_1\in{C(d_2)}\ or\ d_2\in{C(d_1)}\ or\ (I(d_1)\ and\ I(d_2)\ and\ d_1R_{ov}d_2) \end{eqnarray}

If data object \(D_y\) of an output edge ( \(N_x\), \(D_y\)) overlaps with a data object \(D_z\) then the result is implementation defined.

Node Execution Independence

In the following example a client computes the gradient magnitude and gradient phase from a blurred input image. The vxMagnitudeNode and vxPhaseNode are independently computed, in that each does not depend on the output of the other. OpenVX does not mandate that they are run simultaneously or in parallel, but it could be implemented this way by the OpenVX vendor.

dot_simple_independent.png
A simple graph with some independent nodes.

The code to construct such a graph can be seen below.

vx_image images[] = {
vxCreateImage(context, 640, 480, VX_DF_IMAGE_UYVY),
vxCreateImage(context, 640, 480, VX_DF_IMAGE_S16),
vxCreateImage(context, 640, 480, VX_DF_IMAGE_U8),
};
vx_graph graph = vxCreateGraph(context);
vx_image virts[] = {
};
vxChannelExtractNode(graph, images[0], VX_CHANNEL_Y, virts[0]),
vxGaussian3x3Node(graph, virts[0], virts[1]),
vxSobel3x3Node(graph, virts[1], virts[2], virts[3]),
vxMagnitudeNode(graph, virts[2], virts[3], images[1]),
vxPhaseNode(graph, virts[2], virts[3], images[2]),
status = vxVerifyGraph(graph);
if (status == VX_SUCCESS)
{
status = vxProcessGraph(graph);
}
vxReleaseContext(&context); /* this will release everything */

Verification

Graphs within OpenVX must go through a rigorous validation process before execution to satisfy the design concept of eliminating run-time overhead (parameter checking) that guarantees safe execution of the graph. OpenVX must check for (but is not limited to) these conditions:

Parameters To Nodes:

  • Each required parameter is given to the node (vx_parameter_state_e). Optional parameters may not be present and therefore are not checked when absent. If present, they are checked.
  • Each parameter given to a node must be of the right direction (a value from vx_direction_e).
  • Each parameter given to a node must be of the right object type (from the object range of vx_type_e).
  • Each parameter attribute or value must be verified. In the case of a scalar value, it may need to be range checked (e.g., \( 0.5 <= k <= 1.0 \)). The implementation is not required to do run-time range checking of scalar values. If the value of the scalar changes at run time to go outside the range, the results are undefined. The rationale is that the potential performance hit for run-time range checking is too large to be enforced. It will still be checked at graph verification time as a time-zero sanity check. If the scalar is an output parameter of another node, it must be initialized to a legal value. In the case of vxScaleImageNode, the relation of the input image dimensions to the output image dimensions determines the scaling factor. These values or attributes of data objects must be checked for compatibility on each platform.
  • Graph Connectivity - the vx_graph must be a Directed Acyclic Graph (DAG). No cycles or feedback is allowed. The vx_delay object has been designed to explicitly address feedback between Graph executions.
  • Resolution of Virtual Data Objects - Any changes to Virtual data objects from unspecified to specific format or dimensions, as well as the related creation of objects of specific type that are observable at processing time, takes place at Verification time.

The implementation must check that all node parameters are the correct type at node creation time, unless the parameter value is set to NULL. Additional checks may also be made on non-NULL parameters. The user must be allowed to set parameters to NULL at node creation time, even if they are required parameters, in order to create "exemplar" nodes that are not used in graph execution, or to create nodes incrementally. Therefore the implementation must not generate an error at node creation time for parameters that are explicitly set to NULL. However, the implementation must check that all required parameters are non-NULL and the correct type during vxVerifyGraph. Other more complex checks may also be done during vxVerifyGraph. The implementation should provide specific error reporting of NULL parameters during vxVerifyGraph, e.g., "Parameter<parameter> of Node<node> is NULL."

Callbacks

Callbacks are a method to control graph flow and to make decisions based on completed work. The vxAssignNodeCallback call takes as a parameter a callback function. This function will be called after the execution of the particular node, but prior to the completion of the graph. If nodes are arranged into independent sets, the order of the callbacks is unspecified. Nodes that are arranged in a serial fashion due to data dependencies perform callbacks in order. The callback function may use the node reference first to extract parameters from the node, and then extract the data references. Data outputs of Nodes with callbacks shall be available (via Map/Unmap/Copy methods) when the callback is called.

User Kernels

OpenVX supports the concept of client-defined functions that shall be executed as Nodes from inside the Graph or are Graph internal. The purpose of this paradigm is to:

  • Further exploit independent operation of nodes within the OpenVX platform.
  • Allow componentized functions to be reused elsewhere in OpenVX.
  • Formalize strict verification requirements (i.e., Contract Programming).

dot_user_nodes.png
A graph with User Kernel nodes which are independent of the “base” nodes.

In this example, to execute client-supplied functions, the graph does not have to be halted and then resumed. These nodes shall be executed in an independent fashion with respect to independent base nodes within OpenVX. This allows implementations to further minimize execution time if hardware to exploit this property exists.

Parameter Validation

User Kernels must aid in the Graph Verification effort by providing an explicit validation function for each vision function they implement. Each parameter passed to the instanced Node of a User Kernel is validated using the client-supplied validation function. The client must check these attributes and/or values of each parameter:

  • Each attribute or value of the parameter must be checked. For example, the size of array, or the value of a scalar to be within a range, or a dimensionality constraint of an image such as width divisibility. (Some implementations may have restrictions, such as an image width be evenly divisible by some fixed number).
  • If the output parameters depend on attributes or values from input parameters, those relationships must be checked.

The Meta Format Object

The Meta Format Object is an opaque object used to collect requirements about the output parameter, which then the OpenVX implementation will check. The Client must manually set relevant object attributes to be checked against output parameters, such as dimensionality, format, scaling, etc.

User Kernels Naming Conventions

User Kernels must be exported with a unique name (see Naming Conventions for information on OpenVX conventions) and a unique enumeration. Clients of OpenVX may use either the name or enumeration to retrieve a kernel, so collisions due to non-unique names will cause problems. The kernel enumerations may be extended by following this example:

#define VX_KERNEL_NAME_KHR_XYZ "org.khronos.example.xyz"
#define VX_LIBRARY_XYZ (0x3) // assigned from Khronos, vendors control their own
enum vx_kernel_xyz_ext_e {
VX_KERNEL_KHR_XYZ = VX_KERNEL_BASE(VX_ID_DEFAULT, VX_LIBRARY_XYZ) + 0x0,
// up to 0xFFF kernel enums can be created.
};

Each vendor of a vision function or an implementation must apply to Khronos to get a unique identifier (up to a limit of \( 2^{12}-1 \) vendors). Until they obtain a unique ID vendors must use VX_ID_DEFAULT.

To construct a kernel enumeration, a vendor must have both their ID and a library ID. The library ID's are completely vendor defined (however when using the VX_ID_DEFAULT ID, many libraries may collide in namespace).

Once both are defined, a kernel enumeration may be constructed using the VX_KERNEL_BASE macro and an offset. (The offset is optional, but very helpful for long enumerations.)

Immediate Mode Functions

OpenVX also contains an interface defined within <VX/vxu.h> that allows for immediate execution of vision functions. These interfaces are prefixed with vxu to distinguish them from the Node interfaces, which are of the form vx<Name>Node. Each of these interfaces replicates a Node interface with some exceptions. Immediate mode functions are defined to behave as Single Node Graphs, which have no leaking side-effects (e.g., no Log entries) within the Graph Framework after the function returns. The following tables refer to both the Immediate Mode and Graph Mode vision functions. The Module documentation for each vision function draws a distinction on each API by noting that it is either an immediate mode function with the tag [Immediate] or it is a Graph mode function by the tag [Graph].

Targets

A 'Target' specifies a physical or logical devices where a node or an immediate mode function is executed. This allows the use of different implementations of vision functions on different targets. The existence of allowed Targets is exposed to the applications by the use of defined APIs. The choice of a Target allows for different levels of control on where the nodes can be executed. An OpenVX implementation must support at least one target. Additional supported targets are specified using the appropriate enumerations. See vxSetNodeTarget, vxSetImmediateModeTarget, and vx_target_e. An OpenVX implementation must support at least one target VX_TARGET_ANY as well as VX_TARGET_STRING enumerates. An OpenVX implementation may also support more than these two to indicate the use of specific devices. For example, an implementation may add VX_TARGET_CPU and VX_TARGET_GPU enumerates to indicate the support of two possible targets to assign a nodes to (or to excute an immediate mode function). Another way an implementation can indicate the existence of multiple targets, for example CPU and GPU, is by specifying the target as VX_TARGET_STRING and using strings 'CPU' and 'GPU'. Thus defining targets using names rather than enumerates. The specific naming of string or enumerates is not enforced by the specification and it is up to the vendors to document and communicate the Target naming. Once available in a given implementation Applications can assign a Target to a node to specify the target that must execute that node by using the API vxSetNodeTarget. For immediate mode functions the target specifies the physical or logical device where the future execution of that function will be attempted. When an immediate mode function is not supported on the selected target the execution falls back to VX_TARGET_ANY.

Base Vision Functions

OpenVX comes with a standard or base set of vision functions. The following table lists the supported set of vision functions, their input types (first table) and output types (second table), and the version of OpenVX in which they are supported.

Inputs

Vision Function S8 U8 U16 S16 U32 F32 colorother
AbsDiff 1.0 1.0.1
Accumulate 1.0
AccumulateSquared 1.0
AccumulateWeighted 1.0
Add 1.0 1.0
And 1.0
BilateralFilter 1.2 1.2
Box3x3 1.0
CannyEdgeDetector 1.0
ChannelCombine 1.0
ChannelExtract 1.0
ColorConvert 1.0
ConvertDepth 1.0 1.0
Convolve 1.0
Data Object Copy 1.2
Dilate3x3 1.0
EqualizeHistogram 1.0
Erode3x3 1.0
FastCorners 1.0
Gaussian3x3 1.0
GaussianPyramid 1.1
HarrisCorners 1.0
HalfScaleGaussian 1.0
Histogram 1.0
HOGCells 1.2
HOGFeatures 1.2
HoughLinesP 1.2
IntegralImage 1.0
LaplacianPyramid 1.1
LaplacianReconstruct 1.1
LBP 1.2
Magnitude 1.0
MatchTemplate 1.2
MeanStdDev 1.0
Median3x3 1.0
Max 1.2 1.2
Min 1.2 1.2
MinMaxLoc 1.0 1.0
Multiply 1.0 1.0
NonLinearFilter 1.1
NonMaximaSuppression 1.2 1.2
Not 1.0
OpticalFlowPyrLK 1.0
Or 1.0
Phase 1.0
GaussianPyramid 1.0
Remap 1.0
ScaleImage 1.0
Sobel3x3 1.0
Subtract 1.0 1.0
TableLookup 1.0 1.1
TensorMultiply 1.2 1.2 1.2
TensorAdd 1.2 1.2 1.2
TensorSubtract 1.2 1.2 1.2
TensorMatrixMultiply 1.2 1.2 1.2
TensorTableLookup 1.2 1.2 1.2
TensorTranspose 1.2 1.2 1.2
Threshold 1.0
WarpAffine 1.0
WarpPerspective 1.0
Xor 1.0

Outputs

Vision Function S8 U8 U16 S16 U32 F32 colorother
AbsDiff 1.0 1.0.1
Accumulate 1.0
AccumulateSquared 1.0
AccumulateWeighted 1.0
Add 1.0 1.0
And 1.0
BilateralFilter 1.2 1.2
Box3x3 1.0
CannyEdgeDetector 1.0
ChannelCombine 1.0
ChannelExtract 1.0
ColorConvert 1.0
ConvertDepth 1.0 1.0
Convolve 1.0 1.0
Data Object Copy 1.2
Dilate3x3 1.0
EqualizeHistogram 1.0
Erode3x3 1.0
FastCorners 1.0
Gaussian3x3 1.0
GaussianPyramid 1.1
HarrisCorners 1.0
HalfScaleGaussian 1.0
Histogram 1.0
HOGCells 1.2 1.2
HOGFeatures 1.2 1.2
HoughLinesP 1.2
IntegralImage 1.0
LaplacianPyramid 1.1
LaplacianReconstruct 1.1
LBP 1.2
Magnitude 1.0
MatchTemplate 1.2
MeanStdDev 1.0
Median3x3 1.0
Max 1.2 1.2
Min 1.2 1.2
MinMaxLoc 1.0 1.0 1.0
Multiply 1.0 1.0
NonLinearFilter 1.1
NonMaximaSuppression 1.2 1.2
Not 1.0
OpticalFlowPyrLK
Or 1.0
Phase 1.0
GaussianPyramid 1.0
Remap 1.0
ScaleImage 1.0
Sobel3x3 1.0
Subtract 1.0 1.0
TableLookup 1.0 1.1
TensorMultiply 1.2 1.2 1.2
TensorAdd 1.2 1.2 1.2
TensorSubtract 1.2 1.2 1.2
TensorMatrixMultiply 1.2 1.2 1.2
TensorTableLookup 1.2 1.2 1.2
TensorTranspose 1.2 1.2 1.2
Threshold 1.0
WarpAffine 1.0
WarpPerspective 1.0
Xor 1.0

Parameter ordering convention

For vision functions, the input and output parameter ordering convention is:

  1. Mandatory inputs
  2. Optional inputs
  3. Mandatory in/outs
  4. Optional in/outs
  5. Mandatory outputs
  6. Optional outputs

The known exceptions are:

vxConvertDepthNode,

vxuConvertDepth,

vxOpticalFlowPyrLKNode,

vxuOpticalFlowPyrLK,

vxScaleImageNode,

vxuScaleImage.

Lifecycles

OpenVX Context Lifecycle

The lifecycle of the context is very simple.

msc_system
The lifecycle model for an OpenVX Context.

Graph Lifecycle

OpenVX has four main phases of graph lifecycle:

  • Construction - Graphs are created via vxCreateGraph, and Nodes are connected together by data objects.
  • Verification - The graphs are checked for consistency, correctness, and other conditions. Memory allocation may occur.
  • Execution - The graphs are executed via vxProcessGraph or vxScheduleGraph. Between executions data may be updated by the client or some other external mechanism. The client of OpenVX may change reference of input data to a graph, but this may require the graph to be validated again by checking vxIsGraphVerified.
  • Deconstruction - Graphs are released via vxReleaseGraph. All Nodes in the Graph are released.

dot_phases.png
Graph Lifecycle

Data Object Lifecycle

All objects in OpenVX follow a similar lifecycle model. All objects are

  • Created via vxCreate<Object><Method> or retreived via vxGet<Object><Method> from the parent object if they are internally created.
  • Used within Graphs or immediate functions as needed.
  • Then objects must be released via vxRelease<Object> or via vxReleaseContext when all objects are released.

OpenVX Image Lifecycle

This is an example of the Image Lifecycle using the OpenVX Framework API. This would also apply to other data types with changes to the types and function names.

msc_image
Image Object Lifecycle

Host Memory Data Object Access Patterns

For objects retrieved from OpenVX that are 2D in nature, such as vx_image, vx_matrix, and vx_convolution, the manner in which the host-side has access to these memory regions is well-defined. OpenVX uses a row-major storage (that is each unit in a column is memory-adjacent to its row adjacent unit). Two-dimensional objects are always created (using vxCreateImage or vxCreateMatrix) in width (columns) by height (rows) notation, with the arguments in that order. When accessing these structures in “C” with two-dimensional arrays of declared size, the user must therefore provide the array dimensions in the reverse of the order of the arguments to the Create function. This layout ensures row-wise storage in C on the host. A pointer could also be allocated for the matrix data and would have to be indexed in this row-major method.

Matrix Access Example

const vx_size columns = 3;
const vx_size rows = 4;
vx_matrix matrix = vxCreateMatrix(context, VX_TYPE_FLOAT32, columns, rows);
vx_status status = vxGetStatus((vx_reference)matrix);
if (status == VX_SUCCESS)
{
vx_int32 j, i;
#if defined(OPENVX_USE_C99)
vx_float32 mat[rows][columns]; /* note: row major */
#else
vx_float32 *mat = (vx_float32 *)malloc(rows*columns*sizeof(vx_float32));
#endif
for (j = 0; j < (vx_int32)rows; j++)
for (i = 0; i < (vx_int32)columns; i++)
#if defined(OPENVX_USE_C99)
mat[j][i] = (vx_float32)rand()/(vx_float32)RAND_MAX;
#else
mat[j*columns + i] = (vx_float32)rand()/(vx_float32)RAND_MAX;
#endif
}
#if !defined(OPENVX_USE_C99)
free(mat);
#endif
}

Image Access Example

Images and Array differ slightly in how they are accessed due to more complex memory layout requirements.

void *base_ptr = NULL;
vx_uint32 width = 640, height = 480, plane = 0;
vx_image image = vxCreateImage(context, width, height, VX_DF_IMAGE_U8);
vx_map_id map_id;
rect.start_x = rect.start_y = 0;
rect.end_x = rect.end_y = PATCH_DIM;
status = vxMapImagePatch(image, &rect, plane, &map_id,
&addr, &base_ptr,
if (status == VX_SUCCESS)
{
vx_uint32 x,y,i,j;
vx_uint8 pixel = 0;
/* a couple addressing options */
/* use linear addressing function/macro */
for (i = 0; i < addr.dim_x*addr.dim_y; i++) {
i, &addr);
*ptr2 = pixel;
}
/* 2d addressing option */
for (y = 0; y < addr.dim_y; y+=addr.step_y) {
for (x = 0; x < addr.dim_x; x+=addr.step_x) {
x, y, &addr);
*ptr2 = pixel;
}
}
/* direct addressing by client
* for subsampled planes, scale will change
*/
for (y = 0; y < addr.dim_y; y+=addr.step_y) {
for (x = 0; x < addr.dim_x; x+=addr.step_x) {
vx_uint8 *tmp = (vx_uint8 *)base_ptr;
i = ((addr.stride_y*y*addr.scale_y) /
((addr.stride_x*x*addr.scale_x) /
tmp[i] = pixel;
}
}
/* more efficient direct addressing by client.
* for subsampled planes, scale will change.
*/
for (y = 0; y < addr.dim_y; y+=addr.step_y) {
j = (addr.stride_y*y*addr.scale_y)/VX_SCALE_UNITY;
for (x = 0; x < addr.dim_x; x+=addr.step_x) {
vx_uint8 *tmp = (vx_uint8 *)base_ptr;
i = j + (addr.stride_x*x*addr.scale_x) /
tmp[i] = pixel;
}
}
/* this commits the data back to the image.
*/
status = vxUnmapImagePatch(image, map_id);
}
vxReleaseImage(&image);

Array Access Example

Arrays only require a single value, the stride, instead of the entire addressing structure that images need.

vx_size i, stride = sizeof(vx_size);
void *base = NULL;
vx_map_id map_id;
/* access entire array at once */
vxMapArrayRange(array, 0, num_items, &map_id, &stride, &base, VX_READ_AND_WRITE, VX_MEMORY_TYPE_HOST, 0);
for (i = 0; i < num_items; i++)
{
vxArrayItem(mystruct, base, i, stride).some_uint += i;
vxArrayItem(mystruct, base, i, stride).some_double = 3.14f;
}
vxUnmapArrayRange(array, map_id);

Map/Unmap pairs can also be called on individual elements of array using a method similar to this:

/* access each array item individually */
for (i = 0; i < num_items; i++)
{
mystruct *myptr = NULL;
vxMapArrayRange(array, i, i+1, &map_id, &stride, (void **)&myptr, VX_READ_AND_WRITE, VX_MEMORY_TYPE_HOST, 0);
myptr->some_uint += 1;
myptr->some_double = 3.14f;
vxUnmapArrayRange(array, map_id);
}

Concurrent Data Object Access

Accessing OpenVX data-objects using the functions Map, Copy, Read concurrently to an execution of a graph that is accessing the same data objects is permitted only if all accesses are read-only. That is, for Map, Copy to have a read-only access mode and for nodes in the graph to have that data-object as an input parameter only. In all other cases, including write or read-write modes and Write access function, as well as a graph nodes having the data-object as output or bidirectional, the application must guarantee that the access is not performed concurrently with the graph execution. That can be achieved by calling un-map following a map before calling vxScheduleGraph or vxProcessGraph. In addition, the application must call vxWaitGraph after vxScheduleGraph before calling Map, Read, Write or Copy to avoid restricted concurrent access. An application that fails to follow the above might encounter an undefined behavior and/or data loss without being notified by the OpenVX framework. Accessing images created from ROI (vxCreateImageFromROI) or created from a channel (vxCreateImageFromChannel) must be treated as if the entire image is being accessed.

  • Setting an attribute is considered as writing to a data object in this respect.
  • For concurrent execution of several graphs please see Execution Model
  • Also see the graph formalism section for guidance on accessing ROIs of the same image within a graph.

Valid Image Region

The valid region mechanism informs the application as to which pixels of the output images of a graph's execution have valid values (see valid pixel definition below). The mechanism also applies to immediate mode (VXU) calls, and supports the communication of the valid region between different graph executions. Some vision functions, mainly those providing statistics and summarization of image information, use the valid region to ignore pixels that are not valid on their inputs (potentially bad or unstable pixel values). A good example of such a function is Min/Max Location. Formalization of the valid region mechanism is given below.

  • Valid Pixels - All output pixels of an OpenVX function are considered valid by default, unless their calculation depends on input pixels that are not valid. An input pixel is not valid in one of two situations:
    1. The pixel is outside of the image border and the border mode in use is VX_BORDER_UNDEFINED
    2. The pixel is outside the valid region of the input image.
  • Valid Region - The region in the image that contains all the valid pixels. Theoretically this can be of any shape. OpenVX currently only supports rectangular valid regions. In subsequent text the term 'valid rectangle' denotes a valid region that is rectangular in shape.
  • Valid Rectangle Reset - In some cases it is not possible to calculate a valid rectangle for the output image of a vision function (for example, warps and remap). In such cases, the vision function is said to reset the valid Region to the entire image. The attribute VX_NODE_VALID_RECT_RESET is a read only attribute and is used to communicate valid rectangle reset behavior to the application. When it is set to vx_true_e for a given node the valid rectangle of the output images will reset to the full image upon execution of the node, when it is set to vx_false_e the valid rectangle will be calculated. All standard OpenVX functions will have this attribute set to vx_false_e by default, except for Warp and Remap where it will be set to vx_true_e.
  • Valid Rectangle Initialization - Upon the creation of an image, its valid rectangle is the entire image. One exception to this is when creating an image via vxCreateImageFromROI; in that case, the valid region of the ROI image is the subset of the valid region of the parent image that is within the ROI. In other words, the valid region of an image created using an ROI is the largest rectangle that contains valid pixels in the parent image.
  • Valid Rectangle Calculation - The valid rectangle of an image changes as part of the graph execution, the correct value is guaranteed only when the execution finishes. The valid rectangle of an image remains unchanged between graph executions and persists between graph executions as long as the application doesn't explicitly change the valid region via vxSetImageValidRectangle. Notice that using vxMapImagePatch, vxUnmapImagePatch or vxSwapImageHandle does not change the valid region of an image. If a non-UNDEFINED border mode is used on an image where the valid region is not the full image, the results at the border and resulting size of the valid region are implementation-dependent. This case can occur when mixing UNDEFINED and other border mode, which is not recommended.
  • Valid Rectangle for Immediate mode (VXU) - VXU is considered a single node graph execution, thus the valid rectangle of an output of VXU will be propagated for an input to a consequent VXU call (when using the same output image from one call as input to the consecutive call).
  • Valid Region Usage - For all standard OpenVX functions, the framework must guarantee that all pixel values inside the valid rectangle of the output images are valid. The framework does not guarantee that input pixels outside of the valid rectangle are processed. For the following vision functions, the framework guarantees that pixels outside of the valid rectangle do not participate in calculating the vision function result: Equalize Histogram, Integral Image, Fast Corners, Histogram, Mean and Standard Deviation, Min Max Location, Optical Flow Pyramid (LK) and Canny Edge Detector. An application can get the valid rectangle of an image by using vxGetValidRegionImage.
  • User kernels - User kernels may change the valid rectangles of their output images. To change the valid rectangle, the programmer of the user kernel must provide a call-back function that sets the valid rectangle. The output validator of the user kernel must provide this callback by setting the value of the vx_meta_format attribute VX_VALID_RECT_CALLBACK during the output validator. The callback function must be callable by the OpenVX framework during graph validation and execution. Assumptions must not be made regarding the order and the frequency by which the valid rectangle callback is called. The framework will recalculate the valid region when a change in the input valid regions is detected. For user nodes, the default value of VX_NODE_VALID_RECT_RESET is vx_true_e. Setting VX_VALID_RECT_CALLBACK during parameter validation to a value other than NULL will result in setting VX_NODE_VALID_RECT_RESET to vx_false_e. Note: the above means that when VX_VALID_RECT_CALLBACK is not set or set to NULL the user-node will reset the valid rectangle to the entire image.
  • In addition, valid rectangle reset occurs in the following scenarios:
    1. A reset of the valid rectangle of a parent image when a node writes to one of its ROIs. The only case where the reset does not occur is when the child ROI image is identical to the parent image.
    2. For nodes that have the VX_NODE_VALID_RECT_RESET set to vx_true_e

Extending OpenVX

Beyond User Kernels there are other mechanisms for vendors to extend features in OpenVX. These mechanisms are not available to User Kernels. Each OpenVX official extension has a unique identifier, comprised of capital letters, numbers and the underscore character, prefixed with "KHR_", for example "KHR_NEW_FEATURE".

Extending Attributes

When extending attributes, vendors must use their assigned ID from vx_vendor_id_e in conjunction with the appropriate macros for creating new attributes with VX_ATTRIBUTE_BASE. The typical mechanism to extend a new attribute for some object type (for example a vx_node attribute from VX_ID_TI) would look like this:

enum {
VX_NODE_TI_NEWTHING = VX_ATTRIBUTE_BASE(VX_ID_TI, VX_TYPE_NODE) + 0x0,
}

Vendor Custom Kernels

Vendors wanting to add more kernels to the base set supplied to OpenVX should provide a header of the form

#include <VX/vx_ext_<vendor>.h>

that contains definitions of each of the following.

  • New Node Creation Function Prototype per function.
    vx_node vxXYZNode(vx_graph graph, vx_image input, vx_uint32 value, vx_image output, vx_array temp);
  • A new Kernel Enumeration(s) and Kernel String per function.
    #define VX_KERNEL_NAME_KHR_XYZ "org.khronos.example.xyz"
    #define VX_LIBRARY_XYZ (0x3) // assigned from Khronos, vendors control their own
    enum vx_kernel_xyz_ext_e {
    VX_KERNEL_KHR_XYZ = VX_KERNEL_BASE(VX_ID_DEFAULT, VX_LIBRARY_XYZ) + 0x0,
    // up to 0xFFF kernel enums can be created.
    };
  • [Optional] A new VXU Function per function.
    vx_status vxuXYZ(vx_context context, vx_image input, vx_uint32 value, vx_image output, vx_array temp);
    This should come with good documentation for each new part of the extension. Ideally, these sorts of extensions should not require linking to new objects to facilitate usage.

Vendor Custom Extensions

Some extensions affect base vision functions and thus may be invisible to most users. In these circumstances, the vendor must report the supported extensions to the base nodes through the VX_CONTEXT_EXTENSIONS attribute on the context.

vx_char *tmp, *extensions = NULL;
vx_size size = 0;
vxQueryContext(context,VX_CONTEXT_EXTENSIONS_SIZE,&size,sizeof(size));
extensions = malloc(size);
extensions, size);

Extensions in this list are dependent on the extension itself; they may or may not have a header and new kernels or framework feature or data objects. The common feature is that they are implemented and supported by the implementation vendor.

Hinting

The specification defines a Hinting API that allows Clients to feed information to the implementation for optional behavior changes. See Framework: Hints. It is assumed that most of the hints will be vendor- or implementation-specific. Check with the OpenVX implementation vendor for information on vendor-specific extensions.

Directives

The specification defines a Directive API to control implementation behavior. See Framework: Directives. This may allow things like disabling parallelism for debugging, enabling cache writing-through for some buffers, or any implementation-specific optimization.

Copyright

The Khronos Group 2011-2017. OpenVX™, OpenCL™, OpenGL™, and OpenMAX™ are trademarks of the Khronos Group™.

Copyright © 2016-2017 The Khronos Group Inc. All Rights Reserved.

This specification is protected by copyright laws and contains material proprietary to the Khronos Group, Inc. It or any components may not be reproduced, republished, distributed, transmitted, displayed, broadcast or otherwise exploited in any manner without the express prior written permission of Khronos Group. You may use this specification for implementing the functionality therein, without altering or removing any trademark, copyright or other notice from the specification, but the receipt or possession of this specification does not convey any rights to reproduce, disclose, or distribute its contents, or to manufacture, use, or sell anything that it may describe, in whole or in part.

Khronos Group grants express permission to any current Promoter, Contributor or Adopter member of Khronos to copy and redistribute UNMODIFIED versions of this specification in any fashion, provided that NO CHARGE is made for the specification and the latest available update of the specification for any version of the API is used whenever possible. Such distributed specification may be re-formatted AS LONG AS the contents of the specification are not changed in any way. The specification may be incorporated into a product that is sold as long as such product includes significant independent work developed by the seller. A link to the current version of this specification on the Khronos Group web-site should be included whenever possible with specification distributions.

Khronos Group makes no, and expressly disclaims any, representations or warranties, express or implied, regarding this specification, including, without limitation, any implied warranties of merchantability or fitness for a particular purpose or non-infringement of any intellectual property. Khronos Group makes no, and expressly disclaims any, warranties, express or implied, regarding the correctness, accuracy, completeness, timeliness, and reliability of the specification. Under no circumstances will the Khronos Group, or any of its Promoters, Contributors or Members or their respective partners, officers, directors, employees, agents or representatives be liable for any damages, whether direct, indirect, special or consequential damages for lost revenues, lost profits, or otherwise, arising from or in connection with these materials.

Khronos, DevU, StreamInput, glTF, WebGL, WebCL, COLLADA, OpenKODE, OpenVG, OpenVX, OpenSL ES and OpenMAX are trademarks of the Khronos Group Inc. ASTC is a trademark of ARM Holdings PLC, OpenCL is a trademark of Apple Inc. and OpenGL is a registered trademark and the OpenGL ES and OpenGL SC logos are trademarks of Silicon Graphics International used under license by Khronos. All other product names, trademarks, and/or company names are used solely for identification and belong to their respective owners.