Create a component

What is a component ?

A SolAR component is element embedding processing (and soon storage) capabilities. Components are designed to be connected together in order to create a full pipeline. For this reason, a component defines ideally one (potentially several) processing function defining input (the data to process) and ouptut (the processed data). For interoperability issues, the component has to implement a SolAR component interface defined by the SolAR Framework API available here.

As each component implementation can require dedicated configuration parameters, the SolAR framework provides an easy-to-use mechanism to initialize them at load-time with a external xml configuration file.

Finally, when a SolAR component is implemented, it has to be embedded in a SolAR module for its publication to the SolAR pipeline assemblers.

Create a component with the wizard

Download QTCreator wizards for XPCF

Creating a new component 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 (in your ${REMAKEN_PKG_ROOT}/packages/win-cl-14.1/xpcf/[version]/wizards/qtcreator) or install.sh on Linux (in your ${REMAKEN_PKG_ROOT}/packages/linux-gcc/xpcf/[version]/wizards/qtcreator).

You can also download the wizard on the following link:
https://github.com/SolarFramework/Tools/releases/download/XPCFWizard_2_3_4/XPCF_qtcreator_wizards.zip
Then unzip the downloaded file, and run the install.bat on Windows or the install.sh on Linux.

Create a SolAR component in QTCreator

Open QTCreator and create the project of your module for which you want to add a new component

Right click on your Sources folder, and click on Add New…​ in the contextual menu.

add a new component in QT
Figure 1. Add a new component in QT

Select XPCF and XPCF Component class and click on Choose button.

create an SolAR Component in QT
Figure 2. Create an SolAR Component in QT

Then, set the name of your component, inspired by the name of the SolAR interface you will implement. Do not forget to specifiy the namespace of your component based on the common namespace used for your module. In addition, you will have defined the SolAR interface that your component will inherit. Again, do not forget the namespace of the interface. If you need more information about the interfaces available in SolAR, check its API documentation. Finally, you can define if your component is configurable. If yes, you will be able to easily configure it in a dedicated file attached to your pipeline.

set component name and inherited interface in QT
Figure 3. Set component name and inherited interface in QT

Normally, your component class will be added to the project of your module, just select a version control if needed and click on the Finish button.

Two files have been created, one header file and one cpp file.

Now, let’s take a closer look at these files to implement your component.

The component header file

MyDescriptorsExtractor.h
#ifndef MYDESCRIPTORSEXTRACTOR_H
#define MYDESCRIPTORSEXTRACTOR_H
#include <xpcf/component/ConfigurableBase.h>

(1)
#include "IDescriptorsExtractor.h"
#include "SolARMyModuleAPI.h"

namespace SolAR {
namespace MODULES {
namespace MyModule {

(2)
class MyModule_API_DLLEXPORT MyDescriptorsExtractor : public org::bcom::xpcf::ConfigurableBase, public virtual SolAR::api::features::IDescriptorsExtractor
{
public:
    MyDescriptorsExtractor();
    ~MyDescriptorsExtractor() override;
    void unloadComponent () override;

(3)
    inline std::string getTypeString() override { return std::string("DescriptorsExtractorType::MYCOMPONENT") ;};

    /// @brief Extracts a set of descriptors from a given image around a set of keypoints based on AKAZE algorithm
    /// "Fast explicit diffusion for acceleratedfeatures in nonlinear scale space"
    /// [in] image: source image.
    /// [in] keypoints: set of keypoints.
    /// [out] decsriptors: se of computed descriptors.
    void extract (const SRef<Image> image,
                  const std::vector< Keypoint > &keypoints,
                  SRef<DescriptorBuffer> & descriptors) override;

};


} // namespace MyModule
} // namespace MODULES
} // namespace SolAR

(4)
template <> struct org::bcom::xpcf::ComponentTraits<SolAR::MODULES::MyModule::MyDescriptorsExtractor>
{

    static constexpr const char * UUID = "{4bc8c7d0-9e57-4816-8e92-e26103cc43f7}";
    static constexpr const char * NAME = "MyDescriptorsExtractor";
    static constexpr const char * DESCRIPTION = "MyDescriptorsExtractor implements SolAR::api::features::IDescriptorsExtractor interface";
};

#endif // MYDESCRIPTORSEXTRACTOR_H
1 Add the include files for the SolAR abstract interface that your component inherits, as well as the header file defining the export declaration MACRO of your module.
2 Add your export declaration MACRO defined in MyModuleAPI.h file to export your component in the dynamic library of your module (Here MyModule_API_DLLEXPORT).
3 Here, you will have to define the interface of your component inherited from your SolAR interface. For instance, the getStringType and the extract methods declared in the IDescriptorExtractor abstract interface.
4 The traits are automatically defined with a automatically generated UUID, the name of the component and a description that you are free to change.

As in this sample you are implementing a descriptor extractor component inherited from the SolAR interface IDescriptorsExtractor, you will have to add the following interface to your component:

std::string getTypeString() override;

/// @brief Extracts a set of descriptors from a given image around a set of keypoints based on my wonderfull algorithm
/// [in] image: source image.
/// [in] keypoints: set of keypoints.
/// [out] decsriptors: se of computed descriptors.
void extract (const SRef<Image> image, const std::vector< Keypoint > &keypoints, SRef<DescriptorBuffer> & descriptors) override;
As the SolAR interfaces are defined with abstract class with pure virtual methods, you will have to declare in your component all the pure virtual methods declared in the interface it inherits.
Of course, you can add any method and variable required by your component, but in private, as the only access to a component should be done through the inherited SolAR interface!

The component cpp file

MyDescriptorsExtractor.cpp
#include "MyDescriptorsExtractor.h"


namespace xpcf = org::bcom::xpcf;

template<> SolAR::MODULES::MyModule::MyDescriptorsExtractor * xpcf::ComponentFactory::createInstance<fullComponentType>();


namespace SolAR {
namespace MODULES {
namespace MyModule {

MyDescriptorsExtractor::MyDescriptorsExtractor():xpcf::ConfigurableBase(xpcf::toMap<MyDescriptorsExtractor>())
{

    declareInterface<SolAR::api::features::IDescriptorsExtractor>(this);
    (1)
    //  Inject declarations come here : declare any component that must be injected to your component through its interface
    // declareInjectable<IFilter>(m_filter);
    //
    // Inject declaration can have a name :
    // declareInjectable<IFilter>(m_blurFilter, "blurFilter");
    //
    // Inject declaration can be optional i.e. not finding a binding component for the interface is not an error :
    // declareInjectable<IImageFilter>(m_imageFilter, false);

    // wrap any component member variable to expose as properties with declareProperty<T>() with T matching the variable type
    // For example : declareProperty<float>("blurFactor",m_blurFactor);
    // declareProperty("name",m_memberVariable) also works with template type deduction when m_memberVariable is a supported type of IProperty
}

MyDescriptorsExtractor::~MyDescriptorsExtractor()
{

}

(2)
void MyDescriptorsExtractor::unloadComponent ()
{
    // provide component cleanup strategy

    // default strategy is to delete self, uncomment following line in this case :
    // delete this;
    return;
}

(3)
xpcf::XPCFErrorCode MyDescriptorsExtractor::onConfigured()

{
    // Add custom onConfigured code
    return xpcf::XPCFErrorCode::_SUCCESS;
}

(4)
void MyDescriptorsExtractor::extract(const SRef<Image> image, const std::vector<Keypoint> & keypoints, SRef<DescriptorBuffer> & descriptors)
{
    // Add the code to extract your descriptor

}

} // namespace MyModule
} // namespace MODULES
} // namespace SolAR
1 In the constructor of your component, you can simply inject another component to your current component. Thus, you will be able to define which component you want to inject in your configuration file. You can also define here the class variable members you want to configure from your configuration file. Both component injection and variable configuration funciton can be set in just a line of code.
2 Specify here what your component has to do before it will be deleted.
3 Specify here some processing to do when the class member variable values have been set according to the configuration file.
4 Add the implementation of the method abstract declared by the inherited SolAR interface. Here, the extract method of the IDescriptorExtractor interface.

Now, you can code the implementation of your methods defined in your header file.

Declare your component in your module

That is great, you have created your component, you have implemented its functions, but now you have to declare it in the module.

To do that, just open the main file of your module, and add the following lines of code:

MyModule_main.cpp
#include <xpcf/module/ModuleFactory.h>
#include <iostream>

#include "MyDescriptorExtractor.h" (1)

namespace xpcf=org::bcom::xpcf;

/**
 *  @ingroup xpcfmodule
 */
/**
  * Declare module.
  */
XPCF_DECLARE_MODULE("{41a884a8-645b-47bc-9f41-66d057b2ec5d}","SolAR::MODULES::MyModule","MyModule module description");

/**
 * This method is the module entry point.
 * XPCF uses this method to create components available in the module.
 *
 * Each component exposed must be declared inside a xpcf::tryCreateComponent<ComponentType>() call.
 */
extern "C" XPCF_MODULEHOOKS_API xpcf::XPCFErrorCode XPCF_getComponent(const xpcf::uuids::uuid& componentUUID,SRef<xpcf::IComponentIntrospect>& interfaceRef)
{
    xpcf::XPCFErrorCode errCode = xpcf::XPCFErrorCode::_FAIL;
    errCode = xpcf::tryCreateComponent<SolAR::MODULES::MyModule::MyDescriptorExtractor>(componentUUID,interfaceRef); (2)
 //   if (errCode != xpcf::XPCFErrorCode::_SUCCESS) {
 //       errCode = xpcf::tryCreateComponent<SolAR::MODULES::MyModule::OtherComponents>(componentUUID,interfaceRef);
 //   }
    return errCode;
}

/**
  * The declarations below populate list of the components available in the module (it represents the module index).
  * XPCF uses this index to introspect the components available in a module, providing the ability to generate the configuration file skeleton from the code.
  */
XPCF_BEGIN_COMPONENTS_DECLARATION
(3)
XPCF_ADD_COMPONENT(SolAR::MODULES::MyModule::MyDEscriptorsExtractor)
XPCF_END_COMPONENTS_DECLARATION
1 Add the header file of the component you want to add to your module
2 Add the next 3 following lines of code to your module main file for each component you want to add to your module (update the second line with the name of your component).
3 Add your component in the component declaration of your module.

Add your component to your registry file

Finally, edit the xpcf_MyModule_registry.xml that should be at the root folder of your module project. Add a decsription for each component you want to embed in your module as follows:

xpcf_MyModule_registry.xml
<xpcf-registry>
<module uuid="5b066de7-2a9f-4dff-a730-ce82adafe2f6" name="MyModule" description="MyModule_Description" path="$REMAKEN_PKG_ROOT/packages/SolARBuild/win-cl-14.1/MyModule/1.0.0/lib/x86_64/shared">

<component uuid="4bc8c7d0-9e57-4816-8e92-e26103cc43f7" name="MyDesrciptorsExtractor" description="MyDesrciptorsExtractor Description"> (1)
        <interface uuid="125f2007-1bf9-421d-9367-fbdc1210d006" name="IComponentIntrospect" description="IComponentIntrospect"/> (2)
        <interface uuid="c0e49ff1-0696-4fe6-85a8-9b2c1e155d2e" name="IDescriptorsExtractor" description="SolAR::api::features::IDescriptorsExtractor"/> (3)
</component>

</module>
</xpcf-registry>
1 Copy and paste the component UUID, name and description defined in the traits defined in the component header file.
2 Keep always this declaration related to the inheritance to IComponentIntrospect.
3 Add a description to all SolAR interfaces inherited by your component. To get access to UUID of SolAR interfaces, please take a look at the SolAR framework API documentation.

This is done, your component has been added to your module. You can now build your module, and use it in your pipelines.