Skip to content
Snippets Groups Projects
problem.cpp 46.44 KiB
//--------LICENSE_START--------
//
// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
// All rights reserved.
//
// This file is part of WOLF
// WOLF is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//--------LICENSE_END--------
// wolf includes
#include "core/problem/problem.h"
#include "core/map/factory_map.h"
#include "core/sensor/factory_sensor.h"
#include "core/processor/factory_processor.h"
#include "core/capture/capture_void.h"
#include "core/factor/factor_block_absolute.h"
#include "core/factor/factor_quaternion_absolute.h"
#include "core/state_block/state_quaternion.h"
#include "core/state_block/state_angle.h"
#include "core/tree_manager/factory_tree_manager.h"
#include "core/tree_manager/tree_manager_base.h"
#include "core/utils/loader.h"


namespace wolf
{

Problem::Problem(const std::string& _frame_structure, SizeEigen _dim, MapBasePtr _map) :
        tree_manager_(nullptr),
        hardware_ptr_(std::make_shared<HardwareBase>()),
        trajectory_ptr_(std::make_shared<TrajectoryBase>()),
        map_ptr_(_map),
        motion_provider_map_(),
        frame_structure_(_frame_structure),
        prior_options_(std::make_shared<PriorOptions>()),
        transformed_(false)
{
    dim_ = _dim;
    if (_frame_structure == "PO" and _dim == 2)
    {
        state_size_ = 3;
        state_cov_size_ = 3;
    }else if (_frame_structure == "PO" and _dim == 3)
    {
        state_size_ = 7;
        state_cov_size_ = 6;
    }else if (_frame_structure == "POV" and _dim == 3)
    {
        state_size_ = 10;
        state_cov_size_ = 9;
    }else if (_frame_structure == "POVCDL" and _dim == 3)
    {
        state_size_ = 19;
        state_cov_size_ = 18;
    }
    else std::runtime_error(
            "Problem::Problem(): Unknown frame structure. Add appropriate frame structure to the switch statement.");
}

void Problem::setup()
{
    hardware_ptr_  -> setProblem(shared_from_this());
    trajectory_ptr_-> setProblem(shared_from_this());
    if (map_ptr_)
        map_ptr_   -> setProblem(shared_from_this());
}

ProblemPtr Problem::create(const std::string& _frame_structure, SizeEigen _dim, MapBasePtr _map)
{
    ProblemPtr p(new Problem(_frame_structure, _dim, _map)); // We use `new` and not `make_shared` since the Problem constructor is private and cannot be passed to `make_shared`.
    p->setup();
    return p->shared_from_this();
}

ProblemPtr Problem::autoSetup(const std::string& _input_yaml_file, std::vector<std::string> _schema_folders)
{
#if __APPLE__
    std::string lib_extension = ".dylib";
#else
    std::string lib_extension = ".so";
#endif

    // Create yaml server
    auto server = yaml-schema-cpp::YamlServer({}, _input_yaml_file);

    // 
  
    std::stringstream log;
    if (not server.validate(SensorClass, log))
    { 
        WOLF_ERROR(log.str());
        return nullptr;
    } 
    auto params = std::make_shared<ParamsSensorClass>(server.getNode())

    // Problem structure and dimension
    std::string frame_structure = _server.getParam<std::string> ("problem/frame_structure");
    int         dim             = _server.getParam<int>         ("problem/dimension");
    auto        problem         = Problem::create(frame_structure, dim, nullptr);

    WOLF_TRACE("Setting up problem with frame structure {" + frame_structure + "} and dimension " + std::to_string(dim) + "D");

    // Load plugins
    auto loaders = std::vector<std::shared_ptr<Loader>>();
    std::string plugins_path = _WOLF_LIB_DIR;
    if (plugins_path.back() != '/')
    {
        plugins_path += '/';  // only works for UNIX systems
    }
    for (auto plugin_name : _server.getParam<std::vector<std::string>>("plugins"))
    {
      if (plugin_name == "core" or plugin_name == "wolf" or plugin_name == "") continue; // ignore plugin "core"

      std::string plugin = plugins_path + "libwolf" + plugin_name + lib_extension;
      WOLF_TRACE("Loading plugin " + plugin_name + " via " + plugin);
      auto l = std::make_shared<LoaderRaw>(plugin);
      l->load();
      loaders.push_back(l);
    }

    // load raw libs
    std::vector<std::string> raw_libs;
    try
    {
        raw_libs = _server.getParam<std::vector<std::string>>("raw_libs");
    } 
    catch (MissingValueException& e) 
    {
        WOLF_TRACE("No raw libraries to load...");
    }
    for (auto lib : raw_libs) {
        WOLF_TRACE("Loading raw lib " + lib);
        auto l = std::make_shared<LoaderRaw>(lib);
        l->load();
        loaders.push_back(l);
    }

    // Install sensors and processors -- load plugins only if sensors and processor are not registered
    auto sensors        = _server.getParam<std::vector<std::map<std::string, std::string>>>("sensors");
    for (auto sen : sensors)
    {
        if (not FactorySensor::isCreatorRegistered(sen["type"]))
        {
            auto        plugin_name = sen["plugin"];
            std::string plugin      = plugins_path + "libwolf" + plugin_name + lib_extension;
            WOLF_TRACE("Loading plugin " + plugin_name + " via " + plugin);
            auto l = std::make_shared<LoaderRaw>(plugin);
            l->load();
            loaders.push_back(l);
        }
        problem->installSensor(sen["type"], sen["name"], _server);
    }
    auto processors = _server.getParam<std::vector<std::map<std::string, std::string>>>("processors");
    for (auto prc : processors)
    {
        if (not FactoryProcessor::isCreatorRegistered(prc["type"]))
        {
            auto        plugin_name = prc["plugin"];
            std::string plugin      = plugins_path + "libwolf" + plugin_name + lib_extension;
            WOLF_TRACE("Loading plugin " + plugin_name + " via " + plugin);
            auto l = std::make_shared<LoaderRaw>(plugin);
            l->load();
            loaders.push_back(l);
        }
        problem->installProcessor(prc["type"], prc["name"], prc["sensor_name"], _server);
    }

    // Map (optional)
    std::string map_type, map_plugin;
    try
    {
        map_type = _server.getParam<std::string>("map/type");
        map_plugin = _server.getParam<std::string>("map/plugin");
    } 
    catch (MissingValueException& e) 
    {
        WOLF_TRACE("No map/type and/or map/plugin specified. Emplacing the default map: MapBase.");
        map_type = "MapBase";
        map_plugin = "core";
    }
    WOLF_TRACE("Map Type: ", map_type, " in plugin ", map_plugin);

    auto map = AutoConfFactoryMap::create(map_type, _server);
    map->setProblem(problem);
    problem->setMap(map);

    // Tree manager
    std::string tree_manager_type = _server.getParam<std::string>("problem/tree_manager/type");
    WOLF_TRACE("Tree Manager Type: ", tree_manager_type);
    if (tree_manager_type != "None" and tree_manager_type != "none")
        problem->setTreeManager(FactoryTreeManagerServer::create(tree_manager_type, _server));

    // Set problem prior -- first keyframe
    std::string prior_mode = _server.getParam<std::string>("problem/prior/mode");
    assert((prior_mode == "nothing" || prior_mode == "initial_guess" || prior_mode == "fix" || prior_mode == "factor") && "wrong _mode value, it should be: 'nothing', 'initial_guess', 'fix' or 'factor'");
    WOLF_TRACE("Prior mode: ", prior_mode);
    if (prior_mode == "nothing")
    {
        problem->setPriorOptions(prior_mode);
    }
    else if (prior_mode == "factor")
    {
        problem->setPriorOptions(prior_mode,
                                 _server.getParam<VectorComposite>("problem/prior/state"),
                                 _server.getParam<VectorComposite>("problem/prior/sigma"));
    }
    else
    {
        problem->setPriorOptions(prior_mode,
                                 _server.getParam<VectorComposite>("problem/prior/state"));
    }

    // Done
    return problem;
}

Problem::~Problem()
{
    //    WOLF_DEBUG("destructed -P");
}

SensorBasePtr Problem::installSensor(const std::string& _sen_type,
                                     const std::string& _unique_sensor_name,
                                     ParamsSensorBasePtr _params,
                                     const Priors& _priors)
{
    SensorBasePtr sen_ptr = FactorySensorPriors::create(_sen_type, 
                                                        _unique_sensor_name, 
                                                        dim_, 
                                                        _params, 
                                                        _priors);
    sen_ptr->link(getHardware());
    return sen_ptr;
}

SensorBasePtr Problem::installSensor(const std::string& _sen_type,
                                     const std::string& _unique_sensor_name,
                                     const std::string& _params_yaml_filename)
{
    if (_params_yaml_filename == "" or not file_exists(_params_yaml_filename))
    {
        throw std::runtime_error("Cannot install sensor: provided params YAML file: '" + _params_yaml_filename + "' does not exist.");
    }
    SensorBasePtr sen_ptr = FactorySensorYaml::create(_sen_type, 
                                                      _unique_sensor_name, 
                                                      dim_, 
                                                      _params_yaml_filename);
    sen_ptr->link(getHardware());
    return sen_ptr;
}

SensorBasePtr Problem::installSensor(const std::string& _sen_type,
                                     const std::string& _unique_sensor_name,
                                     const ParamsServer& _server)
{
    SensorBasePtr sen_ptr = FactorySensor::create(_sen_type, _unique_sensor_name, dim_, _server);
    sen_ptr->link(getHardware());
    return sen_ptr;
}

ProcessorBasePtr Problem::installProcessor(const std::string& _prc_type,
                                           const std::string& _unique_processor_name,
                                           SensorBasePtr _corresponding_sensor_ptr,
                                           ParamsProcessorBasePtr _prc_params)
{
    if (_corresponding_sensor_ptr == nullptr)
    {
      WOLF_ERROR("Cannot install processor '", _unique_processor_name,
                 "' since the associated sensor does not exist !");
      return ProcessorBasePtr();
    }

    ProcessorBasePtr prc_ptr = FactoryProcessor::create(_prc_type, _unique_processor_name, _prc_params);

    //Dimension check
    int prc_dim = prc_ptr->getDim();
    auto prb = this;
    assert(((prc_dim == 0) or (prc_dim == prb->getDim())) && "Processor and Problem do not agree on dimension");

    prc_ptr->configure(_corresponding_sensor_ptr);
    prc_ptr->link(_corresponding_sensor_ptr);

    return prc_ptr;
}

ProcessorBasePtr Problem::installProcessor(const std::string& _prc_type, //
                                           const std::string& _unique_processor_name, //
                                           const std::string& _corresponding_sensor_name, //
                                           const std::string& _params_filename)
{
    SensorBasePtr sen_ptr = findSensor(_corresponding_sensor_name);
    if (sen_ptr == nullptr)
        throw std::runtime_error("Cannot bind processor. Reason: Sensor \"" + _corresponding_sensor_name + "\" not found. Check sensor name, it must match in sensor and processor!");
    if (_params_filename == "")
        return installProcessor(_prc_type, _unique_processor_name, sen_ptr, nullptr);
    if (not file_exists(_params_filename))
        throw std::runtime_error("Cannot install processor: parameters' YAML file does not exist: " + _params_filename);
    
    ProcessorBasePtr prc_ptr = FactoryProcessorYaml::create(_prc_type, _unique_processor_name, _params_filename);

    //Dimension check
    int prc_dim = prc_ptr->getDim();
    auto prb = this;
    assert(((prc_dim == 0) or (prc_dim == prb->getDim())) && "Processor and Problem do not agree on dimension");

    prc_ptr->configure(sen_ptr);
    prc_ptr->link(sen_ptr);

    return prc_ptr;
}

ProcessorBasePtr Problem::installProcessor(const std::string& _prc_type, //
                                           const std::string& _unique_processor_name, //
                                           const std::string& _corresponding_sensor_name, //
                                           const ParamsServer& _server)
{
    SensorBasePtr sen_ptr = findSensor(_corresponding_sensor_name);
    if (sen_ptr == nullptr)
        throw std::runtime_error("Cannot bind processor. Reason: Sensor \"" + _corresponding_sensor_name + "\" not found. Check sensor name, it must match in sensor and processor!");

    ProcessorBasePtr prc_ptr = FactoryProcessorServer::create(_prc_type, _unique_processor_name, _server);

    //Dimension check
    int prc_dim = prc_ptr->getDim();
    auto prb = this;
    assert(((prc_dim == 0) or (prc_dim == prb->getDim())) && "Processor and Problem do not agree on dimension");

    prc_ptr->configure(sen_ptr);
    prc_ptr->link(sen_ptr);

    return prc_ptr;
}

SensorBaseConstPtr Problem::findSensor(const std::string& _sensor_name) const
{
    return getHardware()->getSensor(_sensor_name);
}
SensorBasePtr Problem::findSensor(const std::string& _sensor_name)
{
    return getHardware()->getSensor(_sensor_name);
}

ProcessorBaseConstPtr Problem::findProcessor(const std::string& _processor_name) const
{
    for (const auto& sensor : getHardware()->getSensorList())
        for (const auto& processor : sensor->getProcessorList())
            if (processor->getName() == _processor_name)
                return processor;
    return nullptr;
}

ProcessorBasePtr Problem::findProcessor(const std::string& _processor_name)
{
    for (const auto& sensor : getHardware()->getSensorList())
        for (const auto& processor : sensor->getProcessorList())
            if (processor->getName() == _processor_name)
                return processor;
    return nullptr;
}

FrameBasePtr Problem::emplaceFrame(const TimeStamp& _time_stamp, //
                                   const StateStructure& _frame_structure, //
                                   const SizeEigen _dim, //
                                   const Eigen::VectorXd& _frame_state)
{
    VectorComposite state;
    SizeEigen index = 0;
    SizeEigen size = 0;
    for (auto key : _frame_structure)
    {
        if (key == 'O')
        {
            if (_dim == 2) size = 1;
            else size = 4;
        }
        else
            size = _dim;

        assert (_frame_state.size() >= index+size && "State vector incompatible with given structure and dimension! Vector too small.");

        state.emplace(key, _frame_state.segment(index, size));

        // append new key to frame structure
        if (frame_structure_.find(key) == std::string::npos) // not found
            frame_structure_ += std::string(1,key);

        index += size;
    }

    assert (_frame_state.size() == index && "State vector incompatible with given structure and dimension! Vector too large.");

    auto frm = FrameBase::emplace<FrameBase>(trajectory_ptr_,
                                             _time_stamp,
                                             _frame_structure,
                                             state);

    return frm;
}

FrameBasePtr Problem::emplaceFrame(const TimeStamp& _time_stamp, //
                                      const StateStructure& _frame_structure, //
                                      const SizeEigen _dim)
{
    return emplaceFrame(_time_stamp,
                        _frame_structure,
                        getState(_time_stamp));
}

FrameBasePtr Problem::emplaceFrame (const TimeStamp& _time_stamp, //
                                    const StateStructure& _frame_structure, //
                                    const VectorComposite& _frame_state)
{
    return FrameBase::emplace<FrameBase>(getTrajectory(),
                                         _time_stamp,
                                         _frame_structure,
                                         _frame_state);
}

FrameBasePtr Problem::emplaceFrame (const TimeStamp& _time_stamp, //
                                       const VectorComposite& _frame_state)
{
    // append new keys to frame structure
    for (auto pair_key_vec : _frame_state)
    {
        auto key = pair_key_vec.first;
        if (frame_structure_.find(key) == std::string::npos) // not found
            frame_structure_ += std::string(1,key);
    }

    return FrameBase::emplace<FrameBase>(getTrajectory(),
                                         _time_stamp,
                                         getFrameStructure(),
                                         _frame_state);
}

FrameBasePtr Problem::emplaceFrame(const TimeStamp& _time_stamp, //
                                   const Eigen::VectorXd& _frame_state)
{
    return emplaceFrame(_time_stamp,
                        this->getFrameStructure(),
                        this->getDim(),
                        _frame_state);
}

FrameBasePtr Problem::emplaceFrame(const TimeStamp& _time_stamp)
{
    return emplaceFrame(_time_stamp,
                        this->getFrameStructure(),
                        this->getDim());
}

TimeStamp Problem::getTimeStamp ( ) const
{
    TimeStamp  ts = TimeStamp::Invalid();

    for (auto prc_pair : motion_provider_map_)
        if (prc_pair.second->getTimeStamp().ok())
            if ( (not ts.ok() ) or prc_pair.second->getTimeStamp() > ts)
                ts = prc_pair.second->getTimeStamp();


    if (not ts.ok())
    {
        auto last_kf = trajectory_ptr_->getLastFrame();

        if (last_kf)
            ts = last_kf->getTimeStamp(); // Use last estimated frame's state

    }

    WOLF_DEBUG_COND(not ts.ok(), "Problem has nowhere to find a valid timestamp!");

    return ts;
}


VectorComposite Problem::getState(const StateStructure& _structure) const
{
    StateStructure structure = (_structure == "" ? getFrameStructure() : _structure);

    VectorComposite state;

    // compose the states of all MotionProvider processors (ordered by user-defined priority) into one only state
    for (auto prc_pair : motion_provider_map_)
    {
        auto prc_state = prc_pair.second->getState(structure);

        // transfer processor vector blocks to problem state
        for (auto pair_key_vec : prc_state)
        {
            if (state.count(pair_key_vec.first) == 0) // Only write once. This gives priority to processors with more priority
            {  
              state.insert(pair_key_vec);
            }
        }

        //If all keys are filled return
        if (state.size() == structure.size())
        {
            return state;
        }
    }

    // check for empty blocks and fill them with the last KF, with the prior, or with zeros in the worst case

    VectorComposite state_last;

    auto last_kf = trajectory_ptr_->getLastFrame();

    if (last_kf)
        state_last = last_kf->getState(structure);

    else if (prior_options_ and prior_options_->mode != "nothing")
        state_last = prior_options_->state;

    for (auto key : structure)
    {
        if (state.count(key) == 0)
        {
            auto state_last_it = state_last.find(key);

            if (state_last_it != state_last.end())
                state.emplace(key, state_last_it->second);

            else
                state.emplace(key, stateZero(string(1,key)).at(key));
        }
    }

    return state;
}

VectorComposite Problem::getState (const TimeStamp& _ts, const StateStructure& _structure) const
{
    StateStructure structure = (_structure == "" ? getFrameStructure() : _structure);

    VectorComposite state;

    // compose the states of all MotionProvider processors (ordered by user-defined priority) into one only state
    for (auto prc_pair : motion_provider_map_)
    {
        auto prc_state = prc_pair.second->getState(_ts, structure);

        // transfer processor vector blocks to problem state
        for (auto pair_key_vec : prc_state)
        {
            if (state.count(pair_key_vec.first) == 0) // Only write once. This gives priority to processors with more priority
                state.insert(pair_key_vec);
        }

        //If all keys are filled return
        if (state.size() == structure.size())
        {
            return state;
        }
    }

    // check for empty blocks and fill them with the closest KF to ts, with the prior, or with zeros in the worst case

    VectorComposite state_last;

    auto last_kf = trajectory_ptr_->closestFrameToTimeStamp(_ts);

    if (last_kf)
        state_last = last_kf->getState(structure);

    else if (prior_options_ and prior_options_->mode != "nothing")
        state_last = prior_options_->state;

    for (auto key : structure)
    {
        if (state.count(key) == 0)
        {
            auto state_last_it = state_last.find(key);

            if (state_last_it != state_last.end())
                state.emplace(key, state_last_it->second);

            else
                state.emplace(key, stateZero(string(1,key)).at(key));
        }
    }

    return state;
}

VectorComposite Problem::getOdometry(const StateStructure& _structure) const
{
    StateStructure structure = (_structure == "" ? getFrameStructure() : _structure);

    VectorComposite odom_state;

    // compose the states of all MotionProvider processors (ordered by user-defined priority) into one only state
    for (auto prc_pair : motion_provider_map_)
    {
        auto prc_state = prc_pair.second->getOdometry();

        // transfer processor vector blocks to problem state
        for (auto pair_key_vec : prc_state)
        {
            if (odom_state.count(pair_key_vec.first) == 0) // Only write once. This gives priority to processors with more priority
                odom_state.insert(pair_key_vec);
        }
    }

    // check for empty blocks and fill them with the the prior, or with zeros in the worst case

    VectorComposite state_last;

    if (prior_options_ and prior_options_->mode != "nothing")
        state_last = prior_options_->state;

    for (auto key : structure)
    {
        if (odom_state.count(key) == 0)
        {
            auto state_last_it = state_last.find(key);

            if (state_last_it != state_last.end())
                odom_state.emplace(key, state_last_it->second);

            else
                odom_state.emplace(key, stateZero(string(1,key)).at(key));
        }
    }

    return odom_state;
}

SizeEigen Problem::getDim() const
{
    return dim_;
}

const StateStructure& Problem::getFrameStructure() const
{
    return frame_structure_;
}

void Problem::appendToStructure(const StateStructure& _structure)
{
    for (auto key : _structure)
        if (frame_structure_.find(key) == std::string::npos) // now key not found in problem structure -> append!
            frame_structure_ += key;
}

void Problem::setTreeManager(TreeManagerBasePtr _gm)
{
    if (tree_manager_)
        tree_manager_->setProblem(nullptr);
    tree_manager_ = _gm;
    tree_manager_->setProblem(shared_from_this());
}

void Problem::addMotionProvider(MotionProviderPtr _motion_provider_ptr)
{
    // Check if is state getter
    if (not _motion_provider_ptr->isStateGetter())
    {
        WOLF_WARN("Problem::addMotionProvider: adding a MotionProvider processor with state_getter=false. Not adding this processor");
        return;
    }

    // check duplicated priority
    while (motion_provider_map_.count(_motion_provider_ptr->getStatePriority()) == 1)
    {
        WOLF_ERROR("Problem::addMotionProvider: adding a MotionProvider processor with priority = ",
                   _motion_provider_ptr->getStatePriority(),
                   " which is already taken. Trying to add it with priority = ",
                   _motion_provider_ptr->getStatePriority()+1);
        _motion_provider_ptr->setStatePriority(_motion_provider_ptr->getStatePriority()+1);
    }

    // add to map ordered by priority
    motion_provider_map_.emplace(_motion_provider_ptr->getStatePriority(), _motion_provider_ptr);
    appendToStructure(_motion_provider_ptr->getStateStructure());
}

void Problem::removeMotionProvider(MotionProviderPtr proc)
{
    WOLF_WARN_COND(motion_provider_map_.count(proc->getStatePriority()) == 0, "Problem::clearMotionProvider: missing processor");

    motion_provider_map_.erase(proc->getStatePriority());

    // rebuild frame structure with remaining motion processors
    frame_structure_.clear();
    for (auto pm : motion_provider_map_)
        appendToStructure(pm.second->getStateStructure());
}

VectorComposite Problem::stateZero ( const StateStructure& _structure ) const
{
    StateStructure structure = (_structure == "" ? getFrameStructure() : _structure);

    VectorComposite state;
    for (auto key : structure)
    {
        VectorXd vec;
        if (key == 'O')
        {
            if (dim_ == 2) vec = VectorXd::Zero(1);
            else if (dim_ == 3) vec = Quaterniond::Identity().coeffs();
        }
        else
        {
            vec = VectorXd::Zero(dim_);
        }
        state.emplace(key, vec);
    }
    return state;
}

bool Problem::permitKeyFrame(ProcessorBasePtr _processor_ptr) const
{
    // This should implement a black list of processors that have forbidden keyframe creation
    // This decision is made at problem level, not at processor configuration level.
    // If you want to configure a processor for not creating keyframes, see Processor::voting_active_ and its accessors.

    // Currently allowing all processors to vote:
    return true;
}

void Problem::keyFrameCallback(FrameBasePtr _keyframe_ptr, ProcessorBasePtr _processor_ptr)
{
    WOLF_DEBUG_COND(_processor_ptr!=nullptr,(_processor_ptr->isMotionProvider() ? "PM " : "PT "), _processor_ptr->getName(), ": KF", _keyframe_ptr->id(), " Callback emitted with ts = ", _keyframe_ptr->getTimeStamp());
    WOLF_DEBUG_COND(_processor_ptr==nullptr,"External callback: KF", _keyframe_ptr->id(), " Callback emitted with ts = ", _keyframe_ptr->getTimeStamp());

    // pause processor profiling
    if (_processor_ptr)
        _processor_ptr->stopCaptureProfiling();

    for (auto sensor : hardware_ptr_->getSensorList())
        for (auto processor : sensor->getProcessorList())
            if (processor && (processor != _processor_ptr) )
            {
#ifdef PROFILING
                auto start = std::chrono::high_resolution_clock::now();
#endif
                processor->keyFrameCallback(_keyframe_ptr);
#ifdef PROFILING
                auto stop     = std::chrono::high_resolution_clock::now();
                auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
                WOLF_INFO("keyFrameCallback Prc_id: ", processor->id(),
                          " Prc_type: ", processor->getType(),
                          " Prc_name: ", processor->getName(),
                          " keyframe_id: ", _keyframe_ptr->id(),
                          " keyframe_type: ", _keyframe_ptr->getType(),
                          " timestamp: ", _keyframe_ptr->getTimeStamp(),
                          " microseconds: ", duration.count());
#endif
            }

    // notify tree manager
    if (tree_manager_)
        tree_manager_->keyFrameCallback(_keyframe_ptr);

    // resume processor profiling
    if (_processor_ptr)
        _processor_ptr->startCaptureProfiling();
}

StateBlockPtr Problem::notifyStateBlock(StateBlockPtr _state_ptr, Notification _noti)
{
    std::lock_guard<std::mutex> lock(mut_notifications_);
    //std::cout << "Problem::addStateBlockPtr " << _state_ptr.get() << std::endl;

    // Check if there is already a notification for this state block
    auto notification_it = state_block_notification_map_.find(_state_ptr);

    // exsiting notification for this state block
    if (notification_it != state_block_notification_map_.end())
    {
        // duplicated notification
        if ( notification_it->second == _noti)
        {
            WOLF_WARN("This notification has been already notified");
        }
        // opposite notification -> cancell out eachother
        else
            state_block_notification_map_.erase(notification_it);
    }
    // Add notification
    else
        state_block_notification_map_[_state_ptr] = _noti;

    return _state_ptr;
}

bool Problem::getStateBlockNotification(const StateBlockPtr& sb_ptr, Notification& notif) const
{
    std::lock_guard<std::mutex> lock(mut_notifications_);
    if (state_block_notification_map_.find(sb_ptr) == state_block_notification_map_.end())
        return false;

    notif = state_block_notification_map_.at(sb_ptr);
    return true;
}

FactorBasePtr Problem::notifyFactor(FactorBasePtr _factor_ptr, Notification _noti)
{
    std::lock_guard<std::mutex> lock(mut_notifications_);
    //std::cout << "Problem::addFactorPtr " << _factor_ptr->id() << std::endl;

    // Check if there is already the same notification for this factor
    auto notification_it = factor_notification_map_.find(_factor_ptr);
    // exsiting notification for this factor
    if (notification_it != factor_notification_map_.end())
    {
        // duplicated notification
        if (notification_it->second == _noti)
        {
            WOLF_WARN("This notification has been already notified");
        }
        // opposite notification -> cancell out eachother
        else
        {
            factor_notification_map_.erase(notification_it);
        }

    }
    // Add notification
    else
        factor_notification_map_[_factor_ptr] = _noti;

    return _factor_ptr;
}
bool Problem::getFactorNotification(const FactorBasePtr& fac_ptr, Notification& notif) const
{
    std::lock_guard<std::mutex> lock(mut_notifications_);
    if (factor_notification_map_.find(fac_ptr) == factor_notification_map_.end())
        return false;

    notif = factor_notification_map_.at(fac_ptr);
    return true;
}

void Problem::clearCovariance()
{
    std::lock_guard<std::mutex> lock(mut_covariances_);
    covariances_.clear();
}

void Problem::addCovarianceBlock(StateBlockPtr _state1, StateBlockPtr _state2, const Eigen::MatrixXd& _cov)
{
    assert(_state1->getLocalSize() == (unsigned int ) _cov.rows() && "wrong covariance block size");
    assert(_state2->getLocalSize() == (unsigned int ) _cov.cols() && "wrong covariance block size");

    std::lock_guard<std::mutex> lock(mut_covariances_);
    covariances_[std::pair<StateBlockPtr, StateBlockPtr>(_state1, _state2)] = _cov;
}

void Problem::addCovarianceBlock(StateBlockPtr _state1, const Eigen::MatrixXd& _cov)
{
    assert(_state1->getLocalSize() == (unsigned int ) _cov.rows() && "wrong covariance block size");
    assert(_state1->getLocalSize() == (unsigned int ) _cov.cols() && "wrong covariance block size");

    std::lock_guard<std::mutex> lock(mut_covariances_);
    covariances_[std::make_pair(_state1, _state1)] = _cov;
}

bool Problem::getCovarianceBlock(StateBlockConstPtr _state1, 
                                 StateBlockConstPtr _state2, 
                                 Eigen::MatrixXd& _cov, 
                                 const int _row,
                                 const int _col) const
{
    //std::cout << "entire cov to be filled:" << std::endl << _cov << std::endl;
    //std::cout << "_row " << _row << std::endl;
    //std::cout << "_col " << _col << std::endl;
    //std::cout << "_state1 tangent space size: " << _state1->getLocalSize() << std::endl;
    //std::cout << "_state2 tangent space size: " << _state2->getLocalSize() << std::endl;
    //std::cout << "part of cov to be filled:" << std::endl <<  _cov.block(_row, _col, _state1->getLocalSize(), _state2->getLocalSize()) << std::endl;
    //if (covariances_.find(std::pair<StateBlockPtr, StateBlockPtr>(_state1, _state2)) != covariances_.end())
    //    std::cout << "stored cov" << std::endl << covariances_[std::pair<StateBlockPtr, StateBlockPtr>(_state1, _state2)] << std::endl;
    //else if (covariances_.find(std::pair<StateBlockPtr, StateBlockPtr>(_state2, _state1)) != covariances_.end())
    //    std::cout << "stored cov" << std::endl << covariances_[std::pair<StateBlockPtr, StateBlockPtr>(_state2, _state1)].transpose() << std::endl;

    assert(_row + _state1->getLocalSize() <= _cov.rows() && _col + _state2->getLocalSize() <= _cov.cols() && "Problem::getCovarianceBlock: Bad matrix covariance size!");

    std::lock_guard<std::mutex> lock(mut_covariances_);

    if (covariances_.find(std::pair<StateBlockConstPtr, StateBlockConstPtr>(_state1, _state2)) != covariances_.end())
        _cov.block(_row, _col, _state1->getLocalSize(), _state2->getLocalSize()) =
                covariances_.at(std::pair<StateBlockConstPtr, StateBlockConstPtr>(_state1, _state2));
    else if (covariances_.find(std::pair<StateBlockConstPtr, StateBlockConstPtr>(_state2, _state1)) != covariances_.end())
        _cov.block(_row, _col, _state1->getLocalSize(), _state2->getLocalSize()) =
                covariances_.at(std::pair<StateBlockConstPtr, StateBlockConstPtr>(_state2, _state1)).transpose();
    else
    {
      WOLF_DEBUG("Could not find the requested covariance block.");
      return false;
    }

    return true;
}
bool Problem::getCovarianceBlock(std::map<StateBlockConstPtr, unsigned int> _sb_2_idx, Eigen::MatrixXd& _cov) const
{
    std::lock_guard<std::mutex> lock(mut_covariances_);

    // fill _cov
    for (auto it1 = _sb_2_idx.begin(); it1 != _sb_2_idx.end(); it1++)
        for (auto it2 = it1; it2 != _sb_2_idx.end(); it2++)
        {
            auto sb1 = it1->first;
            auto sb2 = it2->first;
            std::pair<StateBlockConstPtr, StateBlockConstPtr> pair_12(sb1, sb2);
            std::pair<StateBlockConstPtr, StateBlockConstPtr> pair_21(sb2, sb1);

            // search st1 & st2
            if (covariances_.find(pair_12) != covariances_.end())
            {
                assert(_sb_2_idx[sb1] + sb1->getLocalSize() <= _cov.rows() &&
                       _sb_2_idx[sb2] + sb2->getLocalSize() <= _cov.cols() && "Problem::getCovarianceBlock: Bad matrix covariance size!");
                assert(_sb_2_idx[sb2] + sb2->getLocalSize() <= _cov.rows() &&
                       _sb_2_idx[sb1] + sb1->getLocalSize() <= _cov.cols() && "Problem::getCovarianceBlock: Bad matrix covariance size!");

                _cov.block(_sb_2_idx[sb1], _sb_2_idx[sb2], sb1->getLocalSize(), sb2->getLocalSize()) = covariances_.at(pair_12);
                _cov.block(_sb_2_idx[sb2], _sb_2_idx[sb1], sb2->getLocalSize(), sb1->getLocalSize()) = covariances_.at(pair_12).transpose();
            }
            else if (covariances_.find(pair_21) != covariances_.end())
            {
                assert(_sb_2_idx[sb1] + sb1->getLocalSize() <= _cov.rows() &&
                       _sb_2_idx[sb2] + sb2->getLocalSize() <= _cov.cols() && "Problem::getCovarianceBlock: Bad matrix covariance size!");
                assert(_sb_2_idx[sb2] + sb2->getLocalSize() <= _cov.rows() &&
                       _sb_2_idx[sb1] + sb1->getLocalSize() <= _cov.cols() && "Problem::getCovarianceBlock: Bad matrix covariance size!");

                _cov.block(_sb_2_idx[sb1], _sb_2_idx[sb2], sb1->getLocalSize(), sb2->getLocalSize()) = covariances_.at(pair_21).transpose();
                _cov.block(_sb_2_idx[sb2], _sb_2_idx[sb1], sb2->getLocalSize(), sb1->getLocalSize()) = covariances_.at(pair_21);
            }
            else
                return false;
        }

    return true;
}

bool Problem::getCovarianceBlock(StateBlockConstPtr _state, Eigen::MatrixXd& _cov, const int _row_and_col) const
{
    return getCovarianceBlock(_state, _state, _cov, _row_and_col, _row_and_col);
}

bool Problem::getFrameCovariance(FrameBaseConstPtr _frame_ptr, Eigen::MatrixXd& _covariance) const
{
    bool success(true);

    // resizing
    SizeEigen sz = _frame_ptr->getLocalSize();
    _covariance.resize(sz, sz);

    // filling covariance
    int i = 0, j = 0;
    for (auto sb_i : _frame_ptr->getStateBlockVec())
    {
        j = 0;
        for (auto sb_j : _frame_ptr->getStateBlockVec())
        {
            success = success && getCovarianceBlock(sb_i, sb_j, _covariance, i, j);
            j += sb_j->getLocalSize();
        }
        i += sb_i->getLocalSize();
    }

    return success;
}
bool Problem::getLastFrameCovariance(Eigen::MatrixXd& cov) const
{
    auto frm = getLastFrame();
    return getFrameCovariance(frm, cov);
}

bool Problem::getLandmarkCovariance(LandmarkBaseConstPtr _landmark_ptr, Eigen::MatrixXd& _covariance) const
{
    bool success(true);

    // resizing
    SizeEigen sz = _landmark_ptr->getLocalSize();
    _covariance.resize(sz, sz);

    // filling covariance
    int i = 0, j = 0;
    for (auto sb_i : _landmark_ptr->getStateBlockVec())
    {
        j = 0;
        for (auto sb_j : _landmark_ptr->getStateBlockVec())
        {
            success = success && getCovarianceBlock(sb_i, sb_j, _covariance, i, j);
            j += sb_j->getLocalSize();
        }
        i += sb_i->getLocalSize();
    }

    return success;
}

MapBaseConstPtr Problem::getMap() const
{
    return map_ptr_;
}

MapBasePtr Problem::getMap()
{
    return map_ptr_;
}

void Problem::setMap(MapBasePtr _map_ptr)
{
    assert(map_ptr_ == nullptr && "map has already been set");

    map_ptr_ = _map_ptr;
}

TrajectoryBaseConstPtr Problem::getTrajectory() const
{
    return trajectory_ptr_;
}

TrajectoryBasePtr Problem::getTrajectory()
{
    return trajectory_ptr_;
}

HardwareBaseConstPtr Problem::getHardware() const
{
    return hardware_ptr_;
}

HardwareBasePtr Problem::getHardware()
{
    return hardware_ptr_;
}

FrameBaseConstPtr Problem::getLastFrame() const
{
    return trajectory_ptr_->getLastFrame();
}

FrameBasePtr Problem::getLastFrame()
{
    return trajectory_ptr_->getLastFrame();
}

FrameBaseConstPtr Problem::closestFrameToTimeStamp(const TimeStamp& _ts) const
{
    return trajectory_ptr_->closestFrameToTimeStamp(_ts);
}

FrameBasePtr Problem::closestFrameToTimeStamp(const TimeStamp& _ts)
{
    return trajectory_ptr_->closestFrameToTimeStamp(_ts);
}

void Problem::setPriorOptions(const std::string& _mode,
                              const VectorComposite& _state ,
                              const VectorComposite& _sigma   )
{
    assert(prior_options_ != nullptr && "prior options have already been applied");
    assert(prior_options_->mode == "" && "prior options have already been set");
    assert((_mode == "nothing" || _mode == "initial_guess" || _mode == "fix" || _mode == "factor") && "wrong _mode value, it should be: 'nothing', 'initial_guess', 'fix' or 'factor'");

    // Store options (optionals depending on the mode)
    WOLF_TRACE("prior mode:           ", _mode);
    prior_options_->mode = _mode;

    if (prior_options_->mode != "nothing")
    {
        assert(_state.includesStructure(frame_structure_) && "any missing key in prior state");

        WOLF_TRACE("prior state:          ", _state);
        prior_options_->state = _state;

        if (prior_options_->mode == "factor")
        {
            assert(_sigma.includesStructure(frame_structure_) && "any missing key in prior sigma");

            bool isPositive = true;
            for(const auto& it: _sigma)
                isPositive = isPositive and (it.second.array() > Constants::EPS).all();

            assert(isPositive && "prior sigma is not positive");

            MatrixComposite Q;
            for (const auto& pair_key_sig : _sigma)
            {
                const auto& key = pair_key_sig.first;
                const auto& sig_blk = pair_key_sig.second;

                const auto& cov_blk = (sig_blk.array() * sig_blk.array()).matrix().asDiagonal();
                Q.emplace(key,key,cov_blk);
            }
            WOLF_TRACE("prior covariance:"    , Q);
            prior_options_->cov = Q;
        }
    }
}

FrameBasePtr Problem::applyPriorOptions(const TimeStamp& _ts)
{
    assert(!isPriorSet() && "applyPriorOptions can be called once!");

    FrameBasePtr prior_keyframe(nullptr);

    if (prior_options_->mode != "nothing" and prior_options_->mode != "")
    {
        prior_keyframe = emplaceFrame(_ts, frame_structure_, prior_options_->state);

        // Update origin for odometry processors
        for (auto proc_pair : motion_provider_map_)
            proc_pair.second->setOdometry(prior_options_->state);

        if (prior_options_->mode == "fix")
            prior_keyframe->fix();
        else if (prior_options_->mode == "factor")
        {
            // ASSUMPTION: Independent measurements (non-correlated block-diagonal covariance matrix)

            // Emplace a capture
            auto prior_cap = CaptureBase::emplace<CaptureVoid>(prior_keyframe, _ts, nullptr);

            // Emplace a feature and a factor for each state block
            for (auto pair_key_sb : prior_keyframe->getStateBlockMap())
            {
                auto key = pair_key_sb.first;
                auto sb  = pair_key_sb.second;

                const auto& state_blk = prior_options_->state.at(key);
                const auto& covar_blk = prior_options_->cov.at(key,key);

                assert(sb->getSize()      == state_blk.size() && "prior_options state wrong size");
                assert(sb->getLocalSize() == covar_blk.rows() && "prior_options cov. wrong size");

                // feature
                auto prior_fea = FeatureBase::emplace<FeatureBase>(prior_cap, "Prior " + string(1,key), state_blk, covar_blk);

                // factor
                if (sb->hasLocalParametrization())
                {
                    if (std::dynamic_pointer_cast<StateQuaternion>(sb) != nullptr)
                        auto prior_fac = FactorBase::emplace<FactorQuaternionAbsolute>(prior_fea, prior_fea, sb, nullptr, false);
                    else if (std::dynamic_pointer_cast<StateAngle>(sb) != nullptr)
                        auto prior_fac = FactorBase::emplace<FactorBlockAbsolute>(prior_fea, prior_fea, sb, nullptr, false);
                    else
                        throw std::runtime_error("not implemented...!");
                }
                else
                {
                    auto prior_fac = FactorBase::emplace<FactorBlockAbsolute>(prior_fea, prior_fea, sb, nullptr, false);
                }

            }

        }
        else
            assert(prior_options_->mode == "initial_guess" && "wrong prior_options->mode");

        // notify all processors
        keyFrameCallback(prior_keyframe, nullptr);
    }
    // remove prior options
    prior_options_ = nullptr;

    return prior_keyframe;
}

FrameBasePtr Problem::setPriorFactor(const VectorComposite &_state,
                                     const VectorComposite &_sigma,
                                     const TimeStamp &_ts)
{
    setPriorOptions("factor", _state, _sigma);
    return applyPriorOptions(_ts);
}


FrameBasePtr Problem::setPriorFix(const VectorComposite &_state,
                                  const TimeStamp &_ts)
{
    setPriorOptions("fix", _state);
    return applyPriorOptions(_ts);
}

FrameBasePtr Problem::setPriorInitialGuess(const VectorComposite &_state,
                                           const TimeStamp &_ts)
{
    setPriorOptions("initial_guess", _state);
    return applyPriorOptions(_ts);
}

void Problem::loadMap(const std::string& _filename_dot_yaml)
{
    getMap()->load(_filename_dot_yaml);
}

void Problem::saveMap(const std::string& _filename_dot_yaml, const std::string& _map_name)
{
    getMap()->save(_filename_dot_yaml, _map_name);
}

void Problem::print(int _depth, bool _constr_by, bool _metric, bool _state_blocks, std::ostream& _stream) const
{

    _stream << std::endl;
    _stream << "Wolf tree status ------------------------" << std::endl;

    getHardware()->print(_depth, _constr_by, _metric, _state_blocks, _stream, "");

    getTrajectory()->print(_depth, _constr_by, _metric, _state_blocks, _stream, "");

    getMap()->print(_depth, _constr_by, _metric, _state_blocks, _stream, "");

    _stream << "-----------------------------------------" << std::endl;
    _stream << std::endl;
}

bool Problem::check(bool _verbose, std::ostream& _stream) const
{

    CheckLog log(true, "");
    log.explanation_ = "## WOLF::problem inconsistencies list \n   ---------------------------------- \n";

    std::string tabs = "";

    if (_verbose) _stream << std::endl;
    if (_verbose) _stream << "Wolf tree integrity ---------------------" << std::endl;
    auto P = shared_from_this();
    if (_verbose)
    {
        _stream << "Prb @ " << P.get() << std::endl;
    }
    // ------------------------
    //     HARDWARE branch
    // ------------------------
    auto H = hardware_ptr_;
    H->check(log, H, _verbose, _stream, tabs);
    // ------------------------
    //    TRAJECTORY branch
    // ------------------------
    auto T = trajectory_ptr_;
    T->check(log, T, _verbose, _stream, tabs);

    // ------------------------
    //       MAP branch
    // ------------------------
    auto M = map_ptr_;
    M->check(log, M, _verbose, _stream, tabs);

    if (_verbose) _stream << "--------------------------- Wolf tree " << (log.is_consistent_ ? " OK" : "Not OK !!") << std::endl;
    if (_verbose) _stream << std::endl;
    if (_verbose and not log.is_consistent_) _stream << log.explanation_ << std::endl;

    return log.is_consistent_;
}

bool Problem::check(int _verbose_level) const
{
    return check((_verbose_level > 0), std::cout);
}

void Problem::perturb(double amplitude)
{
    // Sensors
    for (auto S : getHardware()->getSensorList())
        S->perturb(amplitude);

    // Frames and Captures
    for (auto F : getTrajectory()->getFrameMap())
    {
        F.second->perturb(amplitude);
        for (auto& C : F.second->getCaptureList())
            C->perturb(amplitude);
    }

    // Landmarks
    for (auto L : getMap()->getLandmarkList())
        L->perturb(amplitude);
}

void Problem::transform(const VectorComposite& _transformation)
{
    std::lock_guard<std::mutex> lock_transform (mut_transform_); // Protect especially from SolverManager::update()

    transformation_ = _transformation;
    transformed_.store(true);

    // Sensors
    for (auto S : getHardware()->getSensorList())
        S->transform(_transformation);

    // Frames and Captures
    for (auto F : getTrajectory()->getFrameMap())
    {
        F.second->transform(_transformation);
        for (auto& C : F.second->getCaptureList())
            C->transform(_transformation);
    }

    // Landmarks
    for (auto L : getMap()->getLandmarkList())
        L->transform(_transformation);
}

bool Problem::isTransformed() const
{
    return transformed_.load();
}
void Problem::resetTransformed()
{
    transformed_.store(false);
}
VectorComposite Problem::getTransformation() const
{
    std::lock_guard<std::mutex> lock(mut_transform_);
    return transformation_;
}
} // namespace wolf