What is standalone C++ pipeline ?

A standalone C++ pipeline is a SolAR pipeline running in a standalone application. This pipeline will be defined in a main.cpp class, and is very useful for pipeline debugging. We recommend starting with this approach to familiarize yourself with pipeline assembly.

Initialize your pipeline with the QTCreator wizard

Download QTcreator wizards for XPCF

Creating a new pipeline from scratch can be a little bit tricky. To help you, a QTcreator wizard is available and will make the task much easier.
Start by installing this wizard by launching the install.bat on Windows or install.sh on Linux (in your ${XPCF_MODULE_ROOT}/xpcf/[version]/wizards/qtcreator).

Create a standalone SolAR pipeline in QTCreator

Open QTCreator and create a new project (in File menu).

Select XPCF project and XPCF Application template and click on Choose button.

create an XPCF C++ application in QT
Figure 1. Create an XPCF C++ application in QT

Then, set the name of your application embedding your pipeline, and its location (Create a dedicated folder with all pipeline projects is recommended).

Set pipeline project name in QT
Figure 2. Set pipeline project name in QT

Next provide the details concerning your application. You can set your package version. You can define if your dependencies are static or shared. We highly recommend to use the shared library for modules. Also, for installation, all dependencies of your application can be copied in a recursive mode in your installation folder. Thus, when you will install your application, you will be sure that all required third parties will be also there to run it. For the link step, you can let the SolAR build pipeline find automatically the dependencies recursively. Finally, if you want to load your project in visual studio, check the box QTVS. Thus, you will be able to load the QTProject in visual studio via the plugin QT Visual Studio Tools.

set application details in QT
Figure 3. Set application details in QT

For the next step, you have to enter the directory where the XPCF binaries are located. Normally, you will find it in your USER_HOME, in the folder .remaken\packages\.

Set XPCF version directory
Figure 4. Set XPCF version directory

Choose your development kits. We recommend to use MSVC 2017 64bit on Windows or GCC on Linux.

Then, no subproject to add, if you want to add a version control select it, and click on Finish. Your project is now created.

Three files have been created. First the project file MyStandalonePipeline.pro, secondly a C++ file MyStandalonePipeline_main.cpp, and finally a packagedependencies.txt to manage the dependencies of your application.

But let’s take a closer look at these files.

QTCreator project file

MyStandalonePipeline.pro
QT       -= core gui
CONFIG -= app_bundle qt

TARGET = MyStandalonePipeline
TARGET = %{ModuleName}
FRAMEWORK = $${TARGET}
VERSION=0.11.0
DEFINES +=  $${TARGET}VERSION=\"$${VERSION}\"

CONFIG += c++1z
CONFIG += console
CONFIG += shared

# Uncomment following line to add more verbose information from builddefs-qmake rules
# CONFIG += verbose
# Uncomment following line to prepare remaken package
# CONFIG += package_remaken

CONFIG += with_qtvs

with_qtvs {
    PROJECTCONFIG = QTVS
}

include(findremakenrules.pri) (1)

DEPENDENCIESCONFIG = sharedlib
REMAKEN_PKGSUBDIR=shared
DEPENDENCIESCONFIG += install_recurse (2)

CONFIG(debug,debug|release) { (3)
    DEFINES += _DEBUG=1
    DEFINES += DEBUG=1
    REMAKEN_PKGSUBDIR=$${REMAKEN_PKGSUBDIR}/debug
}

CONFIG(release,debug|release) {
    DEFINES += NDEBUG=1
    REMAKEN_PKGSUBDIR=$${REMAKEN_PKGSUBDIR}/release
}

package_remaken {
    message("Preparing remaken package installation in $${REMAKEN_PKGSUBDIR}")
    INSTALLSUBDIR=$${REMAKEN_PKGSUBDIR}
}

## Configuration for Visual Studio to install binaries and dependencies. Work also for QT Creator by replacing QMAKE_INSTALL
PROJECTCONFIG = QTVS (4)

#NOTE : CONFIG as staticlib or sharedlib, DEPENDENCIESCONFIG as staticlib or sharedlib and PROJECTDEPLOYDIR MUST BE DEFINED BEFORE templatelibbundle.pri inclusion
include ($${QMAKE_REMAKEN_RULES_ROOT}/templateappconfig.pri)

#DEFINES += BOOST_ALL_NO_LIB
DEFINES += BOOST_ALL_DYN_LINK
DEFINES += BOOST_AUTO_LINK_NOMANGLE
DEFINES += BOOST_LOG_DYN_LINK

HEADERS +=
SOURCES +=     MyStandalonePipeline_main.cpp

unix {
    LIBS += -ldl
    QMAKE_CXXFLAGS += -DBOOST_LOG_DYN_LINK
}

macx {
    QMAKE_MAC_SDK= macosx
    QMAKE_CXXFLAGS += -fasm-blocks -x objective-c++
}

win32 {
    QMAKE_LFLAGS += /MACHINE:X64
    DEFINES += WIN64 UNICODE _UNICODE
    QMAKE_COMPILER_DEFINES += _WIN64
    QMAKE_CXXFLAGS += -wd4250 -wd4251 -wd4244 -wd4275

    # Windows Kit (msvc2013 64)
    LIBS += -L$$(WINDOWSSDKDIR)lib/winv6.3/um/x64 -lshell32 -lgdi32 -lComdlg32
    INCLUDEPATH += $$(WINDOWSSDKDIR)lib/winv6.3/um/x64
 }

INCLUDEPATH += $${PWD}

DISTFILES +=     Makefile

OTHER_FILES +=     packagedependencies.txt

with_qtvs {
#NOTE : Must be placed at the end of the .pro
    include ($${QMAKE_REMAKEN_RULES_ROOT}/remaken_install_target.pri) (5)
}
1 This .pri file has been installed by the wizard. It will allow to find the remaken folder depending on the OS you are using.
2 The dependencies of your application will be installed recursively. More details are available on the builddefs-qmake project on GitHub.
3 By default, your executable will be installed in the packages folder of remaken. If you want to install it locally in a common bin folder, add these 10 lines.
4 The installation of your application will also work with Visual Studio. Warning, in QTCreator, this will replace the usual QMAKE_INSTALL.
5 Place at the end the .pri file to install your application.

Finally, click on Projects in the left menu of QTcreator, click on Run, add a Custom Executable run configuration set your working directory to the bin folder of your project, and check the box Add build library search path to PATH if not already done.

Package Dependencies file

As mentionned previously, SolAR framework provides developers with a building pipeline allowing among other things to easily manage dependencies (download, version management, packaging during deployment step, etc.).

To define the dependencies used by your pipeline, just replace in your packagedependencies.txt the reference to XPCF package by a reference to the SolARFramework as shown below:

/.packagedependencies.txt

SolARFramework|0.11.0|SolARFramework|SolARBuild@github|https://github.com/SolarFramework/SolarFramework/releases/download

Here is the syntax for each dependency (more information available on the Remaken project on GitHub):

framework#channel | version | [condition]#library name | identifier or local folder@repository_type | repository_url | link_mode | options

As the component manager provided by XPCF can load at runtime the modules used by your pipeline as defined into your configuration file presented next, you do not need to add them into the packagedependencies.txt file. Also, XPCF and the build pipeline handle dependency recursivity, meaning that you do not need to add the dependency to XPCF already referenced by the SolAR framework package.

Your dependencies, also called artifacts, should be available in your ${XPCF_MODULE_ROOT}/ folder. To automatically download your dependencies, just run remaken install where your packagedependencies.txt is located.

Refer to https://github.com/b-com-software-basis/remaken for more information.

Numerous samples of packagedependencies.txt files can be found with the SolAR Samples you have surely installed on your machine.

Right now, your project is configured.

What about Visual Studio ?

You can load your .pro file created with the Wizard in Visual Studio (see above for more details how to configure the .pro file). If you do not want to install QTCreator, create manually your .pro based of the above description.

Microsoft Visual Studio provides a Qt Visual Studio Tools. This enables developers to import QT project files (.pro) into Visual Studio.

Install QT Visual Studio Tools:

  • In Visual Studio, select Tools > Extensions and Updates > Online to install and update QT Visual Studio Tools.

Import the .pro. file into Visual Studio:

  • Select Qt VS Tools > Open Qt Project File (.pro) and choose your .pro file.

Right now, your project is configured.

Create the configuration file

This file will be used at run-time. It will allow XPCF to load Module components and configure them at run-time. So you can experiment different implementations and configurations of a pipeline without the need to recompile your application.

Creating a configuration file is very easy. Once you have identified the modules and the components required to assemble your pipeline, just record them in the xml configuration file.

Following, an example of a configuration file for a pipeline using the camera and image viewer components embedded in the SolARModuleOpenCV module :

MyPipelineConfiguration.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<xpcf-registry autoAlias="true">

<module uuid="15e1990b-86b2-445c-8194-0cbe80ede970" name="SolARModuleOpenCV" path="$XPCF_MODULE_ROOT/SolARBuild/SolARModuleOpenCV/0.11.0/lib/x86_64/shared" description="OpenCV"> (1)
  <component uuid="5B7396F4-A804-4F3C-A0EB-FB1D56042BB4" name="SolARCameraOpencv" description="SolARCameraOpencv"> (2)
    <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="IComponentIntrospect" description="IComponentIntrospect"/>
    <interface uuid="5DDC7DF0-8377-437F-9C81-3643F7676A5B" name="ICamera" description="ICamera"/>
  </component>
  <component uuid="fa4a780a-9720-11e8-9eb6-529269fb1459" name="SolARVideoAsCameraOpencv" description="SolARVideoAsCameraOpencv">
      <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="IComponentIntrospect" description="IComponentIntrospect"/>
      <interface uuid="5DDC7DF0-8377-437F-9C81-3643F7676A5B" name="ICamera" description="ICamera"/>
  </component>
  <component uuid="19ea4e13-7085-4e3f-92ca-93f200ffb01b" name="SolARImageViewerOpencv" description="SolARImageViewerOpencv">
    <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="IComponentIntrospect" description="IComponentIntrospect"/>
    <interface uuid="b05f3dbb-f93d-465c-aee1-fb58e1480c42" name="IImageViewer" description="IImageViewer"/>
  </component>
</module>
<factory>
    <bindings>
      <bind interface="ICamera" to="SolARCameraOpencv" properties="CameraProperty"/> (3)
      <bind interface="ICamera" to="SolARVideoAsCameraOpencv" name="VideoAsCamera" properties="VideoAsCameraProperty"/> (4)
    </bindings>
</factory>

<properties> (5)
  <configuration component="SolARCameraOpencv" name="CameraProperty">
    <property name="calibrationFile" type="string" value="camera_calibration.json"/>
    <property name="deviceID" type="uint" value="0"/>
  </configuration>
  <configuration component="SolARVideoAsCameraOpencv" name="VideoAsCameraProperty"> (6)
    <property name="calibrationFile" type="string" value="camera_calibration.json"/>
    <property name="videoPath" type="string" value="path to video"/>
    <property name="delayTime" type="int" value="30"/>
  </configuration>
  <configuration component="SolARImageViewerOpencv">
    <property name="title" type="string" value="Original Image"/>
    <property name="exitKey" type="int" value="27"/>
    <property name="width" type="int" value="0"/>
    <property name="height" type="int" value="0"/>
  </configuration>
</properties>

</xpcf-registry>
1 Add the modules used by your pipeline. To get information concerning the modules, have a look to their registry file in the ${HOME_DIR}/.xpcf/SolAR/.
2 For each module, add the components used by your pipeline. To get information concerning the components, have a look to the registry file of their module available in the ${HOME_DIR}/.xpcf/SolAR/.
3 Thanks to the factory field, you can bind by default a specific component implementation (here a camera openCV) on a given SolAR interface (here the ICamera).
4 You can give a name to a binding. In this case, in your code you will be able to ask for this specific binding when you will want to instantiate a ICamera component.
5 Optionally, add a configuration to your components. The name of your configuration parameters are generally the name of the variable in the class definition of the component without the prefix "m_". The type of the configuration parameters are simple types such as "String", "Integer", "Float", "Double", or array of these simple types.
6 A second configuration is defined for the SolARVideoAsCameraOpencv with a specific name if you want to instanciate a second component with a specific configuration

Examples of such configuration files are available in the samples directory that is provided with the SolAR installation.

If you need to use new modules please refer to the module API and to the Get modules section to install them. If you need more third parties, please refer to the Package your third parties section.

Implement your pipeline

This section describes how to assemble different components to build a vision pipeline. First as already mentioned in the previous sections, it is supposed that the required components have been identified. And consequently, a configuration file, a dependencies file and a project file have been created.

Main template of a standalone pipeline

To create a standalone C++ pipeline, you can start by replacing the main.cpp code with the following one:

main.cpp
///**
 * Add your header with the copyright and license information concerning your pipeline
 */

// Common headers (1)
#include "xpcf/xpcf.h"
#include "core/Log.h"
#include <boost/log/core.hpp>

// ADD HERE: Component interfaces header. e.g. #include "api/input/devices/ICamera.h"
(2)


// Namespaces (3)
using namespace SolAR;
using namespace SolAR::datastructure;
using namespace SolAR::api;

namespace xpcf  = org::bcom::xpcf;

// Main function
int main(int argc, char *argv[])
{

#if NDEBUG (4)
    boost::log::core::get()->set_logging_enabled(false);
#endif
    LOG_ADD_LOG_TO_CONSOLE(); (5)

// Instantiate component manager and load the pipeline configuration file (6)
    SRef<xpcf::IComponentManager> xpcfComponentManager = xpcf::getComponentManagerInstance();
    if(xpcfComponentManager->load("MyPipelineConfiguration.xml")!=org::bcom::xpcf::_SUCCESS)
    {
        LOG_ERROR("Failed to load the configuration file MyPipelineConfiguration.xml")
        return -1;
    }

// ADD HERE: instantiate concrete components and bind them to abstract component interfaces
    // e.g. SRef<image::ICamera> camera = xpcfComponentManager->resolve<image::ICamera>();
    (7)

// ADD HERE: Declare here the data structures used to connect components
    (8)

// ADD HERE: The pipeline initialization
    (9)

// ADD HERE: The pipeline processing
    while (true)
    {
      (11)
    }

    return 0;
}
1 The xpcf header is required to instantiate components. log header is recomended if you want to log your pipeline.
2 Add the component interface header files of the components used by the pipeline.
3 Add SolAR and XPCF namespaces directives to shorten the calls to SolAR api and datastructures.
4 Add this line to remove irrelevant logs in release mode.
5 Add this line to push logs in the console. You can also push logs to a log file by using the macro LOG_ADD_TO_FILE("path/logfilename.log", "r").
6 Create an instance of an XPCF ComponentManager and use it to load the configuration file of your standalone pipeline.
7 Instantiate concrete components embedded into modules thanks to the XCPF Component Manager. The implementation of the component is automatically resolved according to the factory field defined in your configuration file. Here, an OpenCV camera will be instantiated if you request a component of type ICamera. Thanks to that, swapping a component by another one will just consists of editing your configuration file. More details are given in the next section Instantiate a component.
8 Declare all the data structures used to exchange data between components. Have a look to the data structures defined in the SolARFramework in the Framework API section.
9 if required, add the code to initialize your pipeline (e.g. start a camera, load a reference image, etc.).
10 Create the loop of your pipeline by calling the different processing functions of your components with data structure as input and/or output attributes. More details on how to call a function of a component are given in the next section use a component.

Instantiate a component .

Thanks to XPCF, instantiation of a component is very easy and this operation is done at run-time. The configuration of the components will be initialized with values declared in the configuration file. The syntax is the following for e.g. a component that display an image in a window:

SRef<display::IImageViewer> imageViewer = xpcfComponentManager->resolve<display::IImageViewer>();

As you have only one implementation of a IImageViewer in your configuration file (defined in the OpenCV module), it will be instantiate automatically.

If you want to use several implementations of the same component in your pipeline, you can define in your configuration file different binding with corresponding names. You can then ask to instantiate a dedicated component implementation thanks to this binding name:

SRef<input::devices::ICamera> videoAsCamera = xpcfComponentManager->resolve<input::devices::ICamera>("VideoAsCamera");

Use a component.

Once a component is created, any public function described in its API can be used to build your pipeline. For instance :

if (viewerConfImage->display(image) == FrameworkReturnCode::_STOP )

Pipeline sample

We will present next the implementation of the simplest pipeline that consists in capturing an image from a camera and display it in a window. For this implementation, we will need only one module: SolARModuleOpenCV.

  • as the example contains components that implement virtual interfaces, include the corresponding header files

#include "api/input/devices/ICamera.h"
#include "api/display/IImageViewer.h"
  • as the modules/components to be used are listed in a configuration file (xml), don’t forget to load it via XPCF :

xpcfComponentManager->load("MyStandalonePipelineConfiguration.xml")

Here the full sample code for this standalone pipeline:

///**
 * Add your header with the copyright and license information concerning your pipeline
 */

// Common headers
#include "xpcf/xpcf.h"
#include "core/Log.h"
#include <boost/log/core.hpp>

// Component interfaces header. e.g. #include "api/image/IImageLoader.h"
#include "api/input/devices/ICamera.h" (1)
#include "api/display/IImageViewer.h"

// Namespaces
using namespace SolAR;
using namespace SolAR::datastructure;
using namespace SolAR::api;

namespace xpcf  = org::bcom::xpcf;

// Main function
int main(int argc, char *argv[])
{

#if NDEBUG
    boost::log::core::get()->set_logging_enabled(false);
#endif
    LOG_ADD_LOG_TO_CONSOLE();

// Instantiate component manager and load the pipeline configuration file
    SRef<xpcf::IComponentManager> xpcfComponentManager = xpcf::getComponentManagerInstance();
    if(xpcfComponentManager->load("MyStandalonePipelineConfiguration.xml")!=org::bcom::xpcf::_SUCCESS)
    {
        LOG_ERROR("Failed to load the configuration file MyStandalonePipelineConfiguration.xml")
        return -1;
    }

    // declare and instantiate components (2)
    auto camera = xpcfComponentManager->resolve<input::devices::ICamera>();
    auto imageViewer = xpcfComponentManager->resolve<display::IImageViewer>();

    if (!camera || !imageViewer)
    {
        LOG_ERROR("One or more component creations have failed");
        return -1;
    }

// Declare here the data structures used to connect components
    SRef<Image> image; (3)

// The pipeline initialization

    // start the camera (4)
    if (camera->start() != FrameworkReturnCode::_SUCCESS)
    {
        LOG_ERROR ("Camera cannot start");
        return -1;
    }

// The pipeline processing (5)
    while (true)
    {
        if(camera->getNextImage(image)==SolAR::FrameworkReturnCode::_ERROR_)
            break;
        if (imageViewer->display(image) == FrameworkReturnCode::_STOP )
            break;
     }
    return 0;
}
1 As the example assemble two components, a camera and an image viewer, we include the corresponding component interface header files.
2 We instantiate our two components based on openCV implementations and we bind them to their abstract component interfaces.
3 We declare a shared reference of a SolARImage that will be used to pass the image captured by the camera to the image viewer.
4 We initialize the pipeline by starting the camera. In our configuration file, you will find for the camera first an ID corresponding to the camera you want to start, and secondly a path to a json camera description file defining its intrinsic parameters. This file can be generated by calibrating the camera (see Camera Calibration section for more information).
5 Finally, we implement the core of the pipeline by capturing the current image from the camera, and by passing this image to the image viewer.
Don’t forget to re-run qmake before building your pipeline for QT Creator and re-import .pro file for Visual Studio.

As there is no generic way to implement a pipeline, we encourage the readers to take a look at the many examples provided by SolAR in the Samples directory that comes with the installation of SolAR. For each sample, you will find a configuration file, a dependencies file and a project file to help you to build your own pipeline together with sources codes.