SourceXtractorPlusPlus  0.8
Please provide a description of the project.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HowTo add a new source property computation with configuration

Table of Contents

The structure of this tutorial is identical to that of the HowTo add a new source property computation and readers are assumed to have a good knowledge of this previous tutorial. The main difference is the introduction of a configuration class, through which user inputs can be entered into the property computation. To mark the similarities (and the differences), the same class names are used with a WithConf post-fix. We continue to walk-trough a kind of munimum example. The standard directory structure (already described here) is assumed.

Note that not much effort is made here to make a smart example. It is only thought for illustration purposes.

Source Property

Below is our minimum ExamplePropertyWithConf class.

namespace SExtractor {
class ExamplePropertyWithConf : public Property {
public:
virtual ~ExamplePropertyWithConf() = default;
ExamplePropertyWithConf(std::string name, double value) : m_name(name), m_scaled_mean(value) {}
const std::string& getName () const {
return m_name;
}
double getScaledMean () const {
return m_scaled_mean;
}
private:
std::string m_name;
double m_scaled_mean;
}; // End of ExamplePropertyWithConf class
} // namespace SExtractor

This class is fully analogous to the previous ExampleProperty and we refer to it for explanation. This property has two member variables and two getters, but there is nothing related to the configuration yet.

Configuration

Below is the (new) configuration class. After include and namespace comes the definition of the argument names. The anonymous namespace enclosing these variables limits their visibility to the current file (identical to a static declaration).

namespace po = boost::program_options;
namespace {
const std::string EXAMPLE_DOUBLE_OPTION {"ex-double-option"};
const std::string EXAMPLE_STRING_OPTION {"ex-string-option"};
}
namespace SExtractor {
class ExampleConfWithConf : public Euclid::Configuration::Configuration {
public:
virtual ~ExampleConfWithConf() = default;
ExampleConfWithConf(long manager_id) : Configuration(manager_id) {}
return { {"Example options", {
{ EXAMPLE_DOUBLE_OPTION.c_str(), po::value<double>()->default_value(1.0),
"A double option used as an illustration in ExamplePluginWithConf"},
{ EXAMPLE_STRING_OPTION.c_str(), po::value<std::string>()->default_value("None"),
"A string option used as an illustration in ExamplePluginWithConf"},
}}};
}
void initialize(const UserValues& args) {
m_example_double_option = args.at(EXAMPLE_DOUBLE_OPTION).as<double>();
m_example_string_option = args.at(EXAMPLE_STRING_OPTION).as<std::string>();
}
double getExampleDoubleOption () const {
return m_example_double_option;
}
const std::string& getExampleStringOption () const {
return m_example_string_option;
}
private:
double m_example_double_option;
std::string m_example_string_option;
}; // End of ExampleConfWithConf class
} // namespace SExtractor

The somewhat strange class name should read "example configuration for a plugin with configuration". It extends a Configuration base class which defined to-be-implemented interfaces. getProgramOptions() will be called by the system to get the options specific to this plugin. The syntax follows that of the Boost program_options library. The first field is the option name, the second one the option type, with a default value (if any) and the third one is a longer help text. The initialize method allows to retrieve the options and stored them in local variable. Finally getters are defined to provide means to read the configuration information after initialization. The choice of two configuration item is again only dictated by illustration purposes.

In this example, default values are provided for the two options. This make their run-time specification (though command -line arguments for example) optional. It is also possible not to give any default values, but then, the case where the arguments are missing has to be dealt with in the code. What to do if not arguments are provided? It may lead to a stop with the generation of an exception for missing arguments or to a continuation without argument values. See next tutorial for another example of optional configuration.

SourceTask

Below is the ExampleSourceTaskWithConf example analogous to the ExampleSourceTask. The main method is again computeProperties(source), but now there are two member variables (they were none before), which will be filled through a constructor call with information coming from the configuration. There is however no directed connection between the task and the configuration classes. Things will be tied together through the task factory (see next section).

namespace SExtractor {
class ExampleSourceTaskWithConf: public SourceTask {
public:
virtual ~ExampleSourceTaskWithConf () = default;
ExampleSourceTaskWithConf (std::string a_name, double a_double_value) :
m_name_option(a_name), m_scaling_factor_option(a_double_value) {
}
virtual void computeProperties (SourceInterface& source) const {
const auto& pixel_values = source.getProperty<DetectionFramePixelValues>().getValues();
double mean_value = 0.0;
for (auto value : pixel_values) {
mean_value += value;
}
mean_value /= pixel_values.size();
source.setProperty<ExamplePropertyWithConf>(m_name_option, m_scaling_factor_option * mean_value);
}
private:
std::string m_name_option;
double m_scaling_factor_option;
}; // End of ExampleSourceTaskWithConf class
} // namespace SExtractor

The pixel mean calculation is identical to the previous case, but the configuration-provided information is used to create the property. Remember that source.setProperty<ExamplePropertyWithConf>() is first calling the property constructor (i.e., ExamplePropertyWithConf(m_name_option, m_scaling_factor_option * mean_value), before attaching the property to the source.

TaskFactory

The purpose of the task factory now becomes clearer. As it can be seen in the below ExampleTaskFactoryWithConf example, the configuration object and its members are retrieved through the configuration manager (and the getters). They are then stored in local variable before being channeled to the factory method which create the task. The task factory uses the configuration to initialized the task and in this way, it connects user inputs to the property computation.

#include "SEImplementation/Plugin/ExamplePluginWithConf/ExampleConfWithConf.h"
#include "SEImplementation/Plugin/ExamplePluginWithConf/ExampleSourceTaskWithConf.h"
namespace SExtractor {
class ExampleTaskFactoryWithConf: public TaskFactory {
public:
virtual ~ExampleTaskFactoryWithConf () = default;
void reportConfigDependencies (Euclid::Configuration::ConfigManager& manager) const {
manager.registerConfiguration<ExampleConfWithConf>();
}
void configure (Euclid::Configuration::ConfigManager& manager) {
m_example_string_option = manager.getConfiguration<ExampleConfWithConf>().getExampleStringOption();
m_example_double_option = manager.getConfiguration<ExampleConfWithConf>().getExampleDoubleOption();
}
virtual std::shared_ptr<Task> createTask (const PropertyId& property_id) const {
return std::make_shared<ExampleSourceTaskWithConf>(m_example_string_option, m_example_double_option);
}
private:
std::string m_example_string_option;
double m_example_double_option;
};
} // namespace SExtractor

The reportConfigDependencies(...) method must be implemented to declare configuration dependencies. Here, there is no other dependency than the ExampleConfWithConf itself.

Plugin

The below plugin is almost identical to that of the previous ExamplePlugin example. The only difference is the registration of two output columns (from the same property).

#include "SEImplementation/Plugin/ExamplePluginWithConf/ExamplePropertyWithConf.h"
#include "SEImplementation/Plugin/ExamplePluginWithConf/ExampleTaskFactoryWithConf.h"
namespace SExtractor {
class ExamplePluginWithConf : public Plugin {
public:
virtual ~ExamplePluginWithConf() = default;
virtual void registerPlugin(PluginAPI& plugin_api) {
plugin_api.getTaskFactoryRegistry().registerTaskFactory<ExampleTaskFactoryWithConf, ExamplePropertyWithConf>();
plugin_api.getOutputRegistry().registerColumnConverter<ExamplePropertyWithConf, std::string>(
"redondant_name",
[](const ExamplePropertyWithConf& prop){
return prop.getName();
}
);
plugin_api.getOutputRegistry().registerColumnConverter<ExamplePropertyWithConf, double>(
"scaled_pixel_mean",
[](const ExamplePropertyWithConf& prop){
return prop.getScaledMean();
}
);
plugin_api.getOutputRegistry().optionalOutput<ExamplePropertyWithConf>("ExamplePropertyWithConf");
}
virtual std::string getIdString() const {
return "example_plugin_with_conf";
}
private:
}; // End of TestPlugin class
}

Last but not least, the following statement must appear in the plugin .cpp file. It registers our example plugin as a static plugin (i.e., which must be compiled with SExtractor). Other types of plugin will be described later.

#include "SEImplementation/Plugin/ExamplePluginWithConf/ExamplePluginWithConf.h"
namespace SExtractor {
static StaticPlugin<ExamplePluginWithConf> example_plugin_with_conf;
}