Writing algorithms on top of Images or PointClouds often comes with hurdles, like having to deal with multiple data layouts (Vpat, Linear or Contiguous, interleaved planes or planar ones) or datatypes. To reduce the overhead of handling these cases, CVB++ brings infrastructure for modern C++, leading to algorithms only having to be written once with the most efficient access (contiguous > linear > vpat) being used automatically.
The typical usage pattern might look like in the following example:
At the beginning of the snippet a function object (1) is declared: DivideByTwo implements the actual algorithm in operator()
(2). Instead of a struct/class with a call operator, a C++ lambda with auto block
as parameter can be used in all cases.
The algorithm, in this case dividing every pixel value by 2, gets access to the image plane through the block parameter. The pixel values can be read as well as changed by using the 2D-coordinate access as seen in line (3).
As can be seen in line (2), the Block is templated by the ImagePlane's DataType T and the ACCESS_TRAIT, which represents the vpat / linear / contiguous access. This way no manual reinterpretation of the pixel values is necessary.
The invocation of the algorithm is performed in line (4), through the call to Cvb::Visit, where the first parameter, is the function object and the later parameters, the plane(s) to work on.
The template parameters of the Block also allow specializing the algorithm for certain data types T. This just requires partially specializing the call operator for the specified type. SFINAE can of course also be used to select specialized functions.
To further simplify development of PointCloud algorithms, multiple planes can be visited simultaneously.
In this example, the three visited PointCloud planes are condensed into a single block parameter. By doing so, the type T of the Block, is no longer the plain Planes' DataType, but instead depending on whether the three planes are interleaved or not, T is a Cvb::LinearValue<PT, N> or Cvb::RefValue<PT, N>. N in this case is 3, as three Planes are visited, PT the Planes' DataType. These two wrapper types allow reading and writing multi-dimensional points directly without caring about the data layout. It is important to use decltype(auto)
as seen in line (1) when getting the value from the Block. When assigning a value to point.X()
or the subscript operator point[0]
, it is directly written to the corresponding Plane's memory.
As noted above, the function object's call operator is always instantiated for every combination of DataTypes and access traits. This might be unnecessary, if the Plane DataType is already known. In this case VisitAs can be used for a data type specific algorithm that works independent of the underlying data layout.
The two examples from above could look like this, if the type is known:
In both examples the type of the planes are given via the template parameter to VisitAs in lines (2) and (4). In the PointCloud example, where multiple planes are visited with a single block, this does not allow replacing the T template parameter of the Block in line (1), as this still may either be LinearValue<float, 3> or RefValue<float, 3>. In the Image example, where a single plane is visited by a single block, the type T known to be uint8_t.
In the above examples, it has already been shown that both, Visit and VisitAs, do support visiting either one plane and having a function object with one block, as well as visiting multiple planes with one block. Visit/As both support a generalized variant of the 1 plane, 1 block case: visiting multiple planes with a function object that has the same number of block arguments, as planes are visited:
In this case, the block arguments correspond to the visited planes from left-to-right. All three blocks are of the same type T and use the same access trait type ACCESS_TRAIT (the most general supported by all the Planes). The planes may be of differing sizes in this case, the blocks will report the corresponding one. For the VisitAs case, the template parameter T would be known to be float, as in the 1 plane, 1 block case from above.
In addition to specifying the arithmetic plane type with VisitAs, it also supports specifying custom complex types. This can be useful for visiting pointclouds with custom point types, for which arithmetic operators and custom operations are implemented. Supported examples are CVB++'s point types, like Cvb::Point3D. User defined types can be supported as well, as a starting point, everything in the region Point3D in cvb/_detail/block/detail_block_point_helper.hpp may be copyied in user's code and adapted to their custom types. Note, that the get, set and internal_set functions have to reside in the same namespace as the custom type. The ComponentOf and NumComponentsOf code has to live in the same namespace as the CVB++'s API, as outlined here:
There are two variants how custom types can be used:
The above example shows both variants being used in lines (4) and (5). This version of NormalizePointCloud shows in line (2) how much shorter the algorithm can be by using custom types. One drawback of using fully custom types leads to lines (1) and (3) being slightly different to the above examples: The values of the points are no longer fully in-place. Thus decltype(auto)
is not required anymore and changing point has no effect on the actual planes' values. To commit changes to the planes, it is now necessary to call the Block::Set function.
The following table presents an overview of the variants supported by the Visit/As functions and their corresponding behaviour.
Function | PC/Img | Template argument | #FunctorArgs | #Planes | Type Dispatch | Size Checked | Vpat plane | Linear plane | Contiguous plane | Non-interleaved planes |
---|---|---|---|---|---|---|---|---|---|---|
Visit | PC | N/A | 1 | 3* | Yes | Yes | N/A | LinearPlaneBlock<LinearValue<T, 3>> | ArrayPlaneBlock<LinearValue<T, 3>> | ScatterBlock<RefValue<T, 3>, 3> |
Visit | PC | N/A | 3 | 3 | Yes | No | N/A | LinearPlaneBlock<T>,… | ArrayPlaneBlock<T>,… | N/A |
Visit | Img | N/A | 1 | 3 | Yes | Yes | N/A | LinearPlaneBlock<LinearValue<T, 3>> | ArrayPlaneBlock<LinearValue<T, 3>> | ScatterBlock<RefValue<T, 3>, 3> |
Visit | Img | N/A | 3 | 3 | Yes | No | VpatPlaneBlock<T>,… | LinearPlaneBlock<T>,… | ArrayPlaneBlock<T>,… | N/A |
VisitAs | PC | float | 1 | 3 | No | Yes | N/A | LinearPlaneBlock<LinearValue<float, 3>> | ArrayPlaneBlock<LinearValue<float, 3>> | ScatterBlock<RefValue<float, 3>, 3> |
VisitAs | PC | float | 3 | 3 | No | No | N/A | LinearPlaneBlock<float>,… | ArrayPlaneBlock<float>,… | N/A |
VisitAs | PC | Point3D<float> | 1 | 3 | No | Yes | N/A | LinearPlaneBlock<Point3D<float>> | ArrayPlaneBlock<Point3D<float>> | ScatterBlock<Point3D<float>, 3> |
VisitAs | PC | Point3D<float> | 3 | 3 | No | No | N/A | LinearPlaneBlock<Point3D<float>>,… | ArrayPlaneBlock<Point3D<float>>,… | N/A |
VisitAs | PC | template Point3D | 1 | 3 | Yes | Yes | N/A | LinearPlaneBlock<Point3D<T>> | ArrayPlaneBlock<Point3D<T>> | ScatterBlock<Point3D<T>, 3> |
VisitAs | PC | template Point3D | 3 | 3 | Yes | No | N/A | LinearPlaneBlock<Point3D<T>>,… | ArrayPlaneBlock<Point3D<T>>,… | N/A |
VisitAs | Img | uint8_t | 1 | 3 | No | Yes | N/A | LinearPlaneBlock<LinearValue<uint8_t, 3>> | ArrayPlaneBlock<LinearValue<uint8_t, 3>> | ScatterBlock<RefValue<uint8_t, 3>, 3> |
VisitAs | Img | uint8_t | 3 | 3 | No | No | VpatPlaneBlock<uint8_t>,… | LinearPlaneBlock<uint8_t>,… | ArrayPlaneBlock<uint8_t>,… | N/A |
VisitAs | Img | Point3D<std::uint8_t> | 1 | 3 | No | Yes | N/A | LinearPlaneBlock<Point3D<uint8_t>> | ArrayPlaneBlock<Point3D<uint8_t>> | ScatterBlock<Point3D<uint8_t>, 3> |
VisitAs | Img | Point3D<std::uint8_t> | 3 | 3 | No | No | VpatPlaneBlock<Point3D<uint8_t>>,… | LinearPlaneBlock<Point3D<uint8_t>>,… | ArrayPlaneBlock<Point3D<uint8_t>>,… | N/A |
VisitAs | Img | template Point3D | 1 | 3 | Yes | Yes | N/A | LinearPlaneBlock<Point3D<T>> | ArrayPlaneBlock<Point3D<T>> | ScatterBlock<Point3D<T>, 3> |
VisitAs | Img | template Point3D | 3 | 3 | Yes | No | VpatPlaneBlock<Point3D<T>>,… | LinearPlaneBlock<Point3D<T>>,… | ArrayPlaneBlock<Point3D<T>>,… | N/A |
*3 is only used as an example here, as the exemplary Point3D is a fit for 3 planes.
The first 5 columns are used to select the relevant line, the other columns describe the resulting behaviour of the used variant. An example to illustrate the interpretation of the table:
For the Visit
call in line (1), the first line in the table is relevant: Function is Visit
, 3 pointcloud planes are visited but the lambda only has a single block parameter. In this case, type dispatch has to happen, i.e. the lambda is instantiiated for all supported data types - float, double and int. As the table suggests, all planes must have the same size, otherwise an exception is thrown. The last four columns indicate what types block
will be instantiated with. T
indicates the element type, the number of planes 3 is just an example. As pointcloud planes are used here, no VpatPlaneBlock
instantiations happen. As a single block is used to access all planes, LinearPlaneBlock
, ArrayPlaneBlock
or ScatterBlock
may be used and thus lambda instantiations for all of these happen.
The VisitAs
call in line (2) matches the last line, where the function is VisitAs
, visiting 3 image planes with the templated type Point3D. The functor has 3 block arguments. Given this information, the table can be used to see that type dispatch has to happen as the T
in Point3D<T>
has to be set to the element type. In this case, the planes and thus blocks may have differing sizes, thus no exception will be thrown. The last four columns indicate that all blocks have the same type (e.g. VpatPlaneBlock<Point3D<T>>
), but no ScatterBlock
will be used, as each planes' layout is represented in its own block.
Using Visit
and VisitAs
is also possible with custom plane types. To enable this, the PlaneTraits
struct has to be specialized for the custom plane type. PlaneTraits
implements the following concept:
Two types of planes are supported: those that have at least a linear layout and those that might have a non-linear layout that can be represented by a Vpat. In the latter case, the PlaneTraits<CustomPlane>::HasVpat
value is set to true and the GetVpat
function provides a Cvb::Vpat. For the linear case, HasVpat
is false and GetBasePtr
returns the pointer to the first element, GetXInc
and GetYInc
provide the byte increments for a x and y steps.
The data types, the plane supports and thus are dispatched are specified via the TypeList alias. The Cvb::Plane e.g. has using TypeList = DispatchableTypeList<float, double, int>;
leading to instantiations for those three types. The types in the template argument list of DispatchableTypeList have to be representable by Cvb::DataType.
Example specializations can be found for Plane and ImagePlane in the cvb/plane.hpp
and cvb/_decl/decl_image_plane.hpp
headers.
Note, that the specialization has to reside in the same namespace as CVB++'s functions. So it should be wrapped in