Ikaros Data Structures
In Ikaros, there is only one basic data structure, the matrix, which is used for all data and communication between modules. This is both the largest strength and the largest limitation of Ikaros. Since there is only a single data structure, the outputs of all modules are at least superficially compatible with the inputs of all other modules. As a consequence, it is always possible to connect any output to any input. If this makes computational sense, however, is another matter which obviously depends on what the different modules do with their inputs. It is possible for a module to demand a specific size on its inputs although this should in general be avoided.
Consistent with the design principle to keep everything as simple as possible, the only types you will need to use inside an Ikaros modules are
float * array;
float ** matrix;
Arrays can be used both as inputs and outputs and to store internal data in a module. Although it is possible to allocate memory in the usual way for arrays they are typically allocated in one of three ways in Ikaros.
If the array is used internally in a module it can be allocated using the utility function create_array(size) and destroyed using the function destroy_array(a). Internal data structures like this are typically allocated in the function MyModule::Init() and destroyed in the destructor function MyModule::~MyModule().
If the array is an input it is first declared in the IKC-file for the module and accessed in the Init() function using GetInputArray(name). Input arrays should not be destroyed in the destructor function since this is done automatically by the kernel. The size of an input array is accessed using the function GetInputSize(name). Typically, this size is accessed in the Init() function and stored in a module member variable for later use.
Outputs are allocated in a similar way to inputs, except that their size can optionally be set explicitly when they are created using the function AddOutput(name, size). Like inputs, the data is accessed using GetOutputArray(name) and the size of the output is accessed using GetOutputSize(name).
In special cases, it is also possible to add inputs and outputs programmatically in the creator function using AddInput() and AddOutput().
float * my_input = GetInputArray("MY_INPUT"); float * my_output = GetInputArray("MY_OUTPUT"); float * my_data = create_array(100); int my_input_size = GetInputSize("MY_INPUT"); int my_output_size = GetOutputSize("MY_OUTPUT"); int my_data_size = 100; int v = my_input; destroy_array(my_data);
There are a number of useful utility functions in IKAROS_Utils.h that can be used to do some basic operations on arrays. The function reset_array(a, size) sets all the elements of an array to 0. The function copy_array(a, b, size) copies the contents of array b to array a.
Like arrays, matrices that are to be used as input or outputs are also declared in the IKC-file for the module and pointers to them are collected in the Init function.
To define an input or output matrix in the source code in stead of the IKC-file use the functions AddInput(name, x, y) and to add an output matrix use the function AddOutput(name, x, y). These functions should only be used in exceptional cases since it is much simpler to just declare them in the IKC-file ad let Ikaros take care of the rest. To create data from an internal matrix use create_matrix(x, y) and to destoy it use destroy_matrix(m). Matrices are used for two dimensional data and their use follows that of arrays except that they have two values for size.
The only tricky thing with matrices is to remember in which index is which when accessing elements in a matrix. Matrices in Ikaros are stored in row-major order. This is the reverse order of the arguments to create_matrix. Row-major order is also the natual order to store images from a camera that are scanned line by line.
The following example illustrates the use of matrices:
float ** m = GetInputMatrix("MY_INPUT"); int size_x = GetInputSizeX("MY_INPUT"); int size_y = GetInputSizeY("MY_INPUT"); float sum = 0; for(int j=0; j<size_y; j++) for(int i=0; i<size_x; i++) sum += m[j][i]; float ** w = create_matrix(10, 5); for(int j=0; j<5; j++) for(int i=0; i<10; i++) sum += w[j][i];
The first index j ranges from 0 to size_y-1 and the second index i ranges from 0 to size_x-1.
There is no separate structures to store tabular numerical data in Ikaros, but by convention a matrix is used for this purpose. In this case, the first index ranges over the rows in the table and the second ranges other the individual values at each row.
Suppose, for instance, that you want to send a table of x and y coordinates for a maximum of 30 objects in a scene. For this you would allocate a 2x30 matrix output in the following way in the IKC-file:
<output name="COORDINATES" size_x="2" size_y="30" />
and get a pointer to it using:
float table ** = GetOutputMatrix("COORDINATES");
To set each entry, you would use something like the following statements:
table[i] = x_coordinate(i); table[i] = y_coordinate(i);
A separate output would be used to indicate how many elements (rows) of the table was used.
There are currently no standard modules in Ikaros that uses a full table for input or output, but this may change in the future and it is advisable to use the format described here for such data. Also, if there are x and y coordinates in a table, they should be in the first two columns of each row. If there is in addition a matching value, a correlation or something similar, it should the third column.
A few modules use a minimal version of a table for a <x, y> pair in the same matrix with one row and two columns.
The Internal Structure of Matrices
In most cases it should be sufficient to access the arrays and matrices in the standard way using one or two dimensional indices. However, there are cases when code can be optimized by accesing the data in the matrix or array directly.
To make this possible and to allow arrays and matrixes to used interchangedly in many cases, a matrix is represented in memory as a one dimensional array with a separate set of indices into each row. The elements of the matrix m of size 4x3 in the figure below can be accessed using m[y][x] as usual, but it is also possible to access the whole underlying array directly using m, which has size 12.
For the 4x3 matrix in the figure, the following different ways to access an element of a matrix are all equivalent:
// Standard access using two indices m = 7; // Access using only the second index m = 7; // Pointer arithmetic float * p = m; *(p+9) = 7;
This representation allows for a number of useful shortcuts. For example, if the same operation is to be made on all the elements of a matrix, it can just as well be treated as an array in the following way:
First the output is defined as two dimensional:
<output name="MY_OUTPUT" size_x="10" size_y="5" />
However, it can be treated internally in the module as if it was one dimensional. In this case the underlying array is returned when GetOutputArray is called:
// Get the size of the data, that is, size_x*size_y int size = GetOutputSize("MY_OUTPUT"); // Get a pointer to the one dimensional representation of the matrix float * data = GetOutpuArray("MY_OUTPUT"); // Do something with the data // Loop through all 10*5 elements for(int i=0; i<size; i++) data[i] = 7;
Alternatively, it is possible to treat a one dimensional input as a matrix by getting both the X and Y size of it. In this case, GetInputSizeY(name) will return 1. This allows a module to operate as if a matrix was connected as input even though it is connected to a one dimensional array.
Updated: 2014-10-07blog comments powered by Disqus