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 wizard

We are aware that creating a pipeline step by step can be a little tedious. A QTCreator wizard should be soon available to automate file generation and make the pipeline creation easier for you.

Also, the SolAR Framework has been designed to ease the development of a visual coding tool to build pipeline just by dragging and dropping component boxes and connect them in a Graphic User Interface.

But for now, follow the instructions below to initialize a standalone C++ pipeline step-by-step.

Initialize your pipeline step-by-step

Create your pipeline project

QT Creator

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

Select Non-Qt project and Plain C++ Application and click on Choose button.

create a Plain C++ application in QT
Figure 1. Plain C++ application creation

Then, set the name of your standalone 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, define your build system with qmake. For the next step, choose your development kits. We recommend to use MSVC 2017 64bit on Windows or Clang on Linux.

Your project is now created, but as SolAR provides a smart and easy way to build and deploy you pipeline, you will need to update the MyStandalonePipeline.pro file. Select it in your project tree, and replace its script by the following one:

MyStandalonePipeline.pro
## remove Qt dependencies
QT -= core gui
TARGET = MyStandalonePipeline (1)
VERSION=x.x.x (2)

CONFIG += c++1z
CONFIG -= qt
CONFIG += console

DEFINES += MYVERSION=$${VERSION}

CONFIG(debug,debug|release) {
    DEFINES += _DEBUG=1
    DEFINES += DEBUG=1
}

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

win32:CONFIG -= static
win32:CONFIG += shared

DEPENDENCIESCONFIG = sharedlib
#NOTE : CONFIG as staticlib or sharedlib, DEPENDENCIESCONFIG as staticlib or sharedlib, QMAKE_TARGET.arch and PROJECTDEPLOYDIR MUST BE DEFINED BEFORE templatelibconfig.pri inclusion
include (../../builddefs/qmake/templateappconfig.pri) (3)

SOURCES += \
    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

}

Now, just update the MyStandalonePipeline.pro file:

1 set the TARGET with the name of your standalone pipeline,
2 set the version number of your standalone pipeline,
3 check if the builddefs folder used to define the building pipeline is well referenced

Finally, click on Projects in the left menu of QTcreator, click on Run, set your working directory to the root directory of your project, and check Add build library search path to LD_LIBRARY_PATH if not already done.

 — Using Cmake instead of .pro QT file — 

Create a CMakeLists.txt file and copy the following code to it. This file use temporarily BCOMDEVROOT environment variable link to your SoLAR sources.

CMakeLists.txt
cmake_minimum_required(VERSION 3.7.2)

##################################################
project("MyStandalonePipeline") (1)
set (VERSION_NUMBER "x.x.x") (2)
set (SOURCES main.cpp)
##################################################

# various macros
include("$ENV{BCOMDEVROOT}/bcomBuild/SolARFramework/solarmacros.cmake") (3)
# config setup
setup()
# process packagedependencies.txt
processPackagedependencies()

# define the list of files to copy to build directory

set(FILES_TO_COPY
  # Copy framework and its dependencies binaries in your working folder (4)
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_filesystem.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_system.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_timer.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_log.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_chrono.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_thread.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/boost/${BOOST_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}boost_date_time.${LIBEXTENSION}
	$ENV{BCOMDEVROOT}/thirdParties/xpcf/${XPCF_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}xpcf.${LIBEXTENSION}
  $ENV{BCOMDEVROOT}/bcomBuild/SolARFramework/${SOLARFRAMEWORK_VERSION}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}SolARFramework.${LIBEXTENSION}

  # Copy module binaries in your working folder (5)
  $ENV{BCOMDEVROOT}/bcomBuild/"Module1"/${"MODULE1_VERSION"}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}"Module1".${LIBEXTENSION}
  $ENV{BCOMDEVROOT}/bcomBuild/"Module2"/${"MODULE2_VERSION"}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}"Module2".${LIBEXTENSION}

  # Copy binaries of the third parties of the modules in your working folder (6)
  $ENV{BCOMDEVROOT}/thirdParties/"Module1_Dependency"/${"MODULE1_DEPENDECY_VERSION"}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}"Module1_Dependency"${MODULE1_DEPENDECYVERSIONSUFFIX}.${LIBEXTENSION}
  $ENV{BCOMDEVROOT}/thirdParties/"Module2_Dependency"/${"MODULE2_DEPENDECY_VERSION"}/lib/x86_64/shared/${BUILDCONFIG}/${LIBPREFIX}"Module2_Dependency"${MODULE2_DEPENDECYVERSIONSUFFIX}.${LIBEXTENSION}
	)
# define targets (library, install and uninstall)
defineTargets("executable" "${FILES_TO_COPY}")

now, just update the CMakeLists.txt file:

1 replace the name of the project with the name of your standalone pipeline,
2 set the version number of your standalone pipeline,
3 check if the solarmacros.cmake file exists. This file must have been installed when you launched the installer. If you have decided to build the framework by yourself, this file must have been copied in your ${BCOMDEVROOT} folder when you have built and installed the SolARFramework project.
4 Change nothing here, the binaries required for the SolAR framework will be copied in your working directory.
5 Add the commands to copy the SolAR modules used by your pipeline in you working directory. If they have been well installed, they should be stored in ${BCOMDEVROOT}/bcomBuild/$
6 Add the commands to copy the dependencies of the SolAR modules used by your pipeline in you working directory. If they have been well installed, they should be stored in ${BCOMDEVROOT}/thirdParties/$
We recommend to maintain both a QT project file and a CMake file for any SolAR pipeline.

Select your dependencies

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, create a file called packagedependencies.txt at the root of your project.

Following, an example of packagedependencies.txt file for a standalone pipeline using OpenCV module:

/.packagedependencies.txt

xpcf|2.2.0|xpcf|artifactory|https://repository.b-com.com/amc-generic
spdlog|0.14.0|spdlog|thirdParties|https://github.com/SolarFramework/binaries/releases/download
eigen|3.3.5|eigen|thirdParties|https://github.com/SolarFramework/binaries/releases/download
SolARFramework|0.6.0|SolARFramework|github|https://github.com/SolarFramework/SolarFramework/releases/download
SolARModuleTools|0.6.0|SolARModuleTools|github|https://github.com/SolarFramework/SolARModuleTools/releases/download
SolARModuleOpenCV|0.6.0|SolARModuleOpenCV|github|https://github.com/SolarFramework/SolARModuleOpenCV/releases/download
opencv|3.4.3|opencv|thirdParties|https://github.com/SolarFramework/binaries/releases/download

Here is the syntax for each dependency:

Directory|version|name|local location  or  github |remote location

artifactory refers to .remaken/packages/<yourCompiler>/ folder. You can create a REMAKENROOT variable. All process are made in this folder.

If you need new modules please refer to the Get modules section, and if you need more third parties, please refer to the Package your third parties section.

xpcf, spdlog, eigen and SolARFramework are mandatory third parties for assembling a pipeline.

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

Create the configuration file

his 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 :

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

<module uuid="15e1990b-86b2-445c-8194-0cbe80ede970" name="SolARModuleOpenCV" path="$REMAKENROOT/SolARModuleOpenCV/0.6.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="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>

<properties> (3)
  <configuration component="SolARCameraOpencv">
    <property name="calibrationFile" type="string" value="camera_calibration.yml"/>
    <property name="deviceID" type="UnsignedInteger" value="0"/>
  </configuration>
  <configuration component="SolARImageViewerOpencv">
    <property name="title" type="string" value="Original Image"/>
    <property name="exitKey" type="Integer" value="27"/>
    <property name="width" type="Integer" value="0"/>
    <property name="height" type="Integer" 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 available in the ${REMAKENROOT}/"ModuleName"/"ModuleVersion"/.
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 ${REMAKENROOT}/"ModuleName"/"ModuleVersion"/.
3 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.

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

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"

// ADD HERE: Module traits headers. #include "SolARModuleOpencv_traits.h"
(2)

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


// Namespaces (4)
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 (5)
    boost::log::core::get()->set_logging_enabled(false);
#endif
    LOG_ADD_LOG_TO_CONSOLE(); (6)

// Instantiate component manager and load the pipeline configuration file (7)
    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;
    }

// ADD HERE: instantiate concrete components and bind them to abstract component interfaces
    // e.g. SRef<image::ICamera> camera = xpcfComponentManager->create<SolAR::MODULES::OPENCV::SolARCameraOpencv>()->bindTo<image::ICamera>();
    (8)

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

// ADD HERE: The pipeline initialization
    (10)

// 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 traits header files of the modules used by the pipeline.
3 Add the component interface header files of the components used by the pipeline.
4 Add SolAR and XPCF namespaces directives to shorten the calls to SolAR api and datastructures.
5 Add this line to remove irrelevant logs in release mode.
6 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").
7 Create an instance of an XPCF ComponentManager and use it to load the configuration file of your standalone pipeline.
8 Instantiate concrete components embedded into modules thanks to the XCPF Component Manager. Then bind them to their corresponding abstract component interface. Thanks to that, swapping a component by another one will just consists in changing this line of code. More details are given in the next section Instantiate a component.
9 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.
10 if required, add the code to initialize your pipeline (e.g. start a camera, load a reference image, etc.).
11 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 the use of XPCF, instantiation of a component is very easy and this operation is done at run-time. The configuration of the components will be initialize 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->create<SolAR::MODULES::OPENCV::SolARImageViewerOpencv>()->bindTo<display::IImageViewer>();

If you want to instantiate several instance of the same component but with different configurations, in the configuration file fill in the name attribute for each configuration with a specific value, and add this string value in parameter of the function create:

SRef<display::IImageViewer> imageViewer = xpcfComponentManager->create<SolAR::MODULES::OPENCV::SolARImageViewerOpencv>("configuration1")->bindTo<display::IImageViewer>();

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 relies on SolarModuleOpencv, include the module traits

#include "SolARModuleOpencv_traits.h"
  • as the example contains components that implement virtual interfaces, include the corresponding header files

#include "api/image/IImageLoader.h"
#include "api/display/IImageViewer.h"
  • as the modules/components to be used are listed in a configuration file (yml), don’t forget to load it via xpcf :

xpcfComponentManager->load("conf_ImageLoader.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"

// Module traits headers. #include "SolARModuleOpencv_traits.h"
#include "SolARModuleOpencv_traits.h" (1)

// Component interfaces header. e.g. #include "api/image/IImageLoader.h"
#include "api/input/devices/ICamera.h" (2)
#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 (3)
    SRef<input::devices::ICamera> camera = xpcfComponentManager->create<SolAR::MODULES::OPENCV::SolARCameraOpencv>()->bindTo<input::devices::ICamera>();
    SRef<display::IImageViewer> imageViewer = xpcfComponentManager->create<SolAR::MODULES::OPENCV::SolARImageViewerOpencv>()->bindTo<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; (4)

// The pipeline initialization

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

// The pipeline processing (6)
    while (true)
    {
        if(camera->getNextImage(image)==SolAR::FrameworkReturnCode::_ERROR_)
            break;
        if (imageViewer->display(image) == FrameworkReturnCode::_STOP )
            break;
     }
    return 0;
}
1 As the example relies on SolARModuleOpencv, we include the corresponding module traits header file.
2 As the example assemble two components, a camera and an image viewer, we include the corresponding component interface header files.
3 We instantiate our two components based on openCV implementations and we bind them to their abstract component interfaces.
4 We declare a shared reference of a SolARImage that will be used to pass the image captured by the camera to the image viewer.
5 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 an yml camera description file defining its intrinsic parameters. This file can be generated by calibrating the camera (see Camera Calibration section for more information).
6 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.
On QT Creator, before building your pipeline, do not forget to run qmake on your project (right click on MyStandalonePipeline into the project hierarchy, and click on run qmake).

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.