diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cde987b69c7b41a95af481940cf7b7561e204637..65be5602a8e17b49dca77603899216467aabeae8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,71 +1,185 @@
-image: segaleran/ceres
+stages:
+  - license
+  - build_and_test
 
-before_script:
-  - ls
+############ YAML ANCHORS ############
+.preliminaries_template: &preliminaries_definition
+  ## Install ssh-agent if not already installed, it is required by Docker.
+  ## (change apt-get to yum if you use an RPM-based image)
+  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+
+  ## Run ssh-agent (inside the build environment)
+  - eval $(ssh-agent -s)
+
+  ## Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
+  ## We're using tr to fix line endings which makes ed25519 keys work
+  ## without extra base64 encoding.
+  ## https://gitlab.com/gitlab-examples/ssh-private-key/issues/1#note_48526556
+  - mkdir -p ~/.ssh
+  - chmod 700 ~/.ssh  
+  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
+  # - echo "$SSH_KNOWN_HOSTS" > $HOME/.ssh/known_hosts
+  - ssh-keyscan -H -p 2202 gitlab.iri.upc.edu >> $HOME/.ssh/known_hosts
+
+  # update apt
   - apt-get update
-  - apt-get install -y build-essential cmake 
-
-# SPDLOG
-#  - apt-get install -y libspdlog-dev
-  - if [ -d spdlog ]; then
-  -   echo "directory exists" 
-  -   if [ "$(ls -A ./spdlog)" ]; then 
-  -     echo "directory not empty" 
-  -     cd spdlog
-  -     git pull
-  -   else 
-  -     echo "directory empty" 
-  -     git clone https://github.com/gabime/spdlog.git
-  -     cd spdlog
-  -   fi
+
+  # create 'ci_deps' folder (if not exists)
+  - mkdir -pv ci_deps
+
+.license_header_template: &license_header_definition
+  - cd $CI_PROJECT_DIR
+
+  # configure git
+  - export CI_NEW_BRANCH=ci_processing$RANDOM
+  - echo creating new temporary branch... $CI_NEW_BRANCH
+  - git config --global user.email "${CI_EMAIL}"
+  - git config --global user.name "${CI_USERNAME}"
+  - git checkout -b $CI_NEW_BRANCH # temporary branch
+
+  # license headers
+  - export CURRENT_YEAR=$( date +'%Y' )
+  - echo "current year:" ${CURRENT_YEAR}
+  - if [ -f license_header_${CURRENT_YEAR}.txt ]; then
+      # add license headers to new files
+  -   echo "File license_header_${CURRENT_YEAR}.txt already exists. License headers are assumed to be updated. Adding headers to new files..."
+  -   ./ci_deps/wolf/wolf_scripts/license_manager.sh --add --path=. --license-header=license_header_${CURRENT_YEAR}.txt --exclude=ci_deps
   - else
-  -   echo "directory inexistent" 
-  -   git clone https://github.com/gabime/spdlog.git
-  -   cd spdlog
+      # update license headers of all files
+  -   export PREV_YEAR=$(( CURRENT_YEAR-1 ))
+  -   echo "Creating new file license_header_${CURRENT_YEAR}.txt..."
+  -   git mv license_header_${PREV_YEAR}.txt license_header_${CURRENT_YEAR}.txt
+  -   sed -i "s/${PREV_YEAR}/${PREV_YEAR},${CURRENT_YEAR}/g" license_header_${CURRENT_YEAR}.txt
+  -   ./ci_deps/wolf/wolf_scripts/license_manager.sh --update --path=. --license-header=license_header_${CURRENT_YEAR}.txt --exclude=ci_deps
+  - fi
+
+  # push changes (if any)
+  - if git commit -a -m "[skip ci] license headers added or modified" ; then
+  -   git remote set-url --push origin "ssh://git@gitlab.iri.upc.edu:2202/${CI_PROJECT_PATH}.git"
+  -   git push origin $CI_NEW_BRANCH:${CI_COMMIT_REF_NAME}
+  - else
+  -   echo "No changes, nothing to commit!"
+  - fi
+
+.install_wolf_template: &install_wolf_definition
+  - cd ${CI_PROJECT_DIR}/ci_deps
+  - if [ -d wolf ]; then
+  -   echo "directory wolf exists"
+  -   cd wolf
+  -   git checkout devel
+  -   git pull
+  -   git checkout $WOLF_CORE_BRANCH
+  - else
+  -   git clone ssh://git@gitlab.iri.upc.edu:2202/mobile_robotics/wolf_projects/wolf_lib/wolf.git
+  -   cd wolf
+  -   git checkout $WOLF_CORE_BRANCH
   - fi
-  - git fetch
-  - git checkout v0.17.0
   - mkdir -pv build
   - cd build
-  - ls
-  - cmake -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fPIC" -DSPDLOG_BUILD_TESTING=OFF ..
+  - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_DEMOS=OFF -DBUILD_TESTS=OFF ..
+  - make -j$(nproc)
   - make install
-  - cd ../..
-
-# YAML
-#  - apt-get install -y libyaml-cpp-dev
-  - if [ -d yaml-cpp ]; then
-  -   echo "directory exists" 
-  -   if [ "$(ls -A ./yaml-cpp)" ]; then 
-  -     echo "directory not empty" 
-  -     cd yaml-cpp
-  -     git pull
-  -   else 
-  -     echo "directory empty" 
-  -     git clone https://github.com/jbeder/yaml-cpp.git
-  -     cd yaml-cpp
-  -   fi
+
+.install_wolfimu_template: &install_wolfimu_definition
+  - cd ${CI_PROJECT_DIR}/ci_deps
+  - if [ -d imu ]; then
+  -   echo "directory imu exists"
+  -   cd imu
+  -   git pull
   - else
-  -   echo "directory inexistent" 
-  -   git clone https://github.com/jbeder/yaml-cpp.git
-  -   cd yaml-cpp
+  -   git clone ssh://git@gitlab.iri.upc.edu:2202/mobile_robotics/wolf_projects/wolf_lib/plugins/imu.git
+  -   cd imu
   - fi
   - mkdir -pv build
   - cd build
-  - ls
-  - cmake -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fPIC" -DYAML_CPP_BUILD_TESTS=OFF ..
+  - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_TESTS=OFF ..
+  - make -j$(nproc)
+  - make install
+
+.build_and_test_template: &build_and_test_definition
+  - cd $CI_PROJECT_DIR
+  - mkdir -pv build
+  - cd build
+  - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON ..
+  - make -j$(nproc)
+  - ctest -j$(nproc)
   - make install
-  - cd ../..
 
-wolf_build_and_test:
-  stage: build
+############ LICENSE HEADERS ############
+license_headers:
+  stage: license
+  image: labrobotica/wolf_deps:16.04
+  cache:
+    - key: wolf-xenial
+      paths:
+      - ci_deps/wolf/
+  except:
+    - master
+  before_script:
+    - *preliminaries_definition
+    - *install_wolf_definition
+  script:
+    - *license_header_definition
+
+############ UBUNTU 16.04 TESTS ############
+build_and_test:xenial:
+  image: labrobotica/wolf_deps:16.04
+  stage: build_and_test
+  cache:
+    - key: wolf-xenial
+      paths:
+      - ci_deps/wolf/
+    - key: imu-xenial
+      paths:
+      - ci_deps/imu/
+  except:
+    - master
+  before_script:
+    - *preliminaries_definition
+    - *install_wolf_definition
+    - *install_wolfimu_definition
+    - ldconfig # update links (shared libraries)
+  script:
+    - *build_and_test_definition
+
+############ UBUNTU 18.04 TESTS ############
+build_and_test:bionic:
+  image: labrobotica/wolf_deps:18.04
+  stage: build_and_test
+  cache:
+    - key: wolf-bionic
+      paths:
+      - ci_deps/wolf/
+    - key: imu-bionic
+      paths:
+      - ci_deps/imu/
+  except:
+    - master
+  before_script:
+    - *preliminaries_definition
+    - *install_wolf_definition
+    - *install_wolfimu_definition
+    - ldconfig # update links (shared libraries)
+  script:
+    - *build_and_test_definition
+
+############ UBUNTU 20.04 TESTS ############
+build_and_test:focal:
+  image: labrobotica/wolf_deps:20.04
+  stage: build_and_test
+  cache:
+    - key: wolf-focal
+      paths:
+      - ci_deps/wolf/
+    - key: imu-focal
+      paths:
+      - ci_deps/imu/
   except:
     - master
+  before_script:
+    - *preliminaries_definition
+    - *install_wolf_definition
+    - *install_wolfimu_definition
+    - ldconfig # update links (shared libraries)
   script:
-    - mkdir -pv build
-    - cd build
-    - ls # we can check whether the directory was already full
-    - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON ..
-    - make -j$(nproc)
-    - ctest -j$(nproc)
-    - make install
+    - *build_and_test_definition
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9fb769bb08cee91fc6f3a70c105eb722e808544a..b2d37d37685bef61e1d3478e941593df4a675be8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 # Pre-requisites about cmake itself
-CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
 
 if(COMMAND cmake_policy)
   cmake_policy(SET CMP0005 NEW)
@@ -8,11 +8,12 @@ endif(COMMAND cmake_policy)
 # MAC OSX RPATH
 SET(CMAKE_MACOSX_RPATH 1)
 
-
 # The project name
 PROJECT(bodydynamics)
 set(PLUGIN_NAME wolf${PROJECT_NAME})
 
+MESSAGE("Starting ${PROJECT_NAME} CMakeLists ...")
+
 SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
 SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
 SET(CMAKE_INSTALL_PREFIX /usr/local)
@@ -26,18 +27,14 @@ message(STATUS "Configured to compile in ${CMAKE_BUILD_TYPE} mode.")
 SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -D_REENTRANT")
 SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -D_REENTRANT")
 
-#Set compiler according C++11 support
+#Set compiler according C++14 support
 include(CheckCXXCompilerFlag)
-CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
-CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
-if(COMPILER_SUPPORTS_CXX11)
-		message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++11 support.")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
-elseif(COMPILER_SUPPORTS_CXX0X)
-		message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++0x support.")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
+CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14)
+if(COMPILER_SUPPORTS_CXX14)
+		message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++14 support.")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
 else()
-  message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
+  message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++14 support. Please use a different C++ compiler.")
 endif()
 
 if(UNIX)
@@ -46,9 +43,18 @@ if(UNIX)
     "${CMAKE_CXX_FLAGS} -Werror=all -Werror=extra -Wno-unknown-pragmas -Wno-sign-compare -Wno-unused-parameter -Wno-missing-field-initializers")
 endif(UNIX)
 
+IF(NOT BUILD_TESTS)
+  OPTION(BUILD_TESTS "Build Unit tests" ON)
+ENDIF(NOT BUILD_TESTS)
+
+IF(NOT BUILD_DEMOS)
+  OPTION(BUILD_DEMOS "Build demos in demos folder, requires robotpkg-multicontact-api and robotpkg-pinocchio packages" OFF)
+ENDIF(NOT BUILD_DEMOS)
+
+IF(NOT BUILD_DOC)
+  OPTION(BUILD_DOC "Build Documentation" OFF)
+ENDIF(NOT BUILD_DOC)
 
-#OPTION(BUILD_DOC "Build Documentation" OFF)
-OPTION(BUILD_TESTS "Build Unit tests" ON)
 #############
 ## Testing ##
 #############
@@ -61,42 +67,24 @@ if(BUILD_TESTS)
     enable_testing()
 endif()
 
-#+START_SRC --------------------------------------------------------------------------------------------------------------------------------
-#Start WOLF build
-MESSAGE("Starting WOLF CMakeLists ...")
-CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
-
 #CMAKE modules
-
 SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules")
 MESSAGE(STATUS ${CMAKE_MODULE_PATH})
 
 # Some wolf compilation options
-
 IF((CMAKE_BUILD_TYPE MATCHES DEBUG) OR (CMAKE_BUILD_TYPE MATCHES debug) OR (CMAKE_BUILD_TYPE MATCHES Debug))
   set(_WOLF_DEBUG true)
 ENDIF()
 
 option(_WOLF_TRACE "Enable wolf tracing macro" ON)
 
-# option(BUILD_EXAMPLES "Build examples" OFF)
-set(BUILD_TESTS true)
-set(BUILD_EXAMPLES false)
-
-# Does this has any other interest
-# but for the examples ?
-# yes, for the tests !
-IF(BUILD_EXAMPLES OR BUILD_TESTS)
-  string(TOUPPER ${PROJECT_NAME} UPPER_NAME)
-  set(_WOLF_ROOT_DIR ${CMAKE_SOURCE_DIR})
-ENDIF(BUILD_EXAMPLES OR BUILD_TESTS)
-
-message("UPPER_NAME ${UPPER_NAME}")
-
+# ============ DEPENDENCIES  ============
+FIND_PACKAGE(wolfcore REQUIRED)
+FIND_PACKAGE(wolfimu REQUIRED)
 
-#find dependencies.
-FIND_PACKAGE(wolf REQUIRED)
-FIND_PACKAGE(wolfIMU REQUIRED)
+# ============ CONFIG.H ============ 
+string(TOUPPER ${PROJECT_NAME} UPPER_NAME)
+set(_WOLF_ROOT_DIR ${CMAKE_SOURCE_DIR})
 
 # Define the directory where will be the configured config.h
 SET(WOLF_CONFIG_DIR ${PROJECT_BINARY_DIR}/conf/${PROJECT_NAME}/internal)
@@ -112,124 +100,93 @@ IF(EXISTS "${WOLF_CONFIG_DIR}" AND NOT IS_DIRECTORY "${WOLF_CONFIG_DIR}")
 ENDIF()
 # Configure config.h
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/internal/config.h.in "${WOLF_CONFIG_DIR}/config.h")
-message("WOLF CONFIG ${WOLF_CONFIG_DIR}/config.h")
-message("CONFIG DIRECTORY ${PROJECT_BINARY_DIR}")
 include_directories("${PROJECT_BINARY_DIR}/conf")
 
-include_directories("include")
-include_directories(${wolf_INCLUDE_DIRS})
+# ============ INCLUDES ============ 
+INCLUDE_DIRECTORIES(${wolfimu_INCLUDE_DIRS})
+INCLUDE_DIRECTORIES(BEFORE "include")
 
-#HEADERS
-
-SET(HDRS_COMMON
-  )
+# ============ HEADERS ============ 
 SET(HDRS_MATH
 include/bodydynamics/math/force_torque_delta_tools.h
   )
-SET(HDRS_UTILS
-  )
-SET(HDRS_PROBLEM
-  )
-SET(HDRS_HARDWARE
-  )
-SET(HDRS_TRAJECTORY
-  )
-SET(HDRS_MAP
-  )
-SET(HDRS_FRAME
-  )
-SET(HDRS_STATE_BLOCK
-  )
 SET(HDRS_CAPTURE
+include/bodydynamics/capture/capture_force_torque_preint.h
+include/bodydynamics/capture/capture_inertial_kinematics.h
+include/bodydynamics/capture/capture_leg_odom.h
+include/bodydynamics/capture/capture_point_feet_nomove.h
   )
 SET(HDRS_FACTOR
-include/bodydynamics/factor/factor_autodiff_inertial_kinematics.h
 include/bodydynamics/factor/factor_force_torque.h
+include/bodydynamics/factor/factor_force_torque_preint.h
+include/bodydynamics/factor/factor_inertial_kinematics.h
+include/bodydynamics/factor/factor_point_feet_nomove.h
   )
 SET(HDRS_FEATURE
-include/bodydynamics/feature/feature_inertial_kinematics.h
 include/bodydynamics/feature/feature_force_torque.h
-  )
-SET(HDRS_LANDMARK
+include/bodydynamics/feature/feature_force_torque_preint.h
+include/bodydynamics/feature/feature_inertial_kinematics.h
   )
 SET(HDRS_PROCESSOR
+include/bodydynamics/processor/processor_force_torque_preint.h
+include/bodydynamics/processor/processor_inertial_kinematics.h
+include/bodydynamics/processor/processor_point_feet_nomove.h
   )
 SET(HDRS_SENSOR
-  )
-SET(HDRS_SOLVER
-  )
-SET(HDRS_DTASSC
-  )
-SET(HDRS_YAML
+include/bodydynamics/sensor/sensor_force_torque.h
+include/bodydynamics/sensor/sensor_inertial_kinematics.h
+include/bodydynamics/sensor/sensor_point_feet_nomove.h
   )
 
-#SOURCES
-SET(SRCS_PROBLEM
-  )
-SET(SRCS_HARDWARE
-  )
-SET(SRCS_TRAJECTORY
-  )
-SET(SRCS_MAP
-  )
-SET(SRCS_FRAME
-  )
-SET(SRCS_STATE_BLOCK
-  )
-SET(SRCS_COMMON
-  )
-SET(SRCS_MATH
-  )
-SET(SRCS_UTILS
-  )
+# ============ SOURCES ============ 
 SET(SRCS_CAPTURE
-  )
-SET(SRCS_FACTOR
-  )
+src/capture/capture_force_torque_preint.cpp
+src/capture/capture_inertial_kinematics.cpp
+src/capture/capture_leg_odom.cpp
+src/capture/capture_point_feet_nomove.cpp
+)
 SET(SRCS_FEATURE
-src/feature/feature_inertial_kinematics.cpp
 src/feature/feature_force_torque.cpp
-  )
-SET(SRCS_LANDMARK
+src/feature/feature_force_torque_preint.cpp
+src/feature/feature_inertial_kinematics.cpp
   )
 SET(SRCS_PROCESSOR
-  )
+src/processor/processor_force_torque_preint.cpp
+src/processor/processor_inertial_kinematics.cpp
+src/processor/processor_point_feet_nomove.cpp
+)
 SET(SRCS_SENSOR
-  )
-SET(SRCS_DTASSC
-  )
-SET(SRCS_SOLVER
-  )
-SET(SRCS_YAML
+src/sensor/sensor_force_torque.cpp
+src/sensor/sensor_inertial_kinematics.cpp
+src/sensor/sensor_point_feet_nomove.cpp
   )
 
 # create the shared library
 ADD_LIBRARY(${PLUGIN_NAME}
   SHARED
   ${SRCS_CAPTURE}
-  ${SRCS_COMMON}
-  ${SRCS_DTASSC}
-  ${SRCS_FACTOR}
   ${SRCS_FEATURE}
-  ${SRCS_FRAME}
-  ${SRCS_HARDWARE}
-  ${SRCS_LANDMARK}
-  ${SRCS_MAP}
-  ${SRCS_MATH}
-  ${SRCS_PROBLEM}
   ${SRCS_PROCESSOR}
   ${SRCS_SENSOR}
-  ${SRCS_SOLVER}
-  ${SRCS_STATE_BLOCK}
-  ${SRCS_TRAJECTORY}
-  ${SRCS_UTILS}
-  ${SRCS_WRAPPER}
-  ${SRCS_YAML}
   )
-TARGET_LINK_LIBRARIES(${PLUGIN_NAME} ${wolf_LIBRARIES})
-# Link the library with the required dependencies
-# TARGET_LINK_LIBRARIES(${PLUGIN_NAME} )
 
+# Set compiler options
+# ====================
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+  message(STATUS "Using C++ compiler clang")
+  target_compile_options(${PLUGIN_NAME} PRIVATE -Winconsistent-missing-override)
+  # using Clang
+elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  message(STATUS "Using C++ compiler gnu")
+  target_compile_options(${PLUGIN_NAME} PRIVATE -Wsuggest-override)
+  # using GCC
+endif()
+
+#Link the created libraries
+#===============EXAMPLE=========================
+TARGET_LINK_LIBRARIES(${PLUGIN_NAME} ${wolfimu_LIBRARIES})
+
+# Build demos
 IF(BUILD_DEMOS)
   #Build examples
   MESSAGE("Building demos.")
@@ -237,68 +194,36 @@ IF(BUILD_DEMOS)
 ENDIF(BUILD_DEMOS)
 
 #Build tests
-#===============EXAMPLE=========================
 IF(BUILD_TESTS)
   MESSAGE("Building tests.")
   add_subdirectory(test)
 ENDIF(BUILD_TESTS)
 
 #install library
-
 #=============================================================
 INSTALL(TARGETS ${PLUGIN_NAME} EXPORT ${PLUGIN_NAME}Targets
   RUNTIME DESTINATION bin
-  LIBRARY DESTINATION lib/iri-algorithms
-  ARCHIVE DESTINATION lib/iri-algorithms)
+  LIBRARY DESTINATION lib
+  ARCHIVE DESTINATION lib)
 
 install(EXPORT ${PLUGIN_NAME}Targets DESTINATION lib/cmake/${PLUGIN_NAME})
 #install headers
-INSTALL(FILES ${HDRS_MATH}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/math)
-INSTALL(FILES ${HDRS_UTILS}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/utils)
-INSTALL(FILES ${HDRS_PROBLEM}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/problem)
-INSTALL(FILES ${HDRS_HARDWARE}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/hardware)
-INSTALL(FILES ${HDRS_TRAJECTORY}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/trajectory)
-INSTALL(FILES ${HDRS_MAP}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/map)
-INSTALL(FILES ${HDRS_FRAME}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/frame)
-INSTALL(FILES ${HDRS_STATE_BLOCK}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/state_block)
-INSTALL(FILES ${HDRS_COMMON}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/common)
-INSTALL(FILES ${HDRS_DTASSC}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/association)
 INSTALL(FILES ${HDRS_CAPTURE}
    DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/capture)
 INSTALL(FILES ${HDRS_FACTOR}
   DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/factor)
 INSTALL(FILES ${HDRS_FEATURE}
   DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/feature)
-INSTALL(FILES ${HDRS_SENSOR}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/sensor)
+INSTALL(FILES ${HDRS_MATH}
+  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/math)
 INSTALL(FILES ${HDRS_PROCESSOR}
   DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/processor)
-INSTALL(FILES ${HDRS_LANDMARK}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/landmark)
-INSTALL(FILES ${HDRS_WRAPPER}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/ceres_wrapper)
-INSTALL(FILES ${HDRS_SOLVER_SUITESPARSE}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/solver_suitesparse)
-INSTALL(FILES ${HDRS_SOLVER}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/solver)
-INSTALL(FILES ${HDRS_YAML}
-  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/yaml)
+INSTALL(FILES ${HDRS_SENSOR}
+  DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/sensor)
 
 FILE(WRITE ${PROJECT_NAME}.found "")
 INSTALL(FILES ${PROJECT_NAME}.found
   DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME})
-
-
 INSTALL(FILES "${WOLF_CONFIG_DIR}/config.h"
 DESTINATION include/iri-algorithms/wolf/plugin_${PROJECT_NAME}/${PROJECT_NAME}/internal)
 
@@ -306,7 +231,6 @@ INSTALL(FILES "${CMAKE_SOURCE_DIR}/cmake_modules/${PLUGIN_NAME}Config.cmake" DES
 
 export(PACKAGE ${PLUGIN_NAME})
 
-#-END_SRC --------------------------------------------------------------------------------------------------------------------------------
 FIND_PACKAGE(Doxygen)
 
 FIND_PATH(IRI_DOC_DIR doxygen.conf ${CMAKE_SOURCE_DIR}/doc/iri_doc/)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..281d399f13dfd23087acc56303dd38d68162587c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,619 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
diff --git a/README.md b/README.md
index 7a6bdb0a88395a96d4a97deecf464c691dab1c5d..a57f7feee2fcd5c8e128af029ced7c7fcf31dec4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 WOLF - Windowed Localization Frames | bodydynamics Plugin
 ===================================
 
-For installation guide and code documentation, please visit the [documentation website](http://mobile_robotics.pages.iri.upc-csic.es/wolf_projects/wolf_lib/wolf_doc/).
+For installation guide and code documentation, please visit the [documentation website](http://mobile_robotics.pages.iri.upc-csic.es/wolf_projects/wolf_lib/wolf-doc-sphinx/).
 
-TODO
\ No newline at end of file
+TODO
diff --git a/cmake_modules/wolfConfig.cmake b/cmake_modules/wolfConfig.cmake
deleted file mode 100644
index 4ff944f2ffccea98faba230f382774a82f6ff2cc..0000000000000000000000000000000000000000
--- a/cmake_modules/wolfConfig.cmake
+++ /dev/null
@@ -1,89 +0,0 @@
-#edit the following line to add the librarie's header files
-FIND_PATH(
-    wolfbodydynamics_INCLUDE_DIRS
-    NAMES bodydynamics.found
-    PATHS /usr/local/include/iri-algorithms/wolf/plugin_bodydynamics)
-IF(wolfbodydynamics_INCLUDE_DIRS)
-  MESSAGE("Found bodydynamics include dirs: ${wolfbodydynamics_INCLUDE_DIRS}")
-ELSE(wolfbodydynamics_INCLUDE_DIRS)
-  MESSAGE("Couldn't find bodydynamics include dirs")
-ENDIF(wolfbodydynamics_INCLUDE_DIRS)
-
-FIND_LIBRARY(
-    wolfbodydynamics_LIBRARIES
-    NAMES libwolfbodydynamics.so libwolfbodydynamics.dylib
-    PATHS /usr/local/lib/iri-algorithms)
-IF(wolfbodydynamics_LIBRARIES)
-  MESSAGE("Found bodydynamics lib: ${wolfbodydynamics_LIBRARIES}")
-ELSE(wolfbodydynamics_LIBRARIES)
-  MESSAGE("Couldn't find wolf bodydynamics lib")
-ENDIF(wolfbodydynamics_LIBRARIES)
-
-IF (wolfbodydynamics_INCLUDE_DIRS AND wolfbodydynamics_LIBRARIES)
-   SET(wolfbodydynamics_FOUND TRUE)
- ELSE(wolfbodydynamics_INCLUDE_DIRS AND wolfbodydynamics_LIBRARIES)
-   set(wolfbodydynamics_FOUND FALSE)
-ENDIF (wolfbodydynamics_INCLUDE_DIRS AND wolfbodydynamics_LIBRARIES)
-
-IF (wolfbodydynamics_FOUND)
-   IF (NOT wolfbodydynamics_FIND_QUIETLY)
-      MESSAGE(STATUS "Found bodydynamics: ${wolfbodydynamics_LIBRARIES}")
-   ENDIF (NOT wolfbodydynamics_FIND_QUIETLY)
-ELSE (wolfbodydynamics_FOUND)
-   IF (wolfbodydynamics_FIND_REQUIRED)
-      MESSAGE(FATAL_ERROR "Could not find wolf bodydynamics")
-   ENDIF (wolfbodydynamics_FIND_REQUIRED)
-ENDIF (wolfbodydynamics_FOUND)
-
-
-macro(wolf_report_not_found REASON_MSG)
-  set(wolfbodydynamics_FOUND FALSE)
-  unset(wolfbodydynamics_INCLUDE_DIRS)
-  unset(wolfbodydynamics_LIBRARIES)
-
-  # Reset the CMake module path to its state when this script was called.
-  set(CMAKE_MODULE_PATH ${CALLERS_CMAKE_MODULE_PATH})
-
-  # Note <package>_FIND_[REQUIRED/QUIETLY] variables defined by
-  # FindPackage() use the camelcase library name, not uppercase.
-  if (wolfbodydynamics_FIND_QUIETLY)
-    message(STATUS "Failed to find wolfbodydynamics- " ${REASON_MSG} ${ARGN})
-  else (wolfbodydynamics_FIND_REQUIRED)
-    message(FATAL_ERROR "Failed to find wolfbodydynamics - " ${REASON_MSG} ${ARGN})
-  else()
-    # Neither QUIETLY nor REQUIRED, use SEND_ERROR which emits an error
-    # that prevents generation, but continues configuration.
-    message(SEND_ERROR "Failed to find wolfbodydynamics - " ${REASON_MSG} ${ARGN})
-  endif ()
-  return()
-endmacro(wolf_report_not_found)
-
-if(NOT wolfbodydynamics_FOUND)
-  wolf_report_not_found("Something went wrong while setting up wolf bodydynamics.")
-endif(NOT wolfbodydynamics_FOUND)
-# Set the include directories for wolf (itself).
-set(wolfbodydynamics_FOUND TRUE)
-
-# Suppose that our plugin requires openCV & vision_utils
-
-FIND_PACKAGE(vision_utils REQUIRED)
-list(APPEND wolfbodydynamics_INCLUDE_DIRS ${vision_utils_INCLUDE_DIR})
-list(APPEND wolfbodydynamics_LIBRARIES ${vision_utils_LIBRARY})
-
-FIND_PACKAGE(OpenCV REQUIRED)
-list(APPEND wolfbodydynamics_INCLUDE_DIRS ${OpenCV_INCLUDE_DIRS})
-list(APPEND wolfbodydynamics_LIBRARIES ${OpenCV_LIBS})
-
-#Making sure wolf is looked for
-if(NOT wolf_FOUND)
-  FIND_PACKAGE(wolf REQUIRED)
-
-  #We reverse in order to insert at the start
-  list(REVERSE wolfbodydynamics_INCLUDE_DIRS)
-  list(APPEND wolfbodydynamics_INCLUDE_DIRS ${wolf_INCLUDE_DIRS})
-  list(REVERSE wolfbodydynamics_INCLUDE_DIRS)
-
-  list(REVERSE wolfbodydynamics_LIBRARIES)
-  list(APPEND wolfbodydynamics_LIBRARIES ${wolf_LIBRARIES})
-  list(REVERSE wolfbodydynamics_LIBRARIES)
-endif()
\ No newline at end of file
diff --git a/cmake_modules/wolfbodydynamicsConfig.cmake b/cmake_modules/wolfbodydynamicsConfig.cmake
index 050a2367341c643d938f88c0ef7438ef7e34a0a9..c1eabae603cf15bca171950f3e8f5937506ec80f 100644
--- a/cmake_modules/wolfbodydynamicsConfig.cmake
+++ b/cmake_modules/wolfbodydynamicsConfig.cmake
@@ -12,7 +12,7 @@ ENDIF(wolfbodydynamics_INCLUDE_DIRS)
 FIND_LIBRARY(
     wolfbodydynamics_LIBRARIES
     NAMES libwolfbodydynamics.so
-    PATHS /usr/local/lib/iri-algorithms)
+    PATHS /usr/local/lib)
 IF(wolfbodydynamics_LIBRARIES)
   MESSAGE("Found wolf bodydynamics lib: ${wolfbodydynamics_LIBRARIES}")
 ELSE(wolfbodydynamics_LIBRARIES)
@@ -73,14 +73,19 @@ set(wolfbodydynamics_FOUND TRUE)
 
 # Making sure that wolf is always looked for
 if(NOT wolf_FOUND)
-  FIND_PACKAGE(wolf REQUIRED)
+  FIND_PACKAGE(wolfcore REQUIRED)
 
   #We reverse in order to insert at the start
   list(REVERSE wolfbodydynamics_INCLUDE_DIRS)
-  list(APPEND wolfbodydynamics_INCLUDE_DIRS ${wolf_INCLUDE_DIRS})
+  list(APPEND wolfbodydynamics_INCLUDE_DIRS ${wolfcore_INCLUDE_DIRS})
   list(REVERSE wolfbodydynamics_INCLUDE_DIRS)
 
   list(REVERSE wolfbodydynamics_LIBRARIES)
-  list(APPEND wolfbodydynamics_LIBRARIES ${wolf_LIBRARIES})
+  list(APPEND wolfbodydynamics_LIBRARIES ${wolfcore_LIBRARIES})
   list(REVERSE wolfbodydynamics_LIBRARIES)
-endif()
\ No newline at end of file
+endif()
+
+# provide both INCLUDE_DIR and INCLUDE_DIRS
+SET(wolfbodydynamics_INCLUDE_DIR ${wolfbodydynamics_INCLUDE_DIRS})
+# provide both LIBRARY and LIBRARIES 
+SET(wolfbodydynamics_LIBRARY ${wolfbodydynamics_LIBRARIES})
\ No newline at end of file
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e8896eccf78f6f4aef3138b1c6914646c788617c
--- /dev/null
+++ b/demos/CMakeLists.txt
@@ -0,0 +1,73 @@
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals")  # necessary for files using boost 
+
+FIND_PACKAGE(pinocchio REQUIRED)
+FIND_PACKAGE(multicontact-api REQUIRED)
+
+# SYSTEM disables warnings from library headers
+include_directories(
+    SYSTEM ${PINOCCHIO_INCLUDE_DIRS}
+)
+
+add_library(mcapi_utils mcapi_utils.cpp)
+
+# add_executable(mcapi_povcdl_estimation mcapi_povcdl_estimation.cpp)
+# target_link_libraries(mcapi_povcdl_estimation
+#     mcapi_utils
+#     ${wolfcore_LIBRARIES}
+#     ${wolfimu_LIBRARIES}
+#     ${PLUGIN_NAME}
+#     ${multicontact-api_LIBRARIES}
+#     ${pinocchio_LIBRARIES}
+# )
+# target_compile_definitions(mcapi_povcdl_estimation PRIVATE ${PINOCCHIO_CFLAGS_OTHER})
+
+
+# add_executable(mcapi_pov_estimation mcapi_pov_estimation.cpp)
+# target_link_libraries(mcapi_pov_estimation
+#     mcapi_utils
+#     ${wolfcore_LIBRARIES}
+#     ${wolfimu_LIBRARIES}
+#     ${PLUGIN_NAME}
+#     ${multicontact-api_LIBRARIES}
+#     ${pinocchio_LIBRARIES}
+# )
+# target_compile_definitions(mcapi_pov_estimation PRIVATE ${PINOCCHIO_CFLAGS_OTHER})
+
+add_executable(solo_real_povcdl_estimation solo_real_povcdl_estimation.cpp)
+target_link_libraries(solo_real_povcdl_estimation 
+    mcapi_utils
+    ${wolfcore_LIBRARIES}
+    ${wolfimu_LIBRARIES}
+    ${PLUGIN_NAME}
+    ${pinocchio_LIBRARIES}    
+    /usr/local/lib/libcnpy.so
+    z
+)
+
+add_executable(solo_real_pov_estimation solo_real_pov_estimation.cpp)
+target_link_libraries(solo_real_pov_estimation 
+    mcapi_utils
+    ${wolfcore_LIBRARIES}
+    ${wolfimu_LIBRARIES}
+    ${PLUGIN_NAME}
+    ${pinocchio_LIBRARIES}    
+    /usr/local/lib/libcnpy.so
+    z
+)
+
+add_executable(solo_mocap_imu solo_mocap_imu.cpp)
+target_link_libraries(solo_mocap_imu 
+    ${wolfcore_LIBRARIES}
+    ${wolfimu_LIBRARIES}
+    ${PLUGIN_NAME}
+    ${pinocchio_LIBRARIES}    
+    /usr/local/lib/libcnpy.so
+    z
+)
+
+# add_executable(test_cnpy test_cnpy.cpp)
+# target_link_libraries(test_cnpy
+#     /usr/local/lib/libcnpy.so
+# )
+
diff --git a/demos/eigenmvn.h b/demos/eigenmvn.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca871ae838a86541b5242d2b2437569650163e53
--- /dev/null
+++ b/demos/eigenmvn.h
@@ -0,0 +1,156 @@
+//--------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--------
+/**
+ * Multivariate Normal distribution sampling using C++11 and Eigen matrices.
+ * 
+ * This is taken from http://stackoverflow.com/questions/16361226/error-while-creating-object-from-templated-class
+ * (also see http://lost-found-wandering.blogspot.fr/2011/05/sampling-from-multivariate-normal-in-c.html)
+ * 
+ * I have been unable to contact the original author, and I've performed
+ * the following modifications to the original code:
+ * - removal of the dependency to Boost, in favor of straight C++11;
+ * - ability to choose from Solver or Cholesky decomposition (supposedly faster);
+ * - fixed Cholesky by using LLT decomposition instead of LDLT that was not yielding
+ *   a correctly rotated variance 
+ *   (see this http://stats.stackexchange.com/questions/48749/how-to-sample-from-a-multivariate-normal-given-the-pt-ldlt-p-decomposition-o )
+ */
+
+/**
+ * Copyright (c) 2014 by Emmanuel Benazera beniz@droidnik.fr, All rights reserved.
+ *
+ * This library 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.0 of the License, or (at your option) any later version.
+ *
+ * This library 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 library.
+ */
+
+#ifndef __EIGENMULTIVARIATENORMAL_HPP
+#define __EIGENMULTIVARIATENORMAL_HPP
+
+#include <Eigen/Dense>
+#include <random>
+
+/*
+  We need a functor that can pretend it's const,
+  but to be a good random number generator 
+  it needs mutable state.  The standard Eigen function 
+  Random() just calls rand(), which changes a global
+  variable.
+*/
+namespace Eigen {
+  namespace internal {
+    template<typename Scalar>
+      struct scalar_normal_dist_op
+      {
+	static std::mt19937 rng;                        // The uniform pseudo-random algorithm
+	mutable std::normal_distribution<Scalar> norm; // gaussian combinator
+	
+	EIGEN_EMPTY_STRUCT_CTOR(scalar_normal_dist_op)
+
+	template<typename Index>
+	inline const Scalar operator() (Index, Index = 0) const { return norm(rng); }
+	inline void seed(const uint64_t &s) { rng.seed(s); }
+      };
+
+    template<typename Scalar>
+      std::mt19937 scalar_normal_dist_op<Scalar>::rng;
+      
+    template<typename Scalar>
+      struct functor_traits<scalar_normal_dist_op<Scalar> >
+      { enum { Cost = 50 * NumTraits<Scalar>::MulCost, PacketAccess = false, IsRepeatable = false }; };
+
+  } // end namespace internal
+
+  /**
+    Find the eigen-decomposition of the covariance matrix
+    and then store it for sampling from a multi-variate normal 
+  */
+  template<typename Scalar>
+    class EigenMultivariateNormal
+  {
+    Matrix<Scalar,Dynamic,Dynamic> _covar;
+    Matrix<Scalar,Dynamic,Dynamic> _transform;
+    Matrix< Scalar, Dynamic, 1> _mean;
+    internal::scalar_normal_dist_op<Scalar> randN; // Gaussian functor
+    bool _use_cholesky;
+    SelfAdjointEigenSolver<Matrix<Scalar,Dynamic,Dynamic> > _eigenSolver; // drawback: this creates a useless eigenSolver when using Cholesky decomposition, but it yields access to eigenvalues and vectors
+    
+  public:
+  EigenMultivariateNormal(const Matrix<Scalar,Dynamic,1>& mean,const Matrix<Scalar,Dynamic,Dynamic>& covar,
+			  const bool use_cholesky=false,const uint64_t &seed=std::mt19937::default_seed)
+      :_use_cholesky(use_cholesky)
+     {
+        randN.seed(seed);
+	setMean(mean);
+	setCovar(covar);
+      }
+
+    void setMean(const Matrix<Scalar,Dynamic,1>& mean) { _mean = mean; }
+    void setCovar(const Matrix<Scalar,Dynamic,Dynamic>& covar)
+    {
+      _covar = covar;
+      
+      // Assuming that we'll be using this repeatedly,
+      // compute the transformation matrix that will
+      // be applied to unit-variance independent normals
+      
+      if (_use_cholesky)
+	{
+	  Eigen::LLT<Eigen::Matrix<Scalar,Dynamic,Dynamic> > cholSolver(_covar);
+	  // We can only use the cholesky decomposition if 
+	  // the covariance matrix is symmetric, pos-definite.
+	  // But a covariance matrix might be pos-semi-definite.
+	  // In that case, we'll go to an EigenSolver
+	  if (cholSolver.info()==Eigen::Success)
+	    {
+	      // Use cholesky solver
+	      _transform = cholSolver.matrixL();
+	    }
+	  else
+	    {
+	      throw std::runtime_error("Failed computing the Cholesky decomposition. Use solver instead");
+	    }
+	}
+      else
+	{
+	  _eigenSolver = SelfAdjointEigenSolver<Matrix<Scalar,Dynamic,Dynamic> >(_covar);
+	  _transform = _eigenSolver.eigenvectors()*_eigenSolver.eigenvalues().cwiseMax(0).cwiseSqrt().asDiagonal();
+	}
+    }
+
+    /// Draw nn samples from the gaussian and return them
+    /// as columns in a Dynamic by nn matrix
+    Matrix<Scalar,Dynamic,-1> samples(int nn)
+      {
+	return (_transform * Matrix<Scalar,Dynamic,-1>::NullaryExpr(_covar.rows(),nn,randN)).colwise() + _mean;
+      }
+  }; // end class EigenMultivariateNormal
+} // end namespace Eigen
+#endif
diff --git a/demos/mcapi_pov_estimation.cpp b/demos/mcapi_pov_estimation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9fdc2feebbea2a407bd5268d0be86388804e5de3
--- /dev/null
+++ b/demos/mcapi_pov_estimation.cpp
@@ -0,0 +1,751 @@
+//--------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--------
+#include <iostream>
+// #include <random>
+#include <yaml-cpp/yaml.h>
+#include <Eigen/Dense>
+#include <ctime>
+#include "eigenmvn.h"
+
+#include "pinocchio/parsers/urdf.hpp"
+#include "pinocchio/algorithm/joint-configuration.hpp"
+#include "pinocchio/algorithm/kinematics.hpp"
+#include "pinocchio/algorithm/center-of-mass.hpp"
+#include "pinocchio/algorithm/centroidal.hpp"
+#include "pinocchio/algorithm/rnea.hpp"
+#include "pinocchio/algorithm/crba.hpp"
+#include "pinocchio/algorithm/frames.hpp"
+
+// MCAPI
+#include <curves/fwd.h>
+#include <curves/so3_linear.h>
+#include <curves/se3_curve.h>
+#include <curves/polynomial.h>
+#include <curves/bezier_curve.h>
+#include <curves/piecewise_curve.h>
+#include <curves/exact_cubic.h>
+#include <curves/cubic_hermite_spline.h>
+#include "multicontact-api/scenario/contact-sequence.hpp"
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_block_absolute.h>
+#include <core/processor/processor_odom_3d.h>
+#include <core/sensor/sensor_odom_3d.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/capture/capture_leg_odom.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+#include "bodydynamics/factor/factor_inertial_kinematics.h"
+#include "bodydynamics/factor/factor_force_torque_preint.h"
+
+
+// UTILS
+#include "mcapi_utils.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using namespace pinocchio;
+using namespace multicontact_api::scenario;
+
+typedef pinocchio::SE3Tpl<double> SE3;
+typedef pinocchio::ForceTpl<double> Force;
+
+
+int main (int argc, char **argv) {
+    // retrieve parameters from yaml file
+    YAML::Node config = YAML::LoadFile("/home/mfourmy/Documents/Phd_LAAS/wolf/bodydynamics/demos/mcapi_povcdl_estimation.yaml");
+    std::string robot_urdf = config["robot_urdf"].as<std::string>();
+    int dimc = config["dimc"].as<int>();
+    double dt = config["dt"].as<double>();
+    double min_t = config["min_t"].as<double>();
+    double max_t = config["max_t"].as<double>();
+    double solve_every_sec = config["solve_every_sec"].as<double>();
+    bool solve_end = config["solve_end"].as<bool>();
+    bool noisy_measurements = config["noisy_measurements"].as<bool>();
+
+    // estimator sensor noises
+    double std_pbc_est = config["std_pbc_est"].as<double>();
+    double std_vbc_est = config["std_vbc_est"].as<double>();
+    double std_f_est = config["std_f_est"].as<double>();
+    double std_tau_est = config["std_tau_est"].as<double>();
+    double std_odom3d_est = config["std_odom3d_est"].as<double>();
+
+    // simulated sensor noises
+    double std_acc_sim = config["std_acc_sim"].as<double>();
+    double std_gyr_sim = config["std_gyr_sim"].as<double>();
+    double std_pbc_sim = config["std_pbc_sim"].as<double>();
+    double std_vbc_sim = config["std_vbc_sim"].as<double>();
+    double std_f_sim = config["std_f_sim"].as<double>();
+    double std_tau_sim = config["std_tau_sim"].as<double>();
+    // double std_odom3d_sim = config["std_odom3d_sim"].as<double>();
+    
+    // priors
+    double std_prior_p = config["std_prior_p"].as<double>();
+    double std_prior_o = config["std_prior_o"].as<double>();
+    double std_prior_v = config["std_prior_v"].as<double>();
+    double std_abs_bias_acc = config["std_abs_bias_acc"].as<double>();
+    double std_abs_bias_gyro = config["std_abs_bias_gyro"].as<double>();
+    double std_bp_drift = config["std_bp_drift"].as<double>();
+    std::vector<double> bias_imu_prior_vec = config["bias_imu_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector6d> bias_imu_prior(bias_imu_prior_vec.data());
+
+    double fz_thresh = config["fz_thresh"].as<double>();
+    double scale_dist = config["scale_dist"].as<double>();
+    bool base_dist_only = config["base_dist_only"].as<bool>();
+    bool mass_dist = config["mass_dist"].as<bool>();
+    bool vbc_is_dist = config["vbc_is_dist"].as<bool>();
+    bool Iw_is_dist = config["Iw_is_dist"].as<bool>();
+    bool Lgest_is_dist = config["Lgest_is_dist"].as<bool>();
+
+    std::string contact_sequence_file = config["contact_sequence_file"].as<std::string>();
+
+    ContactSequence cs;
+    std::cout << "Loading file " << contact_sequence_file << std::endl;
+    cs.loadFromBinary(contact_sequence_file);
+    
+    auto q_traj = cs.concatenateQtrajectories();
+    auto dq_traj = cs.concatenateDQtrajectories();
+    auto ddq_traj = cs.concatenateDDQtrajectories();
+    auto c_traj = cs.concatenateCtrajectories();
+    auto dc_traj = cs.concatenateDCtrajectories();
+    auto L_traj = cs.concatenateLtrajectories();
+    auto tau_traj = cs.concatenateTauTrajectories();
+    std::vector<std::string> ee_names = cs.getAllEffectorsInContact();
+    unsigned int nbc = ee_names.size();
+    std::vector<curves::piecewise_t> cforce_traj_lst;
+    for (auto ee_name: ee_names){
+        std::cout << ee_name << std::endl;
+        auto cforce_traj = cs.concatenateContactForceTrajectories(ee_name);
+        cforce_traj_lst.push_back(cforce_traj);
+    }
+
+    if (min_t < 0){
+        min_t = q_traj.min();
+    }
+    if (max_t < 0){
+        max_t = q_traj.max();
+    }
+
+    // initialize some vectors and fill them with dicretized data
+    std::vector<double> t_arr;                // timestamps
+    std::vector<VectorXd> q_traj_arr;         // [p_ob, q_ob, qA]
+    std::vector<VectorXd> dq_traj_arr;        // [spvel_b, qA_dot]
+    std::vector<VectorXd> ddq_traj_arr;       // [spacc_b, qA_ddot]
+    std::vector<VectorXd> c_traj_arr;         // w_p_wc
+    std::vector<VectorXd> dc_traj_arr;        // w_v_wc
+    std::vector<VectorXd> L_traj_arr;         // w_Lc: angular momentum at the CoM expressed in CoM (=World) frame
+    std::vector<VectorXd> tau_traj_arr;         // w_Lc: angular momentum at the CoM expressed in CoM (=World) frame
+    std::vector<VectorXd> bp_traj_arr;        // bias on b_p_bc: bias on the CoM position expressed in base frame
+    std::vector<std::vector<VectorXd>> cforce_traj_arr_lst(nbc); 
+    int N = (max_t - min_t)/dt;
+    int i_t = 0;
+    for (double t=min_t; t <= max_t; t += dt){
+        t_arr.push_back(t);
+        q_traj_arr.push_back(q_traj(t));
+        dq_traj_arr.push_back(dq_traj(t));
+        ddq_traj_arr.push_back(ddq_traj(t));
+        c_traj_arr.push_back(c_traj(t));
+        dc_traj_arr.push_back(dc_traj(t));
+        L_traj_arr.push_back(L_traj(t));
+        tau_traj_arr.push_back(tau_traj(t));
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            cforce_traj_arr_lst[i_ee].push_back(cforce_traj_lst[i_ee](t));
+        }
+        i_t++;
+    }
+    std::cout << "Trajectory extracted, proceed to create sensor data" << std::endl;
+
+    // Creating measurements from simulated trajectory
+    // Load the urdf model
+    Model model;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model);
+    std::cout << "model name: " << model.name << std::endl;
+    Data data(model);
+
+    // distorted model (modified inertias)
+    Model model_dist;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model_dist);
+
+    Eigen::EigenMultivariateNormal<double> noise_mass = Eigen::EigenMultivariateNormal<double>(Vector1d::Zero(), Matrix1d::Identity(), false);
+    Eigen::EigenMultivariateNormal<double> noise_lever = Eigen::EigenMultivariateNormal<double>(Vector3d::Zero(), Matrix3d::Identity(), false);
+
+    // distortion
+    if (base_dist_only){
+        if (!mass_dist){
+            int root_joint_id = model.getJointId("root_joint");
+            Vector3d lever_dist = model.inertias[root_joint_id].lever() + scale_dist * noise_lever.samples(1); 
+            model_dist.inertias[root_joint_id] = pinocchio::Inertia(model.inertias[root_joint_id].mass(), lever_dist, model.inertias[root_joint_id].inertia());
+        } 
+    }
+    else{
+        for (int i=0; i < model_dist.inertias.size(); i++){
+            if (mass_dist){
+                double mass_dist = model.inertias[i].mass() + scale_dist * model.inertias[i].mass() * noise_mass.samples(1)(0,0);
+                model_dist.inertias[i] = pinocchio::Inertia(mass_dist, model.inertias[i].lever(), model.inertias[i].inertia()); 
+                std::cout << "mass model      " << model.inertias[i].mass() << std::endl;
+                std::cout << "mass model_dist " << model_dist.inertias[i].mass() << std::endl;
+            }
+            else {
+                Vector3d lever_dist = model.inertias[i].lever() + scale_dist * noise_lever.samples(1); 
+                model_dist.inertias[i] = pinocchio::Inertia(model.inertias[i].mass(), lever_dist, model.inertias[i].inertia()); 
+            }
+        }
+    }
+    Data data_dist(model_dist);
+
+    std::vector<int> ee_ids;
+    std::cout << "Frame ids" << std::endl;
+    for (auto ee_name: ee_names){
+        ee_ids.push_back(model.getFrameId(ee_name));
+        std::cout << ee_name << std::endl;
+    }
+    std::vector<Vector3d> contacts = contacts_from_footrect_center();
+
+    ///////////////////////////////////////////////
+    // Create some vectors to aggregate the groundtruth and measurements
+    // Groundtruth
+    std::vector<Vector3d> p_ob_gtr_v; 
+    std::vector<Vector4d> q_ob_gtr_v; 
+    std::vector<Vector3d> v_ob_gtr_v; 
+    // std::vector<VectorXd>& c_gtr_v = c_traj_arr;   // already have it directly
+    // std::vector<VectorXd>& cd_gtr_v = dc_traj_arr; // already have it directly
+    // std::vector<Vector3d> L_traj_arr;              // already have it directly
+
+
+    // Measurements
+    std::vector<Vector3d> acc_b_meas_v; 
+    std::vector<Vector3d> w_b_meas_v; 
+    std::vector<std::vector<Vector3d>> p_bl_meas_v(nbc);
+    std::vector<std::vector<Vector4d>> q_bl_meas_v(nbc);
+    std::vector<std::vector<VectorXd>> wrench_meas_v(nbc); 
+    std::vector<Vector3d> p_bc_meas_v; 
+    std::vector<Vector3d> v_bc_meas_v; 
+    std::vector<Vector3d> Lq_meas_v; 
+    std::vector<Matrix3d> Iq_meas_v; 
+    // define vectors to aggregate ground truth and simulated data
+    for (unsigned int i = 0; i < q_traj_arr.size(); i++)
+    {
+        /**
+         * Groundtruth to recover (in world frame, vels/inertial frame):
+         * b position                                  --> OK
+         * b orientation (quat)                        --> OK
+         * b velocity: linear classical one            --> OK
+         * CoM posi                                    --> OK
+         * CoM vel                                     --> OK                
+         * Angular momentum                            --> OK
+         * 
+         * Measures to recover (in local frames):
+         * IMU readings                                --> OK
+         * Kinematics readings                         --> OK
+         * Wrench readings                             --> OK
+         * CoM relative position/vel                   --> OK
+         * Iq, Lq                                      --> OK
+         *            
+        **/
+
+        // retrieve traj data
+        VectorXd q = q_traj_arr[i];
+        VectorXd dq = dq_traj_arr[i];
+        VectorXd ddq = ddq_traj_arr[i];
+        // Vector3d c = c_traj_arr[i];      // gtr
+        // VectorXd dc = dc_traj_arr[i];    // gtr
+        // VectorXd L = L_traj_arr[i];      // gtr
+        VectorXd tau = tau_traj_arr[i];
+
+        // std::cout << "q " << q.transpose() << std::endl;
+        // std::cout << "dq " << dq.transpose() << std::endl;
+        // std::cout << "ddq " << ddq.transpose() << std::endl;
+
+        //////////////////////////////////
+        //////////////////////////////////
+        // Set ground truth variables
+        Vector3d p_wb = q.head<3>();        // gtr
+        Vector4d quat_wb = q.segment<4>(3); // gtr
+        SE3 oTb(Quaterniond(quat_wb).toRotationMatrix(), p_wb);  // global frame pose
+        // body vel is expressed in body frame
+        Vector3d b_v_b = dq.head<3>();  // vsp: spatial linear velocity
+        Vector3d b_w_b = dq.segment<3>(3);  // meas
+        Vector3d b_asp_b = ddq.head<3>();   // body spatial acceleration in body plucker frame
+
+        Vector3d w_v_wb = oTb.rotation() * b_v_b;  // gtr
+
+        ////////////////////////////////////
+        ////////////////////////////////////
+        // Get measurements
+
+        // IMU readings
+        // We use every quantity (spatial acc, body angvel, dr=lin spatial linvel) in the same body frame -> we get acc in body frame
+        Vector3d b_acc = b_asp_b + b_w_b.cross(b_v_b);
+        // universe o frame in mcapi/pinocchio is supposed to have same reference point and orientation as WOLF world frame
+        Vector3d b_proper_acc = b_acc - oTb.rotation().transpose() * gravity();  // meas
+
+        // update and retrieve kinematics
+        forwardKinematics(model, data, q);
+        updateFramePlacements(model, data);
+
+        // compute all linear jacobians (only for quadruped)
+        MatrixXd Jlinvel_all(12, model.nv); Jlinvel_all.setZero();
+        for (int i_ee=0; i_ee < nbc; i_ee++){
+            MatrixXd Jee(6, model.nv); Jee.setZero();
+            computeFrameJacobian(model, data, q, ee_ids[i_ee], Jee);
+            Jlinvel_all.block(3*i_ee, 0, 3, model.nv) = Jee.topRows<3>();
+        }
+        // remove free flyer (not needed for force reconstruction)
+        MatrixXd Jlinvel_noff = Jlinvel_all.rightCols(model.nv-6);
+
+        // estimate forces from torques
+        VectorXd tau_cf = rnea(model, data, q, dq, ddq);  // compute total torques applied on the joints (and free flyer)
+        VectorXd tau_joints = tau_cf.tail(model.nv-6) - tau;  // remove measured joint torque (not on the free flyer)
+        VectorXd forces = Jlinvel_noff.transpose().lu().solve(tau_joints);
+
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            auto oTl = data.oMf[ee_ids[i_ee]];
+            auto bTl = oTb.inverse() * oTl;
+            Vector3d b_p_l = bTl.translation();                        // meas
+            Vector4d b_qvec_l = Quaterniond(bTl.rotation()).coeffs();  // meas
+            p_bl_meas_v[i_ee].push_back(b_p_l);
+            q_bl_meas_v[i_ee].push_back(b_qvec_l);
+            // TODO: Only for 6D wrench or 3D force
+
+            // Compute either from torques or from given force and compare
+            // std::cout << forces.segment(3*i_ee, 3).transpose() - cforce_traj_arr_lst[i_ee][i].transpose() << std::endl;
+            // wrench_meas_v[i_ee].push_back(cforce_traj_arr_lst[i_ee][i]);
+            wrench_meas_v[i_ee].push_back(forces.segment(3*i_ee, 3));
+        }
+
+
+        //////////////////////////////////////////////
+        // COM relative position and velocity measures
+        VectorXd q_static = q;
+        VectorXd dq_static = dq;
+        q_static.head<7>() << 0,0,0, 0,0,0,1;
+        dq_static.head<6>() << 0,0,0, 0,0,0;
+        // compute for both perfect and distorted models to get the bias
+        centerOfMass(model, data, q_static, dq_static);        
+        centerOfMass(model_dist, data_dist, q_static, dq_static);
+
+        // c =def  w_p_wc
+        // Vector3d b_p_bc = oTb.inverse().act(c);  // meas   
+        Vector3d b_p_bc = data.com[0];  // meas   
+        Vector3d b_p_bc_dist = data_dist.com[0];  // meas   
+        Vector3d bias_pbc = b_p_bc_dist - b_p_bc;
+
+        // velocity due to to gesticulation only in base frame 
+        // -> set world frame = base frame and set body frame spatial vel to zero 
+        Vector3d b_v_bc = data.vcom[0];  // meas
+        Vector3d b_v_bc_dist = data_dist.vcom[0];  // meas
+        Vector3d bias_vbc = b_v_bc_dist - b_v_bc;
+
+        /////////////////////////
+        // Centroidal inertia and gesticulation kinetic momentum 
+        Matrix3d b_Iw = compute_Iw(model, data, q_static, b_p_bc);
+        Matrix3d b_Iw_dist = compute_Iw(model_dist, data_dist, q_static, b_p_bc_dist);
+        Matrix3d bias_Iw = b_Iw_dist - b_Iw;
+
+        // gesticulation in base frame: just compute centroidal momentum with static q and vq
+        Vector3d b_Lc_gesti = computeCentroidalMomentum(model, data, q_static, dq_static).angular();
+        Vector3d b_Lc_gesti_dist = computeCentroidalMomentum(model_dist, data_dist, q_static, dq_static).angular();
+        Vector3d bias_Lc = b_Lc_gesti_dist - b_Lc_gesti;
+        
+        
+        // std::cout << "bias_pbc: " << bias_pbc.transpose() << std::endl;
+        // std::cout << "bias_vbc: " << bias_vbc.transpose() << std::endl;
+        // std::cout << "bias_Iw: \n" << bias_Iw << std::endl;
+        // std::cout << "bias_Lc: " << bias_Lc.transpose() << std::endl;
+
+        /////////////////////////
+        // Agreggate everything in vectors for easier WOLF consumption
+        p_ob_gtr_v.push_back(p_wb); 
+        q_ob_gtr_v.push_back(quat_wb); 
+        v_ob_gtr_v.push_back(w_v_wb); 
+        bp_traj_arr.push_back(bias_pbc);
+        // Lc_gtr_v; 
+
+        // Measurements
+        acc_b_meas_v.push_back(b_proper_acc); 
+        w_b_meas_v.push_back(b_w_b); 
+        p_bc_meas_v.push_back(b_p_bc_dist); 
+        if (vbc_is_dist){
+            v_bc_meas_v.push_back(b_v_bc_dist); 
+        }
+        else {
+            v_bc_meas_v.push_back(b_v_bc); 
+        }
+        if (Lgest_is_dist){
+            Lq_meas_v.push_back(b_Lc_gesti_dist);
+        }
+        else{
+            Lq_meas_v.push_back(b_Lc_gesti);
+        }
+        if (Iw_is_dist){
+            Iq_meas_v.push_back(b_Iw_dist);
+        }
+        else {
+            Iq_meas_v.push_back(b_Iw);
+        }
+    }
+
+    /////////////////////////
+    // WOLF enters the scene
+    // SETUP PROBLEM/SENSORS/PROCESSORS
+    std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+    ProblemPtr problem = Problem::create("POV", 3);
+
+    // CERES WRAPPER
+    SolverCeresPtr solver = std::make_shared<SolverCeres>(problem);
+
+    // SENSOR + PROCESSOR IMU
+    SensorBasePtr sen_imu_base = problem->installSensor("SensorImu", "SenImu", (Vector7d() << 0, 0, 0, 0, 0, 0, 1).finished(), bodydynamics_root_dir + "/demos/sensor_imu.yaml");
+    SensorImuPtr sen_imu = std::static_pointer_cast<SensorImu>(sen_imu_base);
+    ProcessorBasePtr proc_ftp_base = problem->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/demos/processor_imu_mcapi_demo.yaml");
+    ProcessorImuPtr proc_imu = std::static_pointer_cast<ProcessorImu>(proc_ftp_base);
+
+    // SENSOR + PROCESSOR ODOM3D FOR LEG ODOMETRY
+    Vector7d extr_senlegodom; extr_senlegodom << 0,0,0, 0,0,0,1;  // not used in practice 
+    SensorBasePtr sen_odom3d_base = problem->installSensor("SensorOdom3d", "SenLegOdom", extr_senlegodom, bodydynamics_root_dir + "/demos/sensor_odom_3d.yaml");
+    SensorOdom3dPtr sen_odom3d = std::static_pointer_cast<SensorOdom3d>(sen_odom3d_base);
+    ParamsProcessorOdom3dPtr params_sen_odom3d = std::make_shared<ParamsProcessorOdom3d>();
+    // ProcessorBasePtr proc_legodom_base = problem->installProcessor("ProcessorOdom3d", "ProcLegOdom", sen_odom3d, params_sen_odom3d);
+    ProcessorBasePtr proc_legodom_base = problem->installProcessor("ProcessorOdom3d", "ProcLegOdom", "SenLegOdom", bodydynamics_root_dir + "/demos/processor_odom_3d.yaml");
+    ProcessorOdom3dPtr proc_legodom = std::static_pointer_cast<ProcessorOdom3d>(proc_legodom_base);
+    //////////////////////////////
+
+    TimeStamp t0(t_arr[0]);
+    VectorComposite x_origin("POV", {p_ob_gtr_v[0], q_ob_gtr_v[0], v_ob_gtr_v[0]});
+    VectorComposite std_origin("POV", {std_prior_p*Vector3d::Ones(), std_prior_o*Vector3d::Ones(), std_prior_v*Vector3d::Ones()});
+    FrameBasePtr KF1 = problem->setPriorFactor(x_origin, std_origin, t0);
+    
+    proc_imu->setOrigin(KF1);
+    proc_legodom->setOrigin(KF1);
+
+
+    ///////////////////////////////////////////
+    // process measurements in sequential order
+    double ts_since_last_kf = 0;
+
+    // Add gaussian noises
+    std::time_t epoch = std::time(nullptr);
+    int64_t seed = (int64_t)epoch;
+	Eigen::EigenMultivariateNormal<double> noise_acc(Vector3d::Zero(), pow(std_acc_sim, 2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_gyr(Vector3d::Zero(), pow(std_gyr_sim, 2)*Matrix3d::Identity(), false, seed);
+
+    //////////////////////////////////////////
+    // Vectors to store estimates at the time of data processing 
+    // fbk -> feedback: to check if the estimation is stable enough for feedback control
+    std::vector<Vector3d> p_ob_fbk_v; 
+    std::vector<Vector4d> q_ob_fbk_v; 
+    std::vector<Vector3d> v_ob_fbk_v; 
+    //////////////////////////////////////////
+
+    unsigned int nb_kf = problem->getTrajectory()->getFrameMap().size();
+    for (unsigned int i=0; i < t_arr.size(); i++){
+        // TimeStamp ts(t_arr[i]+dt); // works best with tsid trajectories
+        TimeStamp ts(t_arr[i]);  // works best with pyb trajectories
+
+        // IMU
+        Vector6d acc_gyr_meas; acc_gyr_meas << acc_b_meas_v[i], w_b_meas_v[i];
+        
+        if (noisy_measurements){
+            acc_gyr_meas.head<3>() += noise_acc.samples(1);
+            acc_gyr_meas.tail<3>() += noise_gyr.samples(1);
+        }
+
+        Matrix6d acc_gyr_cov = sen_imu->getNoiseCov();
+
+        ////////////////////
+        /////////////////
+
+        CaptureImuPtr CImu = std::make_shared<CaptureImu>(ts, sen_imu, acc_gyr_meas, acc_gyr_cov);
+        CImu->process();
+
+
+        if (i > 0){
+            // Leg odometry
+            // 1) detect feet in contact
+            std::vector<int> feet_int_contact;
+            for (unsigned int i_ee=0; i_ee<nbc; i_ee++){
+                // basic contact detection based on normal foot vel, things break if no foot is in contact
+                // std::cout << "F" << ee_names[i_ee] << " norm: " << wrench_meas_v[i_ee][i].norm() << std::endl;
+                if (wrench_meas_v[i_ee][i].norm() > fz_thresh){
+                    feet_int_contact.push_back(i_ee);
+                }
+            }
+            // 2) fill the leg odometry data matrix, depending on the contact dimension
+            Eigen::MatrixXd data_legodom;            
+            if (dimc == 6){
+                // cols organized as:
+                // [pbl1_km; qbl1_k], [pbl2_km; qbl2_k], ...
+                data_legodom.resize(7,feet_int_contact.size()*2);
+                data_legodom.setZero();
+                int j = 0;
+                for (int i_ee_c: feet_int_contact){
+                    data_legodom.col(2*j  ) << p_bl_meas_v[i_ee_c][i-1], q_bl_meas_v[i_ee_c][i-1];
+                    data_legodom.col(2*j+1) << p_bl_meas_v[i_ee_c][i  ], q_bl_meas_v[i_ee_c][i];
+                    j++;
+                }
+            }
+            else if (dimc == 3){
+                // cols organized as:
+                // pbl1_km, pbl1_k, pbl2_km, pbl2_k, ..., w_b
+                data_legodom.resize(3,feet_int_contact.size()*2 + 1);
+                data_legodom.setZero();
+                int j = 0;
+                for (int i_ee_c: feet_int_contact){
+                    data_legodom.col(2*j  ) << p_bl_meas_v[i_ee_c][i-1];
+                    data_legodom.col(2*j+1) << p_bl_meas_v[i_ee_c][i  ];
+                    j++;
+                }
+                data_legodom.rightCols<1>() = acc_gyr_meas.tail<3>();
+            }
+            // std::cout << "data_legodom\n" << data_legodom << std::endl;
+            // 3) process the data and its cov
+            Eigen::Matrix6d data_legodom3D_cov = pow(std_odom3d_est, 2) * Eigen::Matrix6d::Identity(); 
+            CaptureLegOdomPtr CLO = std::make_shared<CaptureLegOdom>(ts, sen_odom3d, data_legodom, data_legodom3D_cov, dt, CaptureLegOdomType::POINT_FEET_RELATIVE_KIN);
+            CLO->process();
+        }
+
+        // if new KF add 
+        // unsigned int new_kf_nb = problem->getTrajectory()->getFrameMap().size();
+        // if (new_kf_nb > nb_kf){
+        //     auto last_kf = problem->getTrajectory()->getFrameMap().rbegin()->second;
+        //     nb_kf = new_kf_nb;
+        //     // ADD ABSOLUTE FACTOR (GPS LIKE)
+        // }
+
+
+        ts_since_last_kf += dt;
+        if ((solve_every_sec > 0) && (ts_since_last_kf >= solve_every_sec)){
+            // problem->print(4,true,true,true);
+            std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+            std::cout << report << std::endl;
+            ts_since_last_kf = 0;
+        }
+
+        // Store current state estimation
+        VectorComposite curr_state = problem->getState(ts);
+        p_ob_fbk_v.push_back(curr_state['P']);
+        q_ob_fbk_v.push_back(curr_state['O']);
+        v_ob_fbk_v.push_back(curr_state['V']);
+
+    }
+
+    ////////////////////////////////////////////
+    // BIAS FACTORS
+    // Add absolute factor on Imu biases to simulate a fix()
+    Vector6d std_abs_imu; std_abs_imu << std_abs_bias_acc*Vector3d::Ones(), std_abs_bias_gyro*Vector3d::Ones();
+    Matrix6d Q_bi = std_abs_imu.array().square().matrix().asDiagonal();
+    CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1, "bi0", t0);
+    FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", bias_imu_prior, Q_bi);
+    FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1->getCaptureOf(sen_imu)->getSensorIntrinsic(), nullptr, false);   
+
+
+    ///////////////////////////////////////////
+    //////////////// SOLVING //////////////////
+    ///////////////////////////////////////////
+    problem->print(4,true,true,true);
+    if (solve_end){
+        std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        problem->print(4,true,true,true);
+        std::cout << report << std::endl;
+    }
+
+    //////////////////////////////////////
+    //////////////////////////////////////
+    //            STORE DATA            //
+    //////////////////////////////////////
+    //////////////////////////////////////
+    std::fstream file_gtr; 
+    std::fstream file_est; 
+    std::fstream file_fbk; 
+    std::fstream file_kfs; 
+    std::fstream file_cov; 
+    file_gtr.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/gtr.csv", std::fstream::out); 
+    file_est.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/est.csv", std::fstream::out); 
+    file_fbk.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/fbk.csv", std::fstream::out); 
+    file_kfs.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/kfs.csv", std::fstream::out); 
+    file_cov.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/cov.csv", std::fstream::out); 
+    std::string header_est = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz\n";
+    file_est << header_est;
+    file_fbk << header_est;
+    std::string header_kfs = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz,bax,bay,baz,bwx,bwy,bwz\n";
+    file_kfs << header_kfs;
+    file_gtr << header_kfs;
+    std::string header_cov = "t,Qpx,Qpy,Qpz,Qqx,Qqy,Qqz,Qvx,Qvy,Qvz,Qbax,Qbay,Qbaz,Qbwx,Qbwy,Qbwz\n";
+    file_cov << header_cov; 
+
+    VectorComposite state_est;
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        file_gtr << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << p_ob_gtr_v[i](0) << ","
+                 << p_ob_gtr_v[i](1) << ","
+                 << p_ob_gtr_v[i](2) << "," 
+                 << q_ob_gtr_v[i](0) << "," 
+                 << q_ob_gtr_v[i](1) << "," 
+                 << q_ob_gtr_v[i](2) << "," 
+                 << q_ob_gtr_v[i](3) << "," 
+                 << v_ob_gtr_v[i](0) << "," 
+                 << v_ob_gtr_v[i](1) << "," 
+                 << v_ob_gtr_v[i](2)
+                 << "\n";
+    }
+
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        state_est = problem->getState(t_arr[i]);
+        file_est << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << state_est['P'](0) << ","
+                 << state_est['P'](1) << ","
+                 << state_est['P'](2) << "," 
+                 << state_est['O'](0) << "," 
+                 << state_est['O'](1) << "," 
+                 << state_est['O'](2) << "," 
+                 << state_est['O'](3) << "," 
+                 << state_est['V'](0) << "," 
+                 << state_est['V'](1) << "," 
+                 << state_est['V'](2)
+                 << "\n"; 
+    }
+
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        file_fbk << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << p_ob_fbk_v[i](0) << ","
+                 << p_ob_fbk_v[i](1) << ","
+                 << p_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](0) << "," 
+                 << q_ob_fbk_v[i](1) << "," 
+                 << q_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](3) << "," 
+                 << v_ob_fbk_v[i](0) << "," 
+                 << v_ob_fbk_v[i](1) << "," 
+                 << v_ob_fbk_v[i](2)
+                 << "\n";    
+    }
+
+    VectorComposite kf_state;
+    CaptureBasePtr cap_imu;
+    VectorComposite bi_bias_est;
+    for (auto& elt: problem->getTrajectory()->getFrameMap()){
+        auto kf = elt.second;
+        kf_state = kf->getState();
+        cap_imu = kf->getCaptureOf(sen_imu); 
+        bi_bias_est = cap_imu->getState();
+
+        file_kfs << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                    << kf->getTimeStamp().get() << ","
+                    << kf_state['P'](0) << ","
+                    << kf_state['P'](1) << ","
+                    << kf_state['P'](2) << "," 
+                    << kf_state['O'](0) << "," 
+                    << kf_state['O'](1) << "," 
+                    << kf_state['O'](2) << "," 
+                    << kf_state['O'](3) << "," 
+                    << kf_state['V'](0) << "," 
+                    << kf_state['V'](1) << "," 
+                    << kf_state['V'](2) << ","
+                    << bi_bias_est['I'](0) << ","
+                    << bi_bias_est['I'](1) << ","
+                    << bi_bias_est['I'](2) << ","
+                    << bi_bias_est['I'](3) << ","
+                    << bi_bias_est['I'](4) << ","
+                    << bi_bias_est['I'](5)
+                    << "\n"; 
+
+        // compute covariances of KF and capture stateblocks
+        Eigen::MatrixXd Qp =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qo =  Eigen::Matrix3d::Identity();  // global or local?
+        Eigen::MatrixXd Qv =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbi =  Eigen::Matrix6d::Identity();
+        
+        solver->computeCovariances(SolverManager::CovarianceBlocksToBeComputed::ALL);  // should not be computed every time !! see below
+        problem->getCovarianceBlock(kf->getStateBlock('P'), Qp);
+        problem->getCovarianceBlock(kf->getStateBlock('O'), Qo);
+        problem->getCovarianceBlock(kf->getStateBlock('V'), Qv);
+
+        solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi);
+
+        file_cov << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                        << kf->getTimeStamp().get() << ","
+                        << Qp(0,0) << ","
+                        << Qp(1,1) << ","
+                        << Qp(2,2) << ","
+                        << Qo(0,0) << ","
+                        << Qo(1,1) << ","
+                        << Qo(2,2) << ","
+                        << Qv(0,0) << ","
+                        << Qv(1,1) << ","
+                        << Qv(2,2) << ","
+                        << Qbi(0,0) << ","
+                        << Qbi(1,1) << ","
+                        << Qbi(2,2) << ","
+                        << Qbi(3,3) << ","
+                        << Qbi(4,4) << ","
+                        << Qbi(5,5)                    
+                        << "\n"; 
+    }
+
+    file_gtr.close();
+    file_est.close();
+    file_fbk.close();
+    file_kfs.close();
+    file_cov.close();
+
+
+    // // Print factor energy
+    // for (auto kf: problem->getTrajectory()->getFrameMap()){
+    //     for (auto cap: kf->getCaptureList()){
+    //         for (auto feat: cap->getFeatureList()){
+    //             for (auto fac: feat->getFactorList()){
+    //                 if (fac->getType() == "FactorForceTorquePreint"){
+    //                     std::cout << "Type: FactorForceTorquePreint" << std::endl;
+    //                     auto fac_ftp = std::static_pointer_cast<FactorForceTorquePreint>(fac);
+    //                     std::cout << "try the cost" << std::endl;
+    //                     std::cout << fac_ftp->cost() << std::endl;
+    //                 }
+    //             }
+    //         }
+    //     }
+    // }
+
+
+}
diff --git a/demos/mcapi_povcdl_estimation.cpp b/demos/mcapi_povcdl_estimation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..82bf7d64e2c9779a17a590fc7b9a6cf2b58cded1
--- /dev/null
+++ b/demos/mcapi_povcdl_estimation.cpp
@@ -0,0 +1,969 @@
+//--------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--------
+#include <iostream>
+// #include <random>
+#include <yaml-cpp/yaml.h>
+#include <Eigen/Dense>
+#include <ctime>
+#include "eigenmvn.h"
+
+#include "pinocchio/parsers/urdf.hpp"
+#include "pinocchio/algorithm/joint-configuration.hpp"
+#include "pinocchio/algorithm/kinematics.hpp"
+#include "pinocchio/algorithm/center-of-mass.hpp"
+#include "pinocchio/algorithm/centroidal.hpp"
+#include "pinocchio/algorithm/rnea.hpp"
+#include "pinocchio/algorithm/crba.hpp"
+#include "pinocchio/algorithm/frames.hpp"
+
+// MCAPI
+#include <curves/fwd.h>
+#include <curves/so3_linear.h>
+#include <curves/se3_curve.h>
+#include <curves/polynomial.h>
+#include <curves/bezier_curve.h>
+#include <curves/piecewise_curve.h>
+#include <curves/exact_cubic.h>
+#include <curves/cubic_hermite_spline.h>
+#include "multicontact-api/scenario/contact-sequence.hpp"
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_block_absolute.h>
+#include <core/processor/processor_odom_3d.h>
+#include <core/sensor/sensor_odom_3d.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/capture/capture_leg_odom.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+#include "bodydynamics/factor/factor_inertial_kinematics.h"
+#include "bodydynamics/factor/factor_force_torque_preint.h"
+
+
+// UTILS
+#include "mcapi_utils.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using namespace pinocchio;
+using namespace multicontact_api::scenario;
+
+typedef pinocchio::SE3Tpl<double> SE3;
+typedef pinocchio::ForceTpl<double> Force;
+
+
+int main (int argc, char **argv) {
+    // retrieve parameters from yaml file
+    YAML::Node config = YAML::LoadFile("/home/mfourmy/Documents/Phd_LAAS/wolf/bodydynamics/demos/mcapi_povcdl_estimation.yaml");
+    std::string robot_urdf = config["robot_urdf"].as<std::string>();
+    int dimc = config["dimc"].as<int>();
+    double dt = config["dt"].as<double>();
+    double min_t = config["min_t"].as<double>();
+    double max_t = config["max_t"].as<double>();
+    double solve_every_sec = config["solve_every_sec"].as<double>();
+    bool solve_end = config["solve_end"].as<bool>();
+    bool noisy_measurements = config["noisy_measurements"].as<bool>();
+
+    // estimator sensor noises
+    double std_pbc_est = config["std_pbc_est"].as<double>();
+    double std_vbc_est = config["std_vbc_est"].as<double>();
+    double std_f_est = config["std_f_est"].as<double>();
+    double std_tau_est = config["std_tau_est"].as<double>();
+    double std_odom3d_est = config["std_odom3d_est"].as<double>();
+
+    // simulated sensor noises
+    double std_acc_sim = config["std_acc_sim"].as<double>();
+    double std_gyr_sim = config["std_gyr_sim"].as<double>();
+    double std_pbc_sim = config["std_pbc_sim"].as<double>();
+    double std_vbc_sim = config["std_vbc_sim"].as<double>();
+    double std_f_sim = config["std_f_sim"].as<double>();
+    double std_tau_sim = config["std_tau_sim"].as<double>();
+    // double std_odom3d_sim = config["std_odom3d_sim"].as<double>();
+    
+    // priors
+    double std_prior_p = config["std_prior_p"].as<double>();
+    double std_prior_o = config["std_prior_o"].as<double>();
+    double std_prior_v = config["std_prior_v"].as<double>();
+    double std_abs_bias_pbc = config["std_abs_bias_pbc"].as<double>();
+    double std_abs_bias_acc = config["std_abs_bias_acc"].as<double>();
+    double std_abs_bias_gyro = config["std_abs_bias_gyro"].as<double>();
+    double std_bp_drift = config["std_bp_drift"].as<double>();
+    std::vector<double> bias_pbc_prior_vec = config["bias_pbc_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> bias_pbc_prior(bias_pbc_prior_vec.data());
+    std::vector<double> bias_imu_prior_vec = config["bias_imu_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector6d> bias_imu_prior(bias_imu_prior_vec.data());
+
+    double fz_thresh = config["fz_thresh"].as<double>();
+    double scale_dist = config["scale_dist"].as<double>();
+    bool base_dist_only = config["base_dist_only"].as<bool>();
+    bool mass_dist = config["mass_dist"].as<bool>();
+    bool vbc_is_dist = config["vbc_is_dist"].as<bool>();
+    bool Iw_is_dist = config["Iw_is_dist"].as<bool>();
+    bool Lgest_is_dist = config["Lgest_is_dist"].as<bool>();
+
+    std::string contact_sequence_file = config["contact_sequence_file"].as<std::string>();
+
+    ContactSequence cs;
+    std::cout << "Loading file " << contact_sequence_file << std::endl;
+    cs.loadFromBinary(contact_sequence_file);
+    
+    auto q_traj = cs.concatenateQtrajectories();
+    auto dq_traj = cs.concatenateDQtrajectories();
+    auto ddq_traj = cs.concatenateDDQtrajectories();
+    auto c_traj = cs.concatenateCtrajectories();
+    auto dc_traj = cs.concatenateDCtrajectories();
+    auto L_traj = cs.concatenateLtrajectories();
+    auto tau_traj = cs.concatenateTauTrajectories();
+    std::vector<std::string> ee_names = cs.getAllEffectorsInContact();
+    unsigned int nbc = ee_names.size();
+    std::vector<curves::piecewise_t> cforce_traj_lst;
+    for (auto ee_name: ee_names){
+        std::cout << ee_name << std::endl;
+        auto cforce_traj = cs.concatenateContactForceTrajectories(ee_name);
+        cforce_traj_lst.push_back(cforce_traj);
+    }
+
+    if (min_t < 0){
+        min_t = q_traj.min();
+    }
+    if (max_t < 0){
+        max_t = q_traj.max();
+    }
+
+    // initialize some vectors and fill them with dicretized data
+    std::vector<double> t_arr;                // timestamps
+    std::vector<VectorXd> q_traj_arr;         // [p_ob, q_ob, qA]
+    std::vector<VectorXd> dq_traj_arr;        // [spvel_b, qA_dot]
+    std::vector<VectorXd> ddq_traj_arr;       // [spacc_b, qA_ddot]
+    std::vector<VectorXd> c_traj_arr;         // w_p_wc
+    std::vector<VectorXd> dc_traj_arr;        // w_v_wc
+    std::vector<VectorXd> L_traj_arr;         // w_Lc: angular momentum at the CoM expressed in CoM (=World) frame
+    std::vector<VectorXd> tau_traj_arr;         // w_Lc: angular momentum at the CoM expressed in CoM (=World) frame
+    std::vector<VectorXd> bp_traj_arr;        // bias on b_p_bc: bias on the CoM position expressed in base frame
+    std::vector<std::vector<VectorXd>> cforce_traj_arr_lst(nbc); 
+    int N = (max_t - min_t)/dt;
+    int i_t = 0;
+    for (double t=min_t; t <= max_t; t += dt){
+        t_arr.push_back(t);
+        q_traj_arr.push_back(q_traj(t));
+        dq_traj_arr.push_back(dq_traj(t));
+        ddq_traj_arr.push_back(ddq_traj(t));
+        c_traj_arr.push_back(c_traj(t));
+        dc_traj_arr.push_back(dc_traj(t));
+        L_traj_arr.push_back(L_traj(t));
+        tau_traj_arr.push_back(tau_traj(t));
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            cforce_traj_arr_lst[i_ee].push_back(cforce_traj_lst[i_ee](t));
+        }
+        i_t++;
+    }
+    std::cout << "Trajectory extracted, proceed to create sensor data" << std::endl;
+
+    // Creating measurements from simulated trajectory
+    // Load the urdf model
+    Model model;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model);
+    std::cout << "model name: " << model.name << std::endl;
+    Data data(model);
+
+    // distorted model (modified inertias)
+    Model model_dist;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model_dist);
+    Eigen::EigenMultivariateNormal<double> noise_mass = Eigen::EigenMultivariateNormal<double>(Vector1d::Zero(), Matrix1d::Identity(), false);
+    Eigen::EigenMultivariateNormal<double> noise_lever = Eigen::EigenMultivariateNormal<double>(Vector3d::Zero(), Matrix3d::Identity(), false);
+    if (base_dist_only){
+        if (!mass_dist){
+            int root_joint_id = model.getJointId("root_joint");
+            Vector3d lever_dist = model.inertias[root_joint_id].lever() + scale_dist * noise_lever.samples(1); 
+            model_dist.inertias[root_joint_id] = pinocchio::Inertia(model.inertias[root_joint_id].mass(), lever_dist, model.inertias[root_joint_id].inertia());
+        } 
+    }
+    else{
+        for (int i=0; i < model_dist.inertias.size(); i++){
+            if (mass_dist){
+                double mass_dist = model.inertias[i].mass() + scale_dist * model.inertias[i].mass() * noise_mass.samples(1)(0,0);
+                model_dist.inertias[i] = pinocchio::Inertia(mass_dist, model.inertias[i].lever(), model.inertias[i].inertia()); 
+                std::cout << "mass model      " << model.inertias[i].mass() << std::endl;
+                std::cout << "mass model_dist " << model_dist.inertias[i].mass() << std::endl;
+            }
+            else {
+                Vector3d lever_dist = model.inertias[i].lever() + scale_dist * noise_lever.samples(1); 
+                model_dist.inertias[i] = pinocchio::Inertia(model.inertias[i].mass(), lever_dist, model.inertias[i].inertia()); 
+            }
+        }
+    }
+    Data data_dist(model_dist);
+
+    // Recover end effector frame ids
+    std::vector<int> ee_ids;
+    std::cout << "Frame ids" << std::endl;
+    for (auto ee_name: ee_names){
+        ee_ids.push_back(model.getFrameId(ee_name));
+        std::cout << ee_name << std::endl;
+    }
+    // std::vector<Vector3d> contacts = contacts_from_footrect_center();  // for Humanoid only TODO
+
+
+    ///////////////////////////////////////////////
+    // Create some vectors to aggregate the groundtruth and measurements
+    // Groundtruth
+    std::vector<Vector3d> p_ob_gtr_v; 
+    std::vector<Vector4d> q_ob_gtr_v; 
+    std::vector<Vector3d> v_ob_gtr_v; 
+    // std::vector<VectorXd>& c_gtr_v = c_traj_arr;   // already have it directly
+    // std::vector<VectorXd>& cd_gtr_v = dc_traj_arr; // already have it directly
+    // std::vector<Vector3d> L_traj_arr;              // already have it directly
+
+    // Measurements
+    std::vector<Vector3d> acc_b_meas_v; 
+    std::vector<Vector3d> w_b_meas_v; 
+    std::vector<std::vector<Vector3d>> p_bl_meas_v(nbc);
+    std::vector<std::vector<Vector4d>> q_bl_meas_v(nbc);
+    std::vector<std::vector<VectorXd>> wrench_meas_v(nbc); 
+    std::vector<Vector3d> p_bc_meas_v; 
+    std::vector<Vector3d> v_bc_meas_v; 
+    std::vector<Vector3d> Lq_meas_v; 
+    std::vector<Matrix3d> Iq_meas_v; 
+    // define vectors to aggregate ground truth and simulated data
+    for (unsigned int i = 0; i < q_traj_arr.size(); i++)
+    {
+        /**
+         * Groundtruth to recover (in world frame, vels/inertial frame):
+         * b position                                  --> OK
+         * b orientation (quat)                        --> OK
+         * b velocity: linear classical one            --> OK
+         * CoM posi                                    --> OK
+         * CoM vel                                     --> OK                
+         * Angular momentum                            --> OK
+         * 
+         * Measures to recover (in local frames):
+         * IMU readings                                --> OK
+         * Kinematics readings                         --> OK
+         * Wrench readings                             --> OK
+         * CoM relative position/vel                   --> OK
+         * Iq, Lq                                      --> OK
+         *            
+        **/
+
+        // retrieve traj data
+        VectorXd q = q_traj_arr[i];
+        VectorXd dq = dq_traj_arr[i];
+        VectorXd ddq = ddq_traj_arr[i];
+        // Vector3d c = c_traj_arr[i];      // gtr
+        // VectorXd dc = dc_traj_arr[i];    // gtr
+        // VectorXd L = L_traj_arr[i];      // gtr
+        VectorXd tau = tau_traj_arr[i];
+
+        // std::cout << "q " << q.transpose() << std::endl;
+        // std::cout << "dq " << dq.transpose() << std::endl;
+        // std::cout << "ddq " << ddq.transpose() << std::endl;
+
+        //////////////////////////////////
+        //////////////////////////////////
+        // Set ground truth variables
+        Vector3d p_wb = q.head<3>();        // gtr
+        Vector4d quat_wb = q.segment<4>(3); // gtr
+        SE3 oTb(Quaterniond(quat_wb).toRotationMatrix(), p_wb);  // global frame pose
+        // body vel is expressed in body frame
+        Vector3d b_v_b = dq.head<3>();  // vsp: spatial linear velocity
+        Vector3d b_w_b = dq.segment<3>(3);  // meas
+        Vector3d b_asp_b = ddq.head<3>();   // body spatial acceleration in body plucker frame
+
+        Vector3d w_v_wb = oTb.rotation() * b_v_b;  // gtr
+
+        ////////////////////////////////////
+        ////////////////////////////////////
+        // Get measurements
+
+        // IMU readings
+        // We use every quantity (spatial acc, body angvel, dr=lin spatial linvel) in the same body frame -> we get acc in body frame
+        Vector3d b_acc = b_asp_b + b_w_b.cross(b_v_b);
+        // universe o frame in mcapi/pinocchio is supposed to have same reference point and orientation as WOLF world frame
+        Vector3d b_proper_acc = b_acc - oTb.rotation().transpose() * gravity();  // meas
+
+        // update and retrieve kinematics
+        forwardKinematics(model, data, q);
+        updateFramePlacements(model, data);
+
+        // compute all linear jacobians (only for quadruped)
+        MatrixXd Jlinvel(12, model.nv); Jlinvel.setZero();
+        for (int i_ee=0; i_ee < nbc; i_ee++){
+            MatrixXd Jee(6, model.nv); Jee.setZero();
+            computeFrameJacobian(model, data, q, ee_ids[i_ee], Jee);
+            Jlinvel.block(3*i_ee, 0, 3, model.nv) = Jee.topRows<3>();
+        }
+
+        // estimate forces from torques
+        VectorXd tau_cf = rnea(model, data, q, dq, ddq);  // compute total torques applied on the joints (and free flyer)
+        tau_cf.tail(model.nv-6) -= tau;  // remove measured joint torque (not on the free flyer)
+
+        // VectorXd forces = Jlinvel_reduced.transpose().lu().solve(tau_joints); // works only for exactly determined system, removing free flyer from the system -> 12x12 J mat 
+        // Solve in Least square sense (3 ways):
+        // VectorXd forces = Jlinvel.transpose().bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(tau_cf);  // SVD
+        VectorXd forces = Jlinvel.transpose().colPivHouseholderQr().solve(tau_cf);  // QR
+        // (A.transpose() * A).ldlt().solve(A.transpose() * b)  // solve the normal equation (twice less accurate than other 2)
+
+        Vector3d o_f_tot = Vector3d::Zero();
+        std::cout << "" << std::endl;
+        std::cout << "t: " << t_arr[i] << std::endl;
+        // std::cout << "ddq: " << ddq.transpose() << std::endl;
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            auto oTl = data.oMf[ee_ids[i_ee]];
+            auto bTl = oTb.inverse() * oTl;
+            Vector3d b_p_l = bTl.translation();                        // meas
+            Vector4d b_qvec_l = Quaterniond(bTl.rotation()).coeffs();  // meas
+            p_bl_meas_v[i_ee].push_back(b_p_l);
+            q_bl_meas_v[i_ee].push_back(b_qvec_l);
+            // TODO: Only for 6D wrench or 3D force
+
+            // Compute either from torques or from given force and compare
+            std::cout << "f_force_from_tau - f_force_from_cs_file: " << forces.segment(3*i_ee, 3).transpose() - cforce_traj_arr_lst[i_ee][i].transpose() << std::endl;
+
+            Vector3d l_f_l = forces.segment(3*i_ee, 3);
+            // wrench_meas_v[i_ee].push_back(cforce_traj_arr_lst[i_ee][i]);  // EITHER
+            wrench_meas_v[i_ee].push_back(l_f_l);
+
+            Vector3d o_f_l = oTl.rotation() * l_f_l;
+            // std::cout << "o_p_" << ee_names[i_ee] << " " << oTl.translation().transpose() << std::endl;
+            // std::cout << "o_f_" << ee_names[i_ee] << " " << o_f_l.transpose() << std::endl;
+            o_f_tot += o_f_l;
+        }
+        // std::cout << "o_f_tot:       " << o_f_tot.transpose() << std::endl;
+        std::cout << "o_f_tot + m*g: " << (o_f_tot + data.mass[0]*model.gravity.linear()).transpose() << std::endl;
+        std::cout << "pin.gravity - wolf gravity: " << model.gravity.linear() - wolf::gravity() << std::endl;
+
+
+        //////////////////////////////////////////////
+        // COM relative position and velocity measures
+        VectorXd q_static = q;
+        VectorXd dq_static = dq;
+        q_static.head<7>() << 0,0,0, 0,0,0,1;
+        dq_static.head<6>() << 0,0,0, 0,0,0;
+        // compute for both perfect and distorted models to get the bias
+        centerOfMass(model, data, q_static, dq_static);        
+        centerOfMass(model_dist, data_dist, q_static, dq_static);
+
+        // c =def  w_p_wc
+        // Vector3d b_p_bc = oTb.inverse().act(c);  // meas   
+        Vector3d b_p_bc = data.com[0];  // meas   
+        Vector3d b_p_bc_dist = data_dist.com[0];  // meas   
+        Vector3d bias_pbc = b_p_bc_dist - b_p_bc;
+
+        // velocity due to to gesticulation only in base frame 
+        // -> set world frame = base frame and set body frame spatial vel to zero 
+        Vector3d b_v_bc = data.vcom[0];  // meas
+        Vector3d b_v_bc_dist = data_dist.vcom[0];  // meas
+        Vector3d bias_vbc = b_v_bc_dist - b_v_bc;
+
+        /////////////////////////
+        // Centroidal inertia and gesticulation kinetic momentum 
+        Matrix3d b_Iw = compute_Iw(model, data, q_static, b_p_bc);
+        Matrix3d b_Iw_dist = compute_Iw(model_dist, data_dist, q_static, b_p_bc_dist);
+        Matrix3d bias_Iw = b_Iw_dist - b_Iw;
+
+        // gesticulation in base frame: just compute centroidal momentum with static q and vq
+        Vector3d b_Lc_gesti = computeCentroidalMomentum(model, data, q_static, dq_static).angular();
+        Vector3d b_Lc_gesti_dist = computeCentroidalMomentum(model_dist, data_dist, q_static, dq_static).angular();
+        Vector3d bias_Lc = b_Lc_gesti_dist - b_Lc_gesti;
+        
+        
+        // std::cout << "bias_pbc: " << bias_pbc.transpose() << std::endl;
+        // std::cout << "bias_vbc: " << bias_vbc.transpose() << std::endl;
+        // std::cout << "bias_Iw: \n" << bias_Iw << std::endl;
+        // std::cout << "bias_Lc: " << bias_Lc.transpose() << std::endl;
+
+        /////////////////////////
+        // Agreggate everything in vectors for easier WOLF consumption
+        p_ob_gtr_v.push_back(p_wb); 
+        q_ob_gtr_v.push_back(quat_wb); 
+        v_ob_gtr_v.push_back(w_v_wb); 
+        bp_traj_arr.push_back(bias_pbc);
+        // Lc_gtr_v; 
+
+        // Measurements
+        acc_b_meas_v.push_back(b_proper_acc); 
+        w_b_meas_v.push_back(b_w_b); 
+        p_bc_meas_v.push_back(b_p_bc_dist); 
+        if (vbc_is_dist){
+            v_bc_meas_v.push_back(b_v_bc_dist); 
+        }
+        else {
+            v_bc_meas_v.push_back(b_v_bc); 
+        }
+        if (Lgest_is_dist){
+            Lq_meas_v.push_back(b_Lc_gesti_dist);
+        }
+        else{
+            Lq_meas_v.push_back(b_Lc_gesti);
+        }
+        if (Iw_is_dist){
+            Iq_meas_v.push_back(b_Iw_dist);
+        }
+        else {
+            Iq_meas_v.push_back(b_Iw);
+        }
+    }
+
+    /////////////////////////
+    // WOLF enters the scene
+    // SETUP PROBLEM/SENSORS/PROCESSORS
+    std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+    ProblemPtr problem = Problem::create("POVCDL", 3);
+
+    // CERES WRAPPER
+    SolverCeresPtr solver = std::make_shared<SolverCeres>(problem);
+
+    //===================================================== INITIALIZATION
+    // SENSOR + PROCESSOR INERTIAL KINEMATICS
+    ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+    intr_ik->std_pbc = std_pbc_est;
+    intr_ik->std_vbc = std_vbc_est;
+    VectorXd extr; // default size for dynamic vector is 0-0 -> what we need
+    SensorBasePtr sen_ikin_base = problem->installSensor("SensorInertialKinematics", "SenIK", extr, intr_ik);
+    // SensorBasePtr sen_ikin_base = problem->installSensor("SensorInertialKinematics", "SenIK", extr,  bodydynamics_root_dir + "/demos/sensor_inertial_kinematics.yaml");  // TODO: does not work!
+    SensorInertialKinematicsPtr sen_ikin = std::static_pointer_cast<SensorInertialKinematics>(sen_ikin_base);
+
+    ParamsProcessorInertialKinematicsPtr params_ik = std::make_shared<ParamsProcessorInertialKinematics>();
+    params_ik->sensor_angvel_name = "SenImu";
+    params_ik->std_bp_drift = std_bp_drift;
+    ProcessorBasePtr proc_ikin_base = problem->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", sen_ikin, params_ik);
+    // ProcessorBasePtr proc_ikin_base = problem->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", "SenIK", bodydynamics_root_dir + "/demos/processor_inertial_kinematics.yaml");
+    ProcessorInertialKinematicsPtr proc_inkin = std::static_pointer_cast<ProcessorInertialKinematics>(proc_ikin_base);
+
+    // SENSOR + PROCESSOR IMU
+    SensorBasePtr sen_imu_base = problem->installSensor("SensorImu", "SenImu", (Vector7d() << 0, 0, 0, 0, 0, 0, 1).finished(), bodydynamics_root_dir + "/demos/sensor_imu.yaml");
+    SensorImuPtr sen_imu = std::static_pointer_cast<SensorImu>(sen_imu_base);
+    ProcessorBasePtr proc_ftp_base = problem->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/demos/processor_imu_mcapi_demo.yaml");
+    ProcessorImuPtr proc_imu = std::static_pointer_cast<ProcessorImu>(proc_ftp_base);
+
+    // SENSOR + PROCESSOR FT PREINT
+    ParamsSensorForceTorquePtr intr_ft = std::make_shared<ParamsSensorForceTorque>();
+    intr_ft->std_f = std_f_est;
+    intr_ft->std_tau = std_tau_est;
+    intr_ft->mass = data.mass[0];
+    std::cout << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << "\n\nROBOT MASS: " << intr_ft->mass << std::endl;
+    SensorBasePtr sen_ft_base = problem->installSensor("SensorForceTorque", "SenFT", VectorXd(0), intr_ft);
+    // SensorBasePtr sen_ft_base = problem->installSensor("SensorForceTorque", "SenFT", VectorXd(0), bodydynamics_root_dir + "/demos/sensor_ft.yaml");
+    SensorForceTorquePtr sen_ft = std::static_pointer_cast<SensorForceTorque>(sen_ft_base);
+    ParamsProcessorForceTorquePreintPtr params_sen_ft = std::make_shared<ParamsProcessorForceTorquePreint>();
+    params_sen_ft->sensor_ikin_name = "SenIK";
+    params_sen_ft->sensor_angvel_name = "SenImu";
+    params_sen_ft->nbc = nbc;
+    params_sen_ft->dimc = dimc;
+    params_sen_ft->time_tolerance = 0.0005;
+    params_sen_ft->unmeasured_perturbation_std = 0.000001;
+    params_sen_ft->max_time_span = 1000;
+    params_sen_ft->max_buff_length = 500;
+    params_sen_ft->dist_traveled = 20000.0;
+    params_sen_ft->angle_turned = 1000;
+    params_sen_ft->voting_active = false;
+    ProcessorBasePtr proc_ft_base = problem->installProcessor("ProcessorForceTorquePreint", "PFTPreint", sen_ft, params_sen_ft);
+    // ProcessorBasePtr proc_ft_base = problem->installProcessor("ProcessorForceTorquePreint", "PFTPreint", "SenFT", bodydynamics_root_dir + "/demos/processor_ft_preint");
+    ProcessorForceTorquePreintPtr proc_ftpreint = std::static_pointer_cast<ProcessorForceTorquePreint>(proc_ft_base);
+
+    // SENSOR + PROCESSOR ODOM3D FOR LEG ODOMETRY
+    Vector7d extr_senlegodom; extr_senlegodom << 0,0,0, 0,0,0,1;  // not used in practice 
+    SensorBasePtr sen_odom3d_base = problem->installSensor("SensorOdom3d", "SenLegOdom", extr_senlegodom, bodydynamics_root_dir + "/demos/sensor_odom_3d.yaml");
+    SensorOdom3dPtr sen_odom3d = std::static_pointer_cast<SensorOdom3d>(sen_odom3d_base);
+    ParamsProcessorOdom3dPtr params_sen_odom3d = std::make_shared<ParamsProcessorOdom3d>();
+    // ProcessorBasePtr proc_legodom_base = problem->installProcessor("ProcessorOdom3d", "ProcLegOdom", sen_odom3d, params_sen_odom3d);
+    ProcessorBasePtr proc_legodom_base = problem->installProcessor("ProcessorOdom3d", "ProcLegOdom", "SenLegOdom", bodydynamics_root_dir + "/demos/processor_odom_3d.yaml");
+    ProcessorOdom3dPtr proc_legodom = std::static_pointer_cast<ProcessorOdom3d>(proc_legodom_base);
+    //////////////////////////////
+
+
+    // SETPRIOR RETRO-ENGINEERING
+    // We are not using setPrior because of processors initial captures problems so we have to
+    // - Manually create the first KF and its pose and velocity priors
+    // - call setOrigin on processors MotionProvider since the flag prior_is_set is not true when doing installProcessor (later)
+    TimeStamp t0(t_arr[0]);
+    VectorXd x_origin; x_origin.resize(19);
+    x_origin << p_ob_gtr_v[0], q_ob_gtr_v[0], v_ob_gtr_v[0], c_traj_arr[0], dc_traj_arr[0], L_traj_arr[0];
+    MatrixXd P_origin(9,9); P_origin.setIdentity();
+    P_origin.block<3,3>(0,0)   = pow(std_prior_p, 2) * Matrix3d::Identity();
+    P_origin.block<3,3>(3,3)   = pow(std_prior_o, 2) * Matrix3d::Identity();
+    P_origin.block<3,3>(6,6)   = pow(std_prior_v, 2) * Matrix3d::Identity();
+    FrameBasePtr KF1 = problem->emplaceFrame(t0, x_origin);
+    // Prior pose factor
+    CapturePosePtr pose_prior_capture = CaptureBase::emplace<CapturePose>(KF1, t0, nullptr, x_origin.head(7), P_origin.topLeftCorner(6, 6));
+    pose_prior_capture->emplaceFeatureAndFactor();
+    // Prior velocity factor
+    CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF1, "Vel0", t0);
+    FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin.segment<3>(7), P_origin.block<3, 3>(6, 6));
+    FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, featV0, KF1->getV(), nullptr, false);
+
+    // Special trick to make things work in the IMU + IKin + FTPreint case
+    // 0. Call keyFrameCallback of processorIKin so that its kf_buffer contains the first KF0
+    // 1. Call setOrigin processorIMU  to generate a fake captureIMU -> generates b_imu
+    // 2. process one IKin capture corresponding to the same KF, the factor needing b_imu -> generates a b_pbc
+    // 3. set processorForceTorquePreint origin which requires both b_imu and b_pbc
+    proc_inkin->keyFrameCallback(KF1, 0.0005);
+    proc_imu->setOrigin(KF1);
+    proc_legodom->setOrigin(KF1);
+
+    // INERTIAL KINEMATICS CAPTURE DATA ZERO MOTION
+    // momentum parameters
+    Matrix<double, 9, 1> meas_ikin; meas_ikin << p_bc_meas_v[0], v_bc_meas_v[0], w_b_meas_v[0];
+    auto CIKin0 = std::make_shared<CaptureInertialKinematics>(t0, sen_ikin, meas_ikin, Iq_meas_v[0], Lq_meas_v[0]);
+    CIKin0->process();
+    proc_ftpreint->setOrigin(KF1);
+
+
+    ////////////////////////////////////////////
+    // INITIAL BIAS FACTORS
+    // Add absolute factor on Imu biases to simulate a fix()
+    Vector6d std_abs_imu; std_abs_imu << std_abs_bias_acc*Vector3d::Ones(), std_abs_bias_gyro*Vector3d::Ones();
+    Matrix6d Q_bi = std_abs_imu.array().square().matrix().asDiagonal();
+    CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1, "bi0", t0);
+    FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", bias_imu_prior, Q_bi);
+    FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1->getCaptureOf(sen_imu)->getSensorIntrinsic(), nullptr, false);   
+
+    // Add loose absolute factor on initial bp bias since dynamical trajectories 
+    Matrix3d Q_bp = pow(std_abs_bias_pbc, 2)* Matrix3d::Identity();
+    CaptureBasePtr cap_bp0 = CaptureBase::emplace<CaptureBase>(KF1, "bp0", t0);
+    FeatureBasePtr feat_bp0 = FeatureBase::emplace<FeatureBase>(cap_bp0, "bp0", bias_pbc_prior, Q_bp);
+    FactorBasePtr fac_bp0 = FactorBase::emplace<FactorBlockAbsolute>(feat_bp0, feat_bp0, KF1->getCaptureOf(sen_ikin)->getSensorIntrinsic(), nullptr, false);   
+
+
+    ///////////////////////////////////////////
+    // process measurements in sequential order
+    // SE3 oTb_prev(Quaterniond(q_ob_gtr_v[0]).toRotationMatrix(), p_ob_gtr_v[0]);
+    // FrameBasePtr KF_prev = KF1;
+    double ts_since_last_kf = 0;
+
+    // Add gaussian noises
+    std::time_t epoch = std::time(nullptr);
+    int64_t seed = (int64_t)epoch;
+	Eigen::EigenMultivariateNormal<double> noise_acc(Vector3d::Zero(), pow(std_acc_sim, 2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_gyr(Vector3d::Zero(), pow(std_gyr_sim, 2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_pbc(Vector3d::Zero(), pow(std_pbc_sim, 2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_vbc(Vector3d::Zero(), pow(std_vbc_sim, 2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_f  (Vector3d::Zero(), pow(std_f_sim,   2)*Matrix3d::Identity(), false, seed);
+	Eigen::EigenMultivariateNormal<double> noise_tau(Vector3d::Zero(), pow(std_tau_sim, 2)*Matrix3d::Identity(), false, seed);
+
+    //////////////////////////////////////////
+    // Vectors to store estimates at the time of data processing 
+    // fbk -> feedback: to check if the estimation is stable enough for feedback control
+    std::vector<Vector3d> p_ob_fbk_v; 
+    std::vector<Vector4d> q_ob_fbk_v; 
+    std::vector<Vector3d> v_ob_fbk_v; 
+    std::vector<Vector3d> c_ob_fbk_v; 
+    std::vector<Vector3d> cd_ob_fbk_v; 
+    std::vector<Vector3d> Lc_ob_fbk_v; 
+    //////////////////////////////////////////
+
+    for (unsigned int i=0; i < t_arr.size(); i++){
+        // TimeStamp ts(t_arr[i]+dt); // works best with tsid trajectories
+        TimeStamp ts(t_arr[i]);  // works best with pyb trajectories
+
+        // IMU
+        Vector6d acc_gyr_meas; acc_gyr_meas << acc_b_meas_v[i], w_b_meas_v[i];
+        
+        if (noisy_measurements){
+            acc_gyr_meas.head<3>() += noise_acc.samples(1);
+            acc_gyr_meas.tail<3>() += noise_gyr.samples(1);
+        }
+
+        Matrix6d acc_gyr_cov = sen_imu->getNoiseCov();
+
+        // Inertial kinematics
+        meas_ikin << p_bc_meas_v[i], v_bc_meas_v[i], w_b_meas_v[i];
+        // meas_ikin << p_bc_meas_v[ii], v_bc_meas_v[ii], w_b_meas_v[ii];
+        // meas_ikin << p_bc_meas_v[ii+1], v_bc_meas_v[ii+1], w_b_meas_v[ii+1];
+
+        if (noisy_measurements){
+            meas_ikin.segment<3>(0) += noise_pbc.samples(1);
+            meas_ikin.segment<3>(3) += noise_vbc.samples(1);
+        }
+
+        ////////////////////
+        // Force Torque
+        ////////////////////
+
+
+        VectorXd f_meas(nbc*3); 
+        VectorXd tau_meas(nbc*3);
+        VectorXd kin_meas(nbc*7);
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            if (dimc == 3){
+                f_meas.segment<3>(3*i_ee) = wrench_meas_v[i_ee][i]; 
+            }
+            else if (dimc == 6){
+                f_meas.segment<3>(3*i_ee) = wrench_meas_v[i_ee][i].head<3>(); 
+                tau_meas.segment<3>(3*i_ee) = wrench_meas_v[i_ee][i].tail<3>(); 
+            }
+            kin_meas.segment<3>(i_ee*3)         = p_bl_meas_v[i_ee][i];
+            kin_meas.segment<4>(nbc*3 + i_ee*4) = q_bl_meas_v[i_ee][i];
+        }
+
+        if (noisy_measurements){
+            f_meas.segment<3>(0) += noise_f.samples(1);
+            f_meas.segment<3>(3) += noise_f.samples(1);
+            tau_meas.segment<3>(0) += noise_tau.samples(1);
+            tau_meas.segment<3>(3) += noise_tau.samples(1);
+        }
+
+        VectorXd cap_ftp_data(dimc*nbc+3+3+nbc*7); 
+        MatrixXd Qftp(dimc*nbc+3+3,dimc*nbc+3+3);  // kin not in covariance mat
+        if (dimc == 3){
+            cap_ftp_data << f_meas, p_bc_meas_v[i], w_b_meas_v[i], kin_meas;
+            Qftp.setIdentity();
+            Qftp.block(0,0, nbc*3,nbc*3) *= pow(std_f_est, 2);
+            Qftp.block<3, 3>(nbc*dimc, nbc*dimc, 3,3) *= pow(std_pbc_est, 2);
+            Qftp.block<3, 3>(nbc*dimc+3,nbc*dimc+3, 3,3) = acc_gyr_cov.bottomRightCorner<3,3>();
+        }
+        if (dimc == 6){
+            cap_ftp_data << f_meas, tau_meas, p_bc_meas_v[i], w_b_meas_v[i], kin_meas;
+            Qftp.setIdentity();
+            Qftp.block(0,0, nbc*3,nbc*3) *= pow(std_f_est, 2);
+            Qftp.block(nbc*3,nbc*3, nbc*3,nbc*3) *= pow(std_tau_est, 2);
+            Qftp.block<3, 3>(nbc*dimc, nbc*dimc, 3,3) *= pow(std_pbc_est, 2);
+            Qftp.block<3, 3>(nbc*dimc+3,nbc*dimc+3, 3,3) = acc_gyr_cov.bottomRightCorner<3,3>();        
+        }
+        ////////////////////
+        /////////////////
+
+        CaptureImuPtr CImu = std::make_shared<CaptureImu>(ts, sen_imu, acc_gyr_meas, acc_gyr_cov);
+        CImu->process();
+        auto CIKin = std::make_shared<CaptureInertialKinematics>(ts, sen_ikin, meas_ikin, Iq_meas_v[i], Lq_meas_v[i]);
+        CIKin->process();
+        auto CFTpreint = std::make_shared<CaptureForceTorquePreint>(ts, sen_ft, CIKin, CImu, cap_ftp_data, Qftp);
+        CFTpreint->process();
+
+
+
+        if (i > 0){
+            // Leg odometry
+            // 1) detect feet in contact
+            std::vector<int> feet_in_contact;
+            for (unsigned int i_ee=0; i_ee<nbc; i_ee++){
+                // basic contact detection based on normal foot vel, things break if no foot is in contact
+                if (wrench_meas_v[i_ee][i].norm() > fz_thresh){
+                    feet_in_contact.push_back(i_ee);
+                }
+            }
+            // 2) fill the leg odometry data matrix, depending on the contact dimension
+            Eigen::MatrixXd data_legodom;            
+            if (dimc == 6){
+                // cols organized as:
+                // [pbl1_km; qbl1_k], [pbl2_km; qbl2_k], ...
+                data_legodom.resize(7,feet_in_contact.size()*2);
+                data_legodom.setZero();
+                int j = 0;
+                for (int i_ee_c: feet_in_contact){
+                    data_legodom.col(2*j  ) << p_bl_meas_v[i_ee_c][i-1], q_bl_meas_v[i_ee_c][i-1];
+                    data_legodom.col(2*j+1) << p_bl_meas_v[i_ee_c][i  ], q_bl_meas_v[i_ee_c][i];
+                    j++;
+                }
+            }
+            else if (dimc == 3){
+                // cols organized as:
+                // pbl1_km, pbl1_k, pbl2_km, pbl2_k, ..., w_b
+                data_legodom.resize(3,feet_in_contact.size()*2 + 1);
+                data_legodom.setZero();
+                int j = 0;
+                for (int i_ee_c: feet_in_contact){
+                    data_legodom.col(2*j  ) << p_bl_meas_v[i_ee_c][i-1];
+                    data_legodom.col(2*j+1) << p_bl_meas_v[i_ee_c][i  ];
+                    j++;
+                }
+                data_legodom.rightCols<1>() = acc_gyr_meas.tail<3>();
+            }
+            // std::cout << "data_legodom\n" << data_legodom << std::endl;
+            // 3) process the data and its cov
+            Eigen::Matrix6d data_legodom3D_cov = pow(std_odom3d_est, 2) * Eigen::Matrix6d::Identity(); 
+            CaptureLegOdomPtr CLO = std::make_shared<CaptureLegOdom>(ts, sen_odom3d, data_legodom, data_legodom3D_cov, dt, CaptureLegOdomType::POINT_FEET_RELATIVE_KIN);
+            CLO->process();
+        }
+
+        ts_since_last_kf += dt;
+        if ((solve_every_sec > 0) && (ts_since_last_kf >= solve_every_sec)){
+            // problem->print(4,true,true,true);
+            std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+            std::cout << report << std::endl;
+            ts_since_last_kf = 0;
+        }
+
+
+        // Store current state estimation
+        VectorComposite curr_state = problem->getState(ts);
+        p_ob_fbk_v.push_back(curr_state['P']);
+        q_ob_fbk_v.push_back(curr_state['O']);
+        v_ob_fbk_v.push_back(curr_state['V']);
+        c_ob_fbk_v.push_back(curr_state['C']);
+        cd_ob_fbk_v.push_back(curr_state['D']);
+        Lc_ob_fbk_v.push_back(curr_state['L']);
+    }
+    
+
+
+    ///////////////////////////////////////////
+    //////////////// SOLVING //////////////////
+    ///////////////////////////////////////////
+    problem->print(4,true,true,true);
+    if (solve_end){
+        std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        problem->print(4,true,true,true);
+        std::cout << report << std::endl;
+    }
+
+    //////////////////////////////////////
+    //////////////////////////////////////
+    //            STORE DATA            //
+    //////////////////////////////////////
+    //////////////////////////////////////
+    std::fstream file_gtr; 
+    std::fstream file_est; 
+    std::fstream file_fbk; 
+    std::fstream file_kfs; 
+    std::fstream file_cov; 
+    std::fstream file_wre; 
+    file_gtr.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/gtr.csv", std::fstream::out); 
+    file_est.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/est.csv", std::fstream::out); 
+    file_fbk.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/fbk.csv", std::fstream::out); 
+    file_kfs.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/kfs.csv", std::fstream::out); 
+    file_cov.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/cov.csv", std::fstream::out); 
+    file_wre.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/wre.csv", std::fstream::out); 
+    std::string header_est = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz,cx,cy,cz,cdx,cdy,cdz,Lcx,Lcy,Lcz\n";
+    file_est << header_est;
+    file_fbk << header_est;
+    std::string header_kfs = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz,bax,bay,baz,bwx,bwy,bwz,cx,cy,cz,cdx,cdy,cdz,Lcx,Lcy,Lcz,bpx,bpy,bpz\n";
+    file_kfs << header_kfs;
+    file_gtr << header_kfs;  // at the same frequency as "est" but ground truth biases added for comparison with kfs 
+    std::string header_cov = "t,Qpx,Qpy,Qpz,Qqx,Qqy,Qqz,Qvx,Qvy,Qvz,Qbax,Qbay,Qbaz,Qbwx,Qbwy,Qbwz,Qcx,Qcy,Qcz,Qcdx,Qcdy,Qcdz,QLcx,QLcy,QLcz,Qbpx,Qbpy,Qbpz\n";
+    file_cov << header_cov; 
+
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        // TODO: change if simulate a non zero imu bias
+        file_gtr << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << p_ob_gtr_v[i](0) << ","
+                 << p_ob_gtr_v[i](1) << ","
+                 << p_ob_gtr_v[i](2) << "," 
+                 << q_ob_gtr_v[i](0) << "," 
+                 << q_ob_gtr_v[i](1) << "," 
+                 << q_ob_gtr_v[i](2) << "," 
+                 << q_ob_gtr_v[i](3) << "," 
+                 << v_ob_gtr_v[i](0) << "," 
+                 << v_ob_gtr_v[i](1) << "," 
+                 << v_ob_gtr_v[i](2) << "," 
+                 << 0.0 << "," 
+                 << 0.0 << "," 
+                 << 0.0 << "," 
+                 << 0.0 << "," 
+                 << 0.0 << "," 
+                 << 0.0 << "," 
+                 << c_traj_arr[i](0) << "," 
+                 << c_traj_arr[i](1) << "," 
+                 << c_traj_arr[i](2) << ","
+                 << dc_traj_arr[i](0) << "," 
+                 << dc_traj_arr[i](1) << "," 
+                 << dc_traj_arr[i](2) << ","
+                 << L_traj_arr[i](0) << ","
+                 << L_traj_arr[i](1) << "," 
+                 << L_traj_arr[i](2) << ","
+                 << bp_traj_arr[i](0) << ","
+                 << bp_traj_arr[i](1) << "," 
+                 << bp_traj_arr[i](2)
+                 << std::endl;
+    }
+
+    VectorComposite state_est;
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        state_est = problem->getState(t_arr[i]);
+        file_est << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << state_est['P'](0) << ","
+                 << state_est['P'](1) << ","
+                 << state_est['P'](2) << "," 
+                 << state_est['O'](0) << "," 
+                 << state_est['O'](1) << "," 
+                 << state_est['O'](2) << "," 
+                 << state_est['O'](3) << "," 
+                 << state_est['V'](0) << "," 
+                 << state_est['V'](1) << "," 
+                 << state_est['V'](2) << "," 
+                 << state_est['C'](0) << ","
+                 << state_est['C'](1) << ","
+                 << state_est['C'](2) << "," 
+                 << state_est['D'](0) << "," 
+                 << state_est['D'](1) << "," 
+                 << state_est['D'](2) << "," 
+                 << state_est['L'](0) << "," 
+                 << state_est['L'](1) << "," 
+                 << state_est['L'](2)
+                 << "\n"; 
+    }
+
+    for (unsigned int i=0; i < t_arr.size(); i++) { 
+        file_fbk << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << p_ob_fbk_v[i](0) << ","
+                 << p_ob_fbk_v[i](1) << ","
+                 << p_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](0) << "," 
+                 << q_ob_fbk_v[i](1) << "," 
+                 << q_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](3) << "," 
+                 << v_ob_fbk_v[i](0) << "," 
+                 << v_ob_fbk_v[i](1) << "," 
+                 << v_ob_fbk_v[i](2) << ","
+                 << c_ob_fbk_v[i](0) << "," 
+                 << c_ob_fbk_v[i](1) << "," 
+                 << c_ob_fbk_v[i](2) << "," 
+                 << cd_ob_fbk_v[i](0) << "," 
+                 << cd_ob_fbk_v[i](1) << "," 
+                 << cd_ob_fbk_v[i](2) << "," 
+                 << Lc_ob_fbk_v[i](0) << "," 
+                 << Lc_ob_fbk_v[i](1) << "," 
+                 << Lc_ob_fbk_v[i](2)
+                 << "\n";    
+    }
+
+    VectorComposite kf_state;
+    CaptureBasePtr cap_ikin;
+    VectorComposite bp_bias_est;
+    CaptureBasePtr cap_imu;
+    VectorComposite bi_bias_est;
+    for (auto& elt: problem->getTrajectory()->getFrameMap()){
+        auto kf = elt.second;
+        kf_state = kf->getState();
+        cap_ikin = kf->getCaptureOf(sen_ikin); 
+        bp_bias_est = cap_ikin->getState();
+        cap_imu = kf->getCaptureOf(sen_imu); 
+        bi_bias_est = cap_imu->getState();
+
+        file_kfs << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                    << kf->getTimeStamp().get() << ","
+                    << kf_state['P'](0) << ","
+                    << kf_state['P'](1) << ","
+                    << kf_state['P'](2) << "," 
+                    << kf_state['O'](0) << "," 
+                    << kf_state['O'](1) << "," 
+                    << kf_state['O'](2) << "," 
+                    << kf_state['O'](3) << "," 
+                    << kf_state['V'](0) << "," 
+                    << kf_state['V'](1) << "," 
+                    << kf_state['V'](2) << "," 
+                    << bi_bias_est['I'](0) << ","
+                    << bi_bias_est['I'](1) << ","
+                    << bi_bias_est['I'](2) << ","
+                    << bi_bias_est['I'](3) << ","
+                    << bi_bias_est['I'](4) << ","
+                    << bi_bias_est['I'](5) << ","
+                    << kf_state['C'](0) << "," 
+                    << kf_state['C'](1) << "," 
+                    << kf_state['C'](2) << "," 
+                    << kf_state['D'](0) << "," 
+                    << kf_state['D'](1) << "," 
+                    << kf_state['D'](2) << "," 
+                    << kf_state['L'](0) << "," 
+                    << kf_state['L'](1) << "," 
+                    << kf_state['L'](2) << ","
+                    << bp_bias_est['I'](0) << ","
+                    << bp_bias_est['I'](1) << ","
+                    << bp_bias_est['I'](2)
+                    << "\n"; 
+
+        // compute covariances of KF and capture stateblocks
+        Eigen::MatrixXd Qp =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qo =  Eigen::Matrix3d::Identity();  // global or local?
+        Eigen::MatrixXd Qv =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbi =  Eigen::Matrix6d::Identity();
+        Eigen::MatrixXd Qc =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qd =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd QL =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbp = Eigen::Matrix3d::Identity();
+        // solver->computeCovariances(kf->getStateBlockVec());     // !! does not work as intended...
+        
+        solver->computeCovariances(SolverManager::CovarianceBlocksToBeComputed::ALL);  // should not be computed every time !! see below
+        problem->getCovarianceBlock(kf->getStateBlock('P'), Qp);
+        problem->getCovarianceBlock(kf->getStateBlock('O'), Qo);
+        problem->getCovarianceBlock(kf->getStateBlock('V'), Qv);
+        problem->getCovarianceBlock(kf->getStateBlock('C'), Qc);
+        problem->getCovarianceBlock(kf->getStateBlock('D'), Qd);
+        problem->getCovarianceBlock(kf->getStateBlock('L'), QL);
+
+        solver->computeCovariances(cap_ikin->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_ikin->getSensorIntrinsic(), Qbp);
+        // std::cout << "Qbp\n" << Qbp << std::endl;
+        solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi);
+
+        file_cov << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                        << kf->getTimeStamp().get() << ","
+                        << Qp(0,0) << ","
+                        << Qp(1,1) << ","
+                        << Qp(2,2) << ","
+                        << Qo(0,0) << ","
+                        << Qo(1,1) << ","
+                        << Qo(2,2) << ","
+                        << Qv(0,0) << ","
+                        << Qv(1,1) << ","
+                        << Qv(2,2) << ","
+                        << Qbi(0,0) << ","
+                        << Qbi(1,1) << ","
+                        << Qbi(2,2) << ","
+                        << Qbi(3,3) << ","
+                        << Qbi(4,4) << ","
+                        << Qbi(5,5) << ","
+                        << Qc(0,0) << ","
+                        << Qc(1,1) << ","
+                        << Qc(2,2) << ","
+                        << Qd(0,0) << ","
+                        << Qd(1,1) << ","
+                        << Qd(2,2) << ","
+                        << QL(0,0) << ","
+                        << QL(1,1) << ","
+                        << QL(2,2) << ","
+                        << Qbp(0,0) << ","
+                        << Qbp(1,1) << ","
+                        << Qbp(2,2)
+                        << "\n"; 
+    }
+
+    file_gtr.close();
+    file_est.close();
+    file_kfs.close();
+    file_cov.close();
+    file_wre.close();
+
+
+}
diff --git a/demos/mcapi_povcdl_estimation.yaml b/demos/mcapi_povcdl_estimation.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..28b57f008d929631d65099e27a746a82c049c3da
--- /dev/null
+++ b/demos/mcapi_povcdl_estimation.yaml
@@ -0,0 +1,76 @@
+# trajectory handling
+dt: 0.001
+min_t: -1.0  # -1 means from the beginning of the trajectory
+max_t: -1  # -1 means until the end of the trajectory
+solve_every_sec: -1   # < 0 strict --> no solve
+solve_end: False
+# solve_every_sec: 0.3   # < 0 strict --> no solve
+# solve_end: True
+
+# estimator sensor noises
+std_acc_est: 0.001
+std_gyr_est: 0.001
+std_pbc_est: 0.001
+std_vbc_est: 0.001   # higher than simulated -> has to take care of the non-modeled bias as well... Good value for solo sin traj
+std_f_est:   0.001
+std_tau_est: 0.001
+# std_odom3d_est:  0.000001
+std_odom3d_est:  100000
+
+# simulated sensor noises
+std_acc_sim: 0.0001
+std_gyr_sim: 0.0001
+std_pbc_sim: 0.0001
+std_vbc_sim: 0.0001
+std_f_sim:   0.0001
+std_tau_sim: 0.0001
+std_odom3d_sim:  100000000
+
+# some controls
+fz_thresh: 1
+noisy_measurements: False
+
+# priors
+std_prior_p: 0.01
+std_prior_o: 0.1
+std_prior_v: 0.1
+
+# std_abs_biasimu: 100000
+std_abs_biasimu: 0.0000001  # almost fixed
+std_abs_bias_pbc: 10000   # noprior  
+# std_abs_bias_pbc: 0.00001  # almost fixed 
+std_bp_drift: 10000     # All the drift you want
+# std_bp_drift: 0.1     # no drift
+
+bias_pbc_prior: [0,0,0]
+
+
+# model disturbance
+scale_dist: 0.00  # disturbance of link inertia
+mass_dist: False
+base_dist_only: False
+
+# which measurement/parameter is disturbed?
+vbc_is_dist:   False
+Iw_is_dist:    False
+Lgest_is_dist: False
+
+
+# Robot model
+# robot_urdf: "/opt/openrobots/share/example-robot-data/robots/talos_data/robots/talos_reduced.urdf"
+# dimc: 6
+robot_urdf: "/opt/openrobots/share/example-robot-data/robots/solo_description/robots/solo12.urdf"
+dimc: 3
+
+
+# Trajectory files
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/talos_sin_traj.cs"
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/talos_contact_switch.cs"
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_sin_traj.cs"
+contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_sin_traj_pyb.cs"
+
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_sin_y_notrunk.cs"
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_sin_r+y.cs"
+
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_sin_r+z.cs"
+# contact_sequence_file: "/home/mfourmy/Documents/Phd_LAAS/data/trajs/solo_stamping.cs"
diff --git a/demos/mcapi_utils.cpp b/demos/mcapi_utils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d893b4669ce2514f3f15b3bf73500f243923e19c
--- /dev/null
+++ b/demos/mcapi_utils.cpp
@@ -0,0 +1,77 @@
+//--------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--------
+
+
+#include "mcapi_utils.h"
+
+Vector3d computeAccFromSpatial(const Vector3d& ddr, const Vector3d& w, const Vector3d& dr)
+{
+    return ddr + w.cross(dr);
+}
+
+
+std::vector<Vector3d> contacts_from_footrect_center()
+{
+    // compute feet corners coordinates like from the leg_X_6_joint
+    double lx = 0.1;
+    double ly = 0.065;
+    double lz = 0.107;
+    std::vector<Vector3d> contacts; contacts.resize(4);
+    contacts[0] = {-lx, -ly, -lz};
+    contacts[1] = {-lx,  ly, -lz};
+    contacts[2] = { lx, -ly, -lz};
+    contacts[3] = { lx,  ly, -lz};
+    return contacts;
+}
+
+
+Matrix<double, 6, 1> contact_force_to_wrench(const std::vector<Vector3d>& contacts, 
+                                 const Matrix<double, 12, 1>& cf12)
+{
+    Vector3d f = cf12.segment<3>(0) + cf12.segment<3>(3) + cf12.segment<3>(6) + cf12.segment<3>(9);
+    Vector3d tau = contacts[0].cross(cf12.segment<3>(0))
+                 + contacts[1].cross(cf12.segment<3>(3))
+                 + contacts[2].cross(cf12.segment<3>(6))
+                 + contacts[3].cross(cf12.segment<3>(9));
+    Matrix<double, 6, 1> wrench; wrench << f, tau;
+    return wrench;
+}
+
+
+Matrix3d compute_Iw(pinocchio::Model& model, 
+                    pinocchio::Data& data, 
+                    VectorXd& q_static, 
+                    Vector3d& b_p_bc)
+{
+    MatrixXd b_Mc = pinocchio::crba(model, data, q_static);  // mass matrix at b frame expressed in b frame
+    // MatrixXd b_Mc = crba(model_dist, data_dist, q_static);  // mass matrix at b frame expressed in b frame
+    MatrixXd b_I_b = b_Mc.topLeftCorner<6,6>();             // inertia matrix at b frame expressed in b frame 
+    pinocchio::SE3 bTc (Eigen::Matrix3d::Identity(), b_p_bc);          // "no free flyer robot -> c frame oriented the same as b"
+    pinocchio::SE3 cTb = bTc.inverse();        
+    // Ic = cXb^star * Ib * bXc = bXc^T * Ib * bXc 
+    // c_I_c block diagonal:
+    // [m*Id3, 03;
+    //  0,     Iw]
+    MatrixXd c_I_c = cTb.toDualActionMatrix() * b_I_b * bTc.toActionMatrix();
+    Matrix3d b_Iw  = c_I_c.bottomRightCorner<3,3>();  // meas
+    return b_Iw;
+}
diff --git a/demos/mcapi_utils.h b/demos/mcapi_utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..c62c4026a1dac41733350f2b96fb554285f30715
--- /dev/null
+++ b/demos/mcapi_utils.h
@@ -0,0 +1,76 @@
+//--------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--------
+#include <vector>
+#include "Eigen/Dense"
+#include "pinocchio/algorithm/crba.hpp"
+
+
+using namespace Eigen; 
+
+
+/**
+ * \brief Compute a 3D acceleration from 6D spatial acceleration
+ * 
+ * \param ddr spatial acc linear part
+ * \param w spatial acc rotational part
+ * \param dr spatial velocity linear part
+**/
+Vector3d computeAccFromSpatial(const Vector3d& ddr, const Vector3d& w, const Vector3d& dr);
+
+
+/**
+* \brief Compute the relative contact points from foot center of a Talos foot.
+* 
+* Order is clockwise, starting from bottom left (looking at a forward pointing foot from above).
+* Expressed in local foot coordinates. 
+**/
+std::vector<Vector3d> contacts_from_footrect_center();
+
+
+/**
+* \brief Compute the wrench at the end effector center expressed in world coordinates.
+
+* Each contact force (at a foot for instance) is represented in MAPI as a set of 
+* 4 forces at the corners of the contact polygone (foot -> rectangle). These forces,
+* as the other quantities, are expressed in world coordinates. From this 4 3D forces, 
+* we can compute the 6D wrench at the center of the origin point of the contacts vectors.
+
+* \param contacts std vector of 3D contact forces
+* \param cforces 12D corner forces vector ordered as [fx1, fy1, fz1, fx2, fy2... fz4], same order as contacts
+* \param wRl rotation matrix, orientation from world frame to foot frame
+**/
+Matrix<double, 6, 1> contact_force_to_wrench(const std::vector<Vector3d>& contacts, 
+                                             const Matrix<double, 12, 1>& cf12);
+
+
+/**
+* \brief Compute the centroidal angular inertia used to compute the angular momentum.
+
+* \param model: pinocchio robot model used
+* \param data: pinocchio robot data used
+* \param q_static: configuration of the robot with free flyer pose at origin (local base frame = world frame)
+* \param b_p_bc: measure of the CoM position relative to the base
+**/
+Matrix3d compute_Iw(pinocchio::Model& model, 
+                    pinocchio::Data& data, 
+                    VectorXd& q_static, 
+                    Vector3d& b_p_bc);
diff --git a/demos/processor_ft_preint.yaml b/demos/processor_ft_preint.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d9b49d70b860fe2e3029a4cc9bd62ffcc2fdc6c8
--- /dev/null
+++ b/demos/processor_ft_preint.yaml
@@ -0,0 +1,12 @@
+type: "ProcessorForceTorquePreint"         # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "PFTPreint"    # This is ignored. The name provided to the SensorFactory prevails
+time_tolerance: 0.0005         # Time tolerance for joining KFs
+unmeasured_perturbation_std: 0.0000000
+sensor_ikin_name: "SenIK"
+sensor_angvel_name: "SenImu"
+keyframe_vote:
+    max_time_span:      100000000000   # seconds
+    max_buff_length:    50000   # motion deltas
+    dist_traveled:      20000.0   # meters
+    angle_turned:       1000   # radians (1 rad approx 57 deg, approx 60 deg)
+    voting_active:      false
\ No newline at end of file
diff --git a/demos/processor_imu_mcapi_demo.yaml b/demos/processor_imu_mcapi_demo.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..38105baa1a5a6b582f67919fc9299d6eb05bfe52
--- /dev/null
+++ b/demos/processor_imu_mcapi_demo.yaml
@@ -0,0 +1,11 @@
+type: "ProcessorImu"         # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "Main imu"             # This is ignored. The name provided to the SensorFactory prevails
+time_tolerance: 0.0005         # Time tolerance for joining KFs
+unmeasured_perturbation_std: 0.000001
+keyframe_vote:
+    max_time_span:      0.29999  # seconds
+    max_buff_length:    100000000000   # motion deltas
+    dist_traveled:      20000.0   # meters
+    angle_turned:       1000   # radians (1 rad approx 57 deg, approx 60 deg)
+    voting_active:      true
+    voting_aux_active:  false
\ No newline at end of file
diff --git a/demos/processor_imu_solo12.yaml b/demos/processor_imu_solo12.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f92b74c06167c96ef4294e00600c7270bd0d20e2
--- /dev/null
+++ b/demos/processor_imu_solo12.yaml
@@ -0,0 +1,10 @@
+keyframe_vote:
+  angle_turned: 1000
+  dist_traveled: 20000.0
+  max_buff_length: 100000000000
+  max_time_span: 0.19999
+  voting_active: true
+name: Main imu
+time_tolerance: 0.0005
+type: ProcessorImu
+unmeasured_perturbation_std: 1.0e-06
diff --git a/demos/processor_inertial_kinematics.yaml b/demos/processor_inertial_kinematics.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..64c0d60f2f1b2e7f1af0b9e570bbcec1473c0207
--- /dev/null
+++ b/demos/processor_inertial_kinematics.yaml
@@ -0,0 +1,5 @@
+type: "ProcessorInertialKinematics"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "PInertialKinematics"        # This is ignored. The name provided to the SensorFactory prevails
+
+sensor_angvel_name: "SenImu" 
+std_bp_drift: 0.01
\ No newline at end of file
diff --git a/demos/processor_odom_3d.yaml b/demos/processor_odom_3d.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..950b77442fd54510ac688dd8b4e1d884d6888d9d
--- /dev/null
+++ b/demos/processor_odom_3d.yaml
@@ -0,0 +1,11 @@
+type: "ProcessorOdom3d"              # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+
+time_tolerance: 0.0005  # seconds
+unmeasured_perturbation_std: 0.0001
+
+keyframe_vote:
+  voting_active:        false
+  max_time_span:          100   # seconds
+  max_buff_length:        10    # motion deltas
+  dist_traveled:          0.5   # meters
+  angle_turned:           0.1   # radians (1 rad approx 57 deg, approx 60 deg)
diff --git a/demos/sensor_ft.yaml b/demos/sensor_ft.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5112ba8e3f474321a74bcb6c1cc01b1b948941db
--- /dev/null
+++ b/demos/sensor_ft.yaml
@@ -0,0 +1,5 @@
+type: "SensorForceTorque"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "SenFT"        # This is ignored. The name provided to the SensorFactory prevails
+mass: 10
+std_f: 0.001 
+std_tau: 0.001
\ No newline at end of file
diff --git a/demos/sensor_imu.yaml b/demos/sensor_imu.yaml
index 24f99895d80a1cc8f3f8dc2d6cc52fbadc25e79b..47a6d19167d559a21de6d0b41f104729181a8bd5 100644
--- a/demos/sensor_imu.yaml
+++ b/demos/sensor_imu.yaml
@@ -1,9 +1,9 @@
-type: "SensorIMU"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
-name: "Main IMU Sensor"        # This is ignored. The name provided to the SensorFactory prevails
+type: "SensorImu"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "Main Imu Sensor"        # This is ignored. The name provided to the SensorFactory prevails
 motion_variances: 
    a_noise:                0.02     # standard deviation of Acceleration noise (same for all the axis) in m/s2
    w_noise:                0.03    # standard deviation of Gyroscope noise (same for all the axis) in rad/sec
    ab_initial_stdev:       0.00     # m/s2    - initial bias 
    wb_initial_stdev:       0.0     # rad/sec - initial bias 
-   ab_rate_stdev:          0.0001       # m/s2/sqrt(s)           
-   wb_rate_stdev:          0.0001    # rad/s/sqrt(s)
\ No newline at end of file
+   ab_rate_stdev:          0.00001       # m/s2/sqrt(s)           
+   wb_rate_stdev:          0.00001    # rad/s/sqrt(s)
\ No newline at end of file
diff --git a/demos/sensor_imu_solo12.yaml b/demos/sensor_imu_solo12.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..68ad34e3b0d14ec0711760551d8875ab005622cc
--- /dev/null
+++ b/demos/sensor_imu_solo12.yaml
@@ -0,0 +1,9 @@
+type: "SensorImu"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "Main Imu Sensor"        # This is ignored. The name provided to the SensorFactory prevails
+motion_variances: 
+   a_noise:                0.05     # standard deviation of Acceleration noise (same for all the axis) in m/s2
+   w_noise:                0.005    # standard deviation of Gyroscope noise (same for all the axis) in rad/sec
+   ab_initial_stdev:       0.00     # m/s2    - initial bias 
+   wb_initial_stdev:       0.0     # rad/sec - initial bias 
+   ab_rate_stdev:          0.0001       # m/s2/sqrt(s)           
+   wb_rate_stdev:          0.0001    # rad/s/sqrt(s)
\ No newline at end of file
diff --git a/demos/sensor_inertial_kinematics.yaml b/demos/sensor_inertial_kinematics.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..62327e3c3e230f32b50d0d595cdcc8ec9a186248
--- /dev/null
+++ b/demos/sensor_inertial_kinematics.yaml
@@ -0,0 +1,5 @@
+type: "SensorInertialKinematics"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "SInertialKinematics"        # This is ignored. The name provided to the SensorFactory prevails
+
+std_pbc: 0.01
+std_vbc: 0.01
\ No newline at end of file
diff --git a/demos/sensor_odom_3d.yaml b/demos/sensor_odom_3d.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8eb2b235011cb3cfe4f35b1f73da6344991af0da
--- /dev/null
+++ b/demos/sensor_odom_3d.yaml
@@ -0,0 +1,7 @@
+type: "SensorOdom3d"              # This must match the KEY used in the FactorySensor. Otherwise it is an error.
+
+k_disp_to_disp:   0.02  # m^2   / m
+k_disp_to_rot:    0.02  # rad^2 / m
+k_rot_to_rot:     0.01  # rad^2 / rad
+min_disp_var:     0.01  # m^2
+min_rot_var:      0.01  # rad^2
diff --git a/demos/solo_mocap_imu.cpp b/demos/solo_mocap_imu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..19f8dfaa700dbcb7876dfd2f3587bb996d68ab1b
--- /dev/null
+++ b/demos/solo_mocap_imu.cpp
@@ -0,0 +1,543 @@
+//--------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--------
+#include <iostream>
+#include <fstream>
+
+// #include <random>
+#include <yaml-cpp/yaml.h>
+#include <Eigen/Dense>
+#include <ctime>
+#include "eigenmvn.h"
+
+#include "pinocchio/parsers/urdf.hpp"
+#include "pinocchio/algorithm/joint-configuration.hpp"
+#include "pinocchio/algorithm/kinematics.hpp"
+#include "pinocchio/algorithm/center-of-mass.hpp"
+#include "pinocchio/algorithm/centroidal.hpp"
+#include "pinocchio/algorithm/rnea.hpp"
+#include "pinocchio/algorithm/crba.hpp"
+#include "pinocchio/algorithm/frames.hpp"
+
+// CNPY
+#include <cnpy.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_pose_3d_with_extrinsics.h>
+#include <core/factor/factor_block_absolute.h>
+#include <core/processor/processor_odom_3d.h>
+#include <core/sensor/sensor_odom_3d.h>
+#include "core/tree_manager/tree_manager_sliding_window_dual_rate.h"
+#include "core/yaml/parser_yaml.h"
+
+#include <core/sensor/sensor_pose.h>
+#include <core/processor/processor_pose.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+#include "imu/factor/factor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+
+
+// UTILS
+#include "mcapi_utils.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using namespace pinocchio;
+
+typedef pinocchio::SE3Tpl<double> SE3;
+typedef pinocchio::ForceTpl<double> Force;
+
+
+int main (int argc, char **argv) {
+    // retrieve parameters from yaml file
+    YAML::Node config = YAML::LoadFile("/home/mfourmy/Documents/Phd_LAAS/wolf/bodydynamics/demos/solo_real_estimation.yaml");
+    double dt = config["dt"].as<double>();
+    double max_t = config["max_t"].as<double>();
+    bool solve_end = config["solve_end"].as<bool>();
+
+    // Ceres setup
+    int max_num_iterations = config["max_num_iterations"].as<int>();
+    
+    // priors
+    double std_prior_p = config["std_prior_p"].as<double>();
+    double std_prior_o = config["std_prior_o"].as<double>();
+    double std_prior_v = config["std_prior_v"].as<double>();
+    double std_abs_bias_acc = config["std_abs_bias_acc"].as<double>();
+    double std_abs_bias_gyro = config["std_abs_bias_gyro"].as<double>();    
+    std::vector<double> bias_imu_prior_vec = config["bias_imu_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector6d> bias_imu_prior(bias_imu_prior_vec.data());
+    std::vector<double> b_p_bi_vec = config["b_p_bi"].as<std::vector<double>>();
+    std::vector<double> b_q_i_vec = config["b_q_i"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> b_p_bi(b_p_bi_vec.data());
+    Eigen::Map<Vector4d> b_qvec_i(b_q_i_vec.data());
+
+    // MOCAP
+    double std_pose_p = config["std_pose_p"].as<double>();
+    double std_pose_o_deg = config["std_pose_o_deg"].as<double>();
+    double std_mocap_extr_p = config["std_mocap_extr_p"].as<double>();
+    double std_mocap_extr_o_deg = config["std_mocap_extr_o_deg"].as<double>();
+    bool unfix_extr_sensor_pose = config["unfix_extr_sensor_pose"].as<bool>();
+    bool time_tolerance_mocap = config["time_tolerance_mocap"].as<double>();
+    double time_shift_mocap = config["time_shift_mocap"].as<double>();
+    
+
+    std::string data_file_path = config["data_file_path"].as<std::string>();
+    std::string out_npz_file_path = config["out_npz_file_path"].as<std::string>();
+
+    // Base to IMU transform
+    Quaterniond b_q_i(b_qvec_i);
+    assert(b_q_i.normalized().isApprox(b_q_i));
+    Quaterniond i_q_b = b_q_i.conjugate();
+    SE3 b_T_i(b_q_i, b_p_bi);
+    SE3 i_T_b = b_T_i.inverse();
+    Matrix3d b_R_i =  b_T_i.rotation();
+    Vector3d i_p_ib = i_T_b.translation();
+    Matrix3d i_R_b =  i_T_b.rotation();
+
+    // initialize some vectors and fill them with dicretized data
+    cnpy::npz_t arr_map = cnpy::npz_load(data_file_path);
+
+    //load it into a new array
+    cnpy::NpyArray t_npyarr = arr_map["t"];
+    cnpy::NpyArray imu_acc_npyarr = arr_map["imu_acc"];
+    // cnpy::NpyArray o_a_oi_npyarr = arr_map["o_a_oi"];
+    cnpy::NpyArray o_q_i_npyarr = arr_map["o_q_i"];
+    cnpy::NpyArray i_omg_oi_npyarr = arr_map["i_omg_oi"];
+    cnpy::NpyArray qa_npyarr = arr_map["qa"];
+    cnpy::NpyArray dqa_npyarr = arr_map["dqa"];
+    cnpy::NpyArray tau_npyarr = arr_map["tau"];
+    cnpy::NpyArray l_forces_npyarr = arr_map["l_forces"];
+    // cnpy::NpyArray w_pose_wm_npyarr = arr_map["w_pose_wm"];
+    cnpy::NpyArray m_v_wm_npyarr = arr_map["m_v_wm"];
+    cnpy::NpyArray w_v_wm_npyarr = arr_map["w_v_wm"];
+    // cnpy::NpyArray o_R_i_npyarr = arr_map["o_R_i"];
+    cnpy::NpyArray w_p_wm_npyarr = arr_map["w_p_wm"];
+    cnpy::NpyArray w_q_m_npyarr = arr_map["w_q_m"];
+    // cnpy::NpyArray contact_npyarr = arr_map["contact"];
+    // cnpy::NpyArray b_p_bl_mocap_npyarr = arr_map["b_p_bl_mocap"];
+
+    // cnpy::NpyArray w_R_m_npyarr = arr_map["w_R_m"];
+    double* t_arr = t_npyarr.data<double>();
+    double* imu_acc_arr = imu_acc_npyarr.data<double>();
+    // double* o_a_oi_arr = o_a_oi_npyarr.data<double>();
+    double* i_omg_oi_arr = i_omg_oi_npyarr.data<double>();
+    double* qa_arr = qa_npyarr.data<double>();
+    double* dqa_arr = dqa_npyarr.data<double>();
+    double* tau_arr = tau_npyarr.data<double>();
+    double* l_forces_arr = l_forces_npyarr.data<double>();
+    // double* w_pose_wm_arr = w_pose_wm_npyarr.data<double>();
+    double* m_v_wm_arr = m_v_wm_npyarr.data<double>();
+    double* w_v_wm_arr = w_v_wm_npyarr.data<double>();
+    double* o_q_i_arr = o_q_i_npyarr.data<double>();
+    // double* o_R_i_arr = o_R_i_npyarr.data<double>();
+    double* w_p_wm_arr = w_p_wm_npyarr.data<double>();
+    double* w_q_m_arr = w_q_m_npyarr.data<double>();
+    
+    // double* contact_arr = contact_npyarr.data<double>();
+    // double* b_p_bl_mocap_arr = b_p_bl_mocap_npyarr.data<double>();
+
+    // double* w_R_m_arr = w_R_m_npyarr.data<double>();
+    unsigned int N = t_npyarr.shape[0];
+    if (max_t > 0){
+        N = N < int(max_t/dt) ? N : int(max_t/dt);
+    }
+
+    /////////////////////////
+    // WOLF enters the scene
+    // SETUP PROBLEM/SENSORS/PROCESSORS
+    std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+    ProblemPtr problem = Problem::create("POV", 3);
+
+    // add a tree manager for sliding window optimization 
+    ParserYaml parser = ParserYaml("demos/tree_manager.yaml", bodydynamics_root_dir);
+    ParamsServer server = ParamsServer(parser.getParams());
+    auto tree_manager = TreeManagerSlidingWindow::create("tree_manager", server);
+    problem->setTreeManager(tree_manager);
+
+    //////////////////////
+    // COMPUTE INITIAL STATE
+    TimeStamp t0(t_arr[0]);
+    Eigen::Map<Vector4d> o_q_i(o_q_i_arr);  // initialize with IMU orientation
+    Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr);  // Hyp: b=i (not the case)
+    Eigen::Map<Vector3d> w_p_wm(w_p_wm_arr);
+    Eigen::Map<Vector4d> w_qvec_wm(w_q_m_arr);
+    w_qvec_wm.normalize();  // not close enough to wolf eps sometimes
+
+    //////////////////////
+
+    // CERES WRAPPER
+    SolverCeresPtr solver = std::make_shared<SolverCeres>(problem);
+    solver->getSolverOptions().max_num_iterations = max_num_iterations;
+
+    //===================================================== INITIALIZATION
+    // SENSOR + PROCESSOR IMU
+    SensorBasePtr sen_imu_base = problem->installSensor("SensorImu", "SenImu", (Vector7d() << 0,0,0, 0,0,0,1).finished(), bodydynamics_root_dir + "/demos/sensor_imu_solo12.yaml");
+    SensorImuPtr sen_imu = std::static_pointer_cast<SensorImu>(sen_imu_base);
+    ProcessorBasePtr proc_imu_base = problem->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/demos/processor_imu_solo12.yaml");
+    ProcessorImuPtr proc_imu = std::static_pointer_cast<ProcessorImu>(proc_imu_base);
+
+    // SENSOR + PROCESSOR POSE (for mocap)
+    // pose sensor and proc (to get extrinsics in the prb)
+    auto intr_sp = std::make_shared<ParamsSensorPose>();
+    intr_sp->std_p = std_pose_p;
+    intr_sp->std_o = toRad(std_pose_o_deg);
+    Vector7d extr_pose; extr_pose << i_p_ib, i_q_b.coeffs();
+    auto sensor_pose = problem->installSensor("SensorPose", "pose", extr_pose, intr_sp);
+    auto params_proc = std::make_shared<ParamsProcessorPose>();
+    params_proc->time_tolerance = time_tolerance_mocap;
+    auto proc_pose = problem->installProcessor("ProcessorPose", "pose", sensor_pose, params_proc);
+    // somehow by default the sensor extrinsics is fixed
+    Matrix3d cov_sp_p = pow(std_mocap_extr_p, 2)*Matrix3d::Identity();
+    Matrix3d cov_sp_o = pow(std_mocap_extr_o_deg/60, 2)*Matrix3d::Identity();
+    sensor_pose->addPriorParameter('P', extr_pose.head<3>(), cov_sp_p);
+    sensor_pose->addPriorParameter('O', extr_pose.tail<4>(), cov_sp_o);
+    if (unfix_extr_sensor_pose){
+        sensor_pose->unfixExtrinsics();
+    }
+    else{
+        sensor_pose->fixExtrinsics();
+    }
+
+    Matrix3d w_R_m_init = q2R(w_qvec_wm);
+    Vector3d w_p_wi_init = w_p_wm + w_R_m_init*b_p_bi; 
+
+    VectorComposite x_origin("POV", {w_p_wi_init, w_qvec_wm, Vector3d::Zero()});
+    VectorComposite std_origin("POV", {std_prior_p*Vector3d::Ones(), std_prior_o*Vector3d::Ones(), std_prior_v*Vector3d::Ones()});
+    // FrameBasePtr KF1 = problem->setPriorFactor(x_origin, std_origin, t0, 0.0005);
+    FrameBasePtr KF1 = problem->setPriorInitialGuess(x_origin, t0);  // if mocap used
+    
+    proc_imu->setOrigin(KF1);
+
+    //////////////////////////////////////////
+    // INITIAL BIAS FACTORS
+    // Add absolute factor on Imu biases to simulate a fix()
+    Vector6d std_abs_imu; std_abs_imu << std_abs_bias_acc*Vector3d::Ones(), std_abs_bias_gyro*Vector3d::Ones();
+    Matrix6d Q_bi = std_abs_imu.array().square().matrix().asDiagonal();    
+    CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1, "bi0", t0);
+    FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", bias_imu_prior, Q_bi);
+    FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1->getCaptureOf(sen_imu)->getSensorIntrinsic(), nullptr, false);   
+    KF1->getCaptureOf(sen_imu)->getStateBlock('I')->setState(bias_imu_prior);
+    sen_imu->getStateBlock('I')->setState(bias_imu_prior);
+    // sen_imu->fixIntrinsics();
+
+
+    //////////////////////////////////////////
+    // Vectors to store estimates at the time of data processing 
+    // fbk -> feedback: to check if the estimation is stable enough for feedback control
+    std::vector<Vector3d> p_ob_fbk_v; 
+    std::vector<Vector4d> q_ob_fbk_v; 
+    std::vector<Vector3d> v_ob_fbk_v; 
+    // Allocate on the heap to prevent stack overflow for large datasets
+    double* o_p_ob_fbk_carr = new double[3*N];
+    double* o_q_b_fbk_carr = new double[4*N];
+    double* o_v_ob_fbk_carr = new double[3*N];
+    double* imu_bias_fbk_carr = new double[6*N];
+    double* extr_mocap_fbk_carr = new double[7*N];
+
+    // covariances computed on the fly
+    std::vector<Vector6d> Qbi_fbk_v; Qbi_fbk_v.push_back(Vector6d::Zero());
+
+    //
+    std::vector<Vector3d> i_omg_oi_v; 
+    //////////////////////////////////////////
+
+    unsigned int nb_kf = 1;
+    for (unsigned int i=0; i < N; i++){
+        TimeStamp ts(t_arr[i]);
+
+        ////////////////////////////////////
+        ////////////////////////////////////
+        // Get measurements
+        // retrieve traj data
+        Eigen::Map<Vector3d> imu_acc(imu_acc_arr+3*i);
+        Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr+3*i);  // Hyp: b=i (not the case)
+        Eigen::Map<Vector3d> w_p_wm(w_p_wm_arr+3*i);
+        Eigen::Map<Vector4d> w_qvec_wm(w_q_m_arr+4*i);
+        w_qvec_wm.normalize();  // not close enough to wolf eps sometimes
+
+        // store i_omg_oi for later computation of o_v_ob from o_v_oi
+        i_omg_oi_v.push_back(i_omg_oi);
+
+        Vector6d imu_bias = sen_imu->getStateBlockDynamic('I', t_arr[i])->getState();
+        Vector3d i_omg_oi_unbiased = (i_omg_oi - imu_bias.tail<3>());
+
+
+        ////////////////////////////////////
+        // IMU
+        Vector6d acc_gyr_meas; acc_gyr_meas << imu_acc, i_omg_oi;
+        Matrix6d acc_gyr_cov = sen_imu->getNoiseCov();
+
+        CaptureImuPtr CImu = std::make_shared<CaptureImu>(ts, sen_imu, acc_gyr_meas, acc_gyr_cov);
+        CImu->process();
+ 
+        // Vector7d pose_meas; pose_meas << w_p_wm, w_qvec_wm;
+        Vector7d pose_meas; pose_meas << w_p_wm, w_qvec_wm;
+        CapturePosePtr CP = std::make_shared<CapturePose>(ts+time_shift_mocap, sensor_pose, pose_meas, sensor_pose->getNoiseCov());
+        CP->process();
+
+        // solve every new KF
+        if (problem->getTrajectory()->getFrameMap().size() > nb_kf ){
+            std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+            std::cout << ts << "  ";
+            std::cout << report << std::endl;
+
+            // recover covariances at this point
+            auto kf_last = problem->getTrajectory()->getFrameMap().rbegin()->second;
+            
+            CaptureBasePtr cap_imu = kf_last->getCaptureOf(sen_imu); 
+
+            Eigen::MatrixXd Qbi_fbk = Eigen::Matrix6d::Identity();
+            solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+            problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi_fbk);
+            Vector6d Qbi_fbk_diag = Qbi_fbk.diagonal(); 
+
+
+            Qbi_fbk_v.push_back(Qbi_fbk_diag);
+
+            // std::cout << "Qbi_fbk: \n" << Qbi_fbk.topLeftCorner<3,3>() << std::endl;
+            // std::cout << "Qbi_fbk: \n" << Qbi_fbk << std::endl;
+
+
+            nb_kf++;
+        }
+
+        // Store current state estimation
+        VectorComposite state_fbk = problem->getState();
+        // VectorComposite state_fbk = problem->getState(ts);
+        Vector3d o_p_oi = state_fbk['P'];
+        Vector4d o_qvec_i = state_fbk['O'];
+        Vector3d o_v_oi = state_fbk['V'];
+
+        // IMU to base frame 
+        Matrix3d o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        // o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        Matrix3d o_R_b = o_R_i * i_R_b;
+        Vector4d o_qvec_b = Quaterniond(o_R_b).coeffs();
+        Vector3d o_p_ob = o_p_oi + o_R_i * i_p_ib;
+        Vector3d o_v_ob = o_v_oi + o_R_i * i_omg_oi_unbiased.cross(o_R_i*i_R_b*b_p_bi);
+        // Vector3d o_p_ob = o_p_oi;
+        // Vector3d o_v_ob = o_v_oi;
+
+        imu_bias = sen_imu->getIntrinsic()->getState();
+        // imu_bias = sen_imu->getIntrinsic(ts)->getState();
+        Vector7d extr_mocap_est; extr_mocap_est << sensor_pose->getP()->getState(), sensor_pose->getO()->getState();
+
+        mempcpy(o_p_ob_fbk_carr+3*i, o_p_ob.data(),             3*sizeof(double));
+        mempcpy(o_q_b_fbk_carr +4*i, o_qvec_b.data(),           4*sizeof(double));
+        mempcpy(o_v_ob_fbk_carr+3*i, o_v_ob.data(),             3*sizeof(double));
+        mempcpy(imu_bias_fbk_carr+6*i, imu_bias.data(),         6*sizeof(double));
+        mempcpy(extr_mocap_fbk_carr+7*i, extr_mocap_est.data(), 7*sizeof(double));
+
+        p_ob_fbk_v.push_back(o_p_ob);
+        q_ob_fbk_v.push_back(o_qvec_b);
+        v_ob_fbk_v.push_back(o_v_ob);
+    }
+    
+
+
+    ///////////////////////////////////////////
+    //////////////// SOLVING //////////////////
+    ///////////////////////////////////////////
+    if (solve_end){
+        std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        problem->print(4,true,true,true);
+        std::cout << report << std::endl;
+    }
+
+    double* o_p_ob_carr = new double[3*N];
+    double* o_q_b_carr = new double[4*N];
+    double* imu_bias_carr = new double[6*N];
+    double* o_v_ob_carr = new double[6*N];
+    for (unsigned int i=0; i < N; i++) { 
+        VectorComposite state_est = problem->getState(t_arr[i]);
+        Vector3d i_omg_oi = i_omg_oi_v[i];
+        Vector3d o_p_oi = state_est['P'];
+        Vector4d o_qvec_i = state_est['O'];
+        Vector3d o_v_oi = state_est['V'];
+
+        auto sb = sen_imu->getStateBlockDynamic('I', t_arr[i]);
+        Vector6d imu_bias = sb->getState();
+        // std::cout << t_arr[i] << ", imu_bias: " << imu_bias.transpose() << std::endl;
+        Vector3d i_omg_oi_unbiased = (i_omg_oi - imu_bias.tail<3>());
+
+        Matrix3d o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        Matrix3d o_R_b = o_R_i * i_R_b;
+        Vector4d o_qvec_b = Quaterniond(o_R_b).coeffs();
+        Vector3d o_p_ob = o_p_oi + o_R_i * i_p_ib;
+        Vector3d o_v_ob = o_v_oi + o_R_i * i_omg_oi_unbiased.cross(i_R_b*b_p_bi);
+        // Vector3d o_p_ob = o_p_oi;
+        // Vector3d o_v_ob = o_v_oi;
+
+        mempcpy(o_p_ob_carr+3*i, o_p_ob.data(),     3*sizeof(double));
+        mempcpy(o_q_b_carr +4*i, o_qvec_b.data(),   4*sizeof(double));
+        mempcpy(o_v_ob_carr+3*i, o_v_ob.data(),     3*sizeof(double));
+        mempcpy(imu_bias_carr+6*i, imu_bias.data(), 6*sizeof(double));
+    }
+
+
+    // Compute Covariances
+    unsigned int Nkf = problem->getTrajectory()->getFrameMap().size();
+    double* tkf_carr  = new double[1*Nkf];
+    double* Qp_carr  = new double[3*Nkf];
+    double* Qo_carr  = new double[3*Nkf];
+    double* Qv_carr  = new double[3*Nkf];
+    double* Qbi_carr = new double[6*Nkf];
+    double* Qm_carr  = new double[6*Nkf];
+    // feedback covariances
+    double* Qbi_fbk_carr = new double[6*Nkf];
+    
+    // factor errors
+    double* fac_imu_err_carr = new double[9*Nkf];
+    double* fac_pose_err_carr  = new double[6*Nkf];
+    int i = 0;
+    for (auto& elt: problem->getTrajectory()->getFrameMap()){
+        std::cout << "Traj " << i << "/" << problem->getTrajectory()->getFrameMap().size() << std::endl;
+        tkf_carr[i] = elt.first.get();
+        auto kf = elt.second;
+        VectorComposite kf_state = kf->getState();
+        
+        // compute covariances of KF and capture stateblocks
+        Eigen::MatrixXd Qp =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qo =  Eigen::Matrix3d::Identity();  // global or local?
+        Eigen::MatrixXd Qv =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbi = Eigen::Matrix6d::Identity();
+        Eigen::MatrixXd Qmp =  Eigen::Matrix3d::Identity();  // extr mocap
+        Eigen::MatrixXd Qmo =  Eigen::Matrix3d::Identity();  // extr mocap
+        
+        solver->computeCovariances(SolverManager::CovarianceBlocksToBeComputed::ALL);  // should not be computed every time !! see below
+        problem->getCovarianceBlock(kf->getStateBlock('P'), Qp);
+        problem->getCovarianceBlock(kf->getStateBlock('O'), Qo);
+        problem->getCovarianceBlock(kf->getStateBlock('V'), Qv);
+
+        CaptureBasePtr cap_imu = kf->getCaptureOf(sen_imu); 
+
+        std::vector<StateBlockPtr> extr_sb_vec = {sensor_pose->getP(), sensor_pose->getO()};
+        solver->computeCovariances(extr_sb_vec);  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(sensor_pose->getP(), Qmp);
+        problem->getCovarianceBlock(sensor_pose->getO(), Qmo);
+        // std::cout << "Qbp\n" << Qbp << std::endl;
+        solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi);
+
+        // diagonal components
+        Vector3d Qp_diag = Qp.diagonal(); 
+        Vector3d Qo_diag = Qo.diagonal(); 
+        Vector3d Qv_diag = Qv.diagonal(); 
+        Vector6d Qbi_diag = Qbi.diagonal(); 
+        Vector6d Qm_diag; Qm_diag << Qmp.diagonal(), Qmo.diagonal(); 
+        
+        memcpy(Qp_carr  + 3*i, Qp_diag.data(),  3*sizeof(double)); 
+        memcpy(Qo_carr  + 3*i, Qo_diag.data(),  3*sizeof(double)); 
+        memcpy(Qv_carr  + 3*i, Qv_diag.data(),  3*sizeof(double)); 
+        memcpy(Qbi_carr + 6*i, Qbi_diag.data(), 6*sizeof(double)); 
+        memcpy(Qm_carr  + 6*i, Qm_diag.data(),  6*sizeof(double)); 
+
+        // Recover feedback covariances
+        memcpy(Qbi_fbk_carr + 6*i, Qbi_fbk_v[i].data(), 6*sizeof(double)); 
+        
+
+        // Factor errors
+        // std::cout << "Factors " << kf->getTimeStamp().get() << std::endl;
+        FactorBasePtrList fac_lst; 
+        kf->getFactorList(fac_lst);
+        Vector9d imu_err = Vector9d::Zero();
+        Vector6d pose_err = Vector6d::Zero();
+        for (auto fac: fac_lst){
+            if (fac->getProcessor() == proc_imu){
+                auto f = std::dynamic_pointer_cast<FactorImu>(fac);
+                // Matrix6d R_bias_drift = f->getMeasurementSquareRootInformationUpper().bottomRightCorner<6,6>();
+                // Matrix6d R_bias_drift = f->getFeature()->getMeasurementSquareRootInformationUpper().bottomRightCorner<6,6>();
+                // std::cout << R_bias_drift << std::endl;
+                // MatrixXd R_bias_drift = f->getFeature()->getMeasurementSquareRootInformationUpper();
+                // MatrixXd Cov_bias_drift = f->getFeature()->getMeasurementCovariance();
+                // std::cout << "R_bias_drift" << std::endl;
+                // std::cout << R_bias_drift << std::endl;
+                // std::cout << "Cov_bias_drift" << std::endl;
+                // std::cout << Cov_bias_drift << std::endl;
+
+                if (f){
+                    imu_err = f->error();
+                }
+            }
+            if (fac->getProcessor() == proc_pose){
+                auto f = std::dynamic_pointer_cast<FactorPose3dWithExtrinsics>(fac);
+                if (f){
+                    pose_err = f->error();
+                }
+            }
+        }
+        memcpy(fac_imu_err_carr + 9*i, imu_err.data(), 9*sizeof(double)); 
+        memcpy(fac_pose_err_carr + 6*i, pose_err.data(),  6*sizeof(double)); 
+
+
+        i++;
+    }
+
+    // Save trajectories in npz file
+    // save ground truth    
+    cnpy::npz_save(out_npz_file_path, "t", t_arr, {N}, "w");             // "w" overwrites any file with same name
+    cnpy::npz_save(out_npz_file_path, "w_p_wm", w_p_wm_arr, {N,3}, "a"); // "a" appends to the file we created above
+    cnpy::npz_save(out_npz_file_path, "w_q_m",  w_q_m_arr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "w_v_wm", w_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "m_v_wm", m_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_i",  o_q_i_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "qa",     qa_arr, {N,12}, "a");
+
+    // save estimates
+    cnpy::npz_save(out_npz_file_path, "o_p_ob_fbk", o_p_ob_fbk_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_b_fbk",  o_q_b_fbk_carr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_ob_fbk", o_v_ob_fbk_carr, {N,3}, "a");
+    
+    cnpy::npz_save(out_npz_file_path, "o_p_ob", o_p_ob_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_b",  o_q_b_carr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_ob", o_v_ob_carr, {N,3}, "a");
+    
+    // and biases/extrinsics
+    cnpy::npz_save(out_npz_file_path, "imu_bias_fbk", imu_bias_fbk_carr, {N,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "imu_bias", imu_bias_carr, {N,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "extr_mocap_fbk", extr_mocap_fbk_carr, {N,7}, "a");
+
+    // covariances
+    cnpy::npz_save(out_npz_file_path, "tkf", tkf_carr, {Nkf}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qp", Qp_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qo", Qo_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qv", Qv_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qbi", Qbi_carr, {Nkf,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qbi_fbk", Qbi_fbk_carr, {Nkf,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qm", Qm_carr,   {Nkf,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "fac_imu_err", fac_imu_err_carr, {Nkf,9}, "a");
+    cnpy::npz_save(out_npz_file_path, "fac_pose_err", fac_pose_err_carr, {Nkf,6}, "a");
+
+}
diff --git a/demos/solo_real_estimation.yaml b/demos/solo_real_estimation.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1867ec7a227dd3a3d623fc6db430a9cbb36d823b
--- /dev/null
+++ b/demos/solo_real_estimation.yaml
@@ -0,0 +1,164 @@
+# trajectory handling
+dt: 0.001
+min_t: -1.0  # -1 means from the beginning of the trajectory
+max_t: -1.0  # -1 means until the end of the trajectory
+# max_t: 4.8  # -1 means until the end of the trajectory
+# solve_every_sec: -1   # < 0 strict --> no solve
+# solve_end: False
+solve_every_sec: 0.05   # < 0 strict --> no solve
+solve_end: True
+
+# Ceres setup
+max_num_iterations: 1000
+
+# estimator sensor noises
+std_pbc_est: 0.01
+std_vbc_est: 0.01
+std_f_est:   1
+std_tau_est: 1000
+std_odom3d_est:   0.01
+# std_odom3d_est:  10000000
+
+# some controls
+fz_thresh: 1 # slow walk
+# fz_thresh: 3 # slow walk
+# fz_thresh: 3  # slow traj
+# fz_thresh: 4  # stamping
+# fz_thresh: 5.5  # walking
+
+# priors
+std_prior_p: 0.00001
+std_prior_o: 1
+std_prior_v: 1
+
+bias_pbc_prior: [0,0,0]
+# bias_pbc_prior: [0.0139338 , 0.02786759, 0.01857839]
+std_abs_bias_pbc: 0.1   # noprior  
+# std_abs_bias_pbc: 0.00001  # almost fixed 
+# std_bp_drift: 1000     # All the drift you want
+std_bp_drift: 0.005     # small drift
+
+std_pose_p: 0.001
+std_pose_o_deg: 1
+std_mocap_extr_o_deg: 0.1
+std_mocap_extr_p: 10
+unfix_extr_sensor_pose: true
+time_shift_mocap: 0
+time_tolerance_mocap: 0.001
+
+bias_imu_prior: [0,0,0, 0,0,0]
+# bias_imu_prior: [0,0,-0.011,-0.000079251,  0.00439540765, -0.0002120267]
+# bias_imu_prior: [0,0,-0.11, 0,0,0]
+# bias_imu_prior: [0,0,0, 0.1,  0.2, 0.3]
+std_abs_bias_acc:  100
+std_abs_bias_gyro: 100
+
+robot_urdf: "/opt/openrobots/share/example-robot-data/robots/solo_description/robots/solo12.urdf"
+dimc: 3
+mass_offset: 0.0
+# mass_offset: -0.00405
+# mass_offset: -0.005
+nb_feet: 4
+
+l_p_lg: [0.0, 0, 0]
+# l_p_lg: [0, 0, -0.016]
+# l_p_lg: [0, 0, -0.031]  # for sin traj, point to which position estimation on z starts to be less good 
+
+# base to IMU transformation
+# b_p_bi: [0.0, 0.0, 0.0]
+# b_p_bi: [-0.2, 0.0, 0.0]
+b_p_bi: [0.1163, 0.0, 0.02]
+b_q_i: [0.0, 0.0, 0.0, 1.0]
+# b_q_i: [0.00000592745,  -0.03255761280,  -0.00025745595,  0.706732091]
+
+artificial_bias_p: [0.0, 0.0, 0.0]
+# artificial_bias_p: [0.03, 0.06, 0.04]
+# artificial_bias_p: [3, 6, 4]
+
+
+
+
+
+# Data files
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/solo12_mpi_stamping_2020-09-29.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/data_2020_10_08_09_50_Walking_Novicon.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/data_2020_10_08_10_04_StandingStill_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/data_2020_10_08_10_04_StandingStill_shortened_format.npz"
+
+# 18:58 Fixe 4 stance phase (20s)
+# 19:00 Rotation 4 stance phase (30s)
+# 19:02 Mouvement avant arrière, rotation, rotation, mvt bas haut, roll (30s)
+# 19:03 Replay sin wave
+# 19:05 Replay stamping
+# 19:06 Marche 0.32 (30s)
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_18_58_format.npz"  # OK
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_18_58_format_shortened.npz"  # OK
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_19_00_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_19_02_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_19_03_format.npz"  # OK
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_19_05_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_09_10_20_soir/data_2020_10_09_19_06_format.npz"
+
+
+
+
+# data_2020_10_15_14_34 standing still
+# data_2020_10_15_14_36 sin traj
+# data_2020_10_15_14_38 solo stamping
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened.npz"
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CST.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CST_0gyr.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CST_Ogyr_Gacc_0dqa.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CST_Ogyr_Gacc_Filtereddqa.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CSTmean_Filtereddqa.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CSTacc_0gyr_Filtereddqa.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CST_0gyr_Gacc.npz"
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_shortened_CSTqa.npz"
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_VERY_shortened.npz"
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_36_format.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_38_format.npz"
+
+
+# data_2020_10_15_18_21: ...
+# data_2020_10_15_18_23: walk
+# data_2020_10_15_18_24: walk
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Log_15_10_2020_part2/data_2020_10_15_18_21.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Log_15_10_2020_part2/data_2020_10_15_18_23.npz"
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Log_15_10_2020_part2/data_2020_10_15_18_24.npz"
+
+
+# same but with precomputed forces
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_34_format_forces.npz"  # standing still
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_36_format_forces.npz"  # sinusoid
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_15_10_2020/data_2020_10_15_14_38_format_forces.npz"  # stamping
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Log_15_10_2020_part2/data_2020_10_15_18_23_format_forces.npz"  # fast walk
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/data_2020_10_31_20_12_format_forces.npz"  # 0.48s walk
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_05_10_2020_18h/data_2020_11_05_18_18_format_forces.npz"  # standing still+walk 0.48 FAIL
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments/Logs_05_10_2020_18h/data_2020_11_05_18_20_format_forces.npz"  # standing still+walk 0.48
+
+
+# POINT FEET
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Point_feet_27_11_20/data_2020_11_27_14_46_format.npz"  # standing still+walk 0.48
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Experiments_Replay_30_11_2020_bis/data_2020_11_30_17_17_format.npz"  # stamping
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Experiments_Replay_30_11_2020_bis/data_2020_11_30_17_18_format.npz"  # sin
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Experiments_Replay_30_11_2020_bis/data_2020_11_30_17_22_format.npz"  # walking
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/SinStamping_Corrected_09_12_2020/data_2020_12_09_17_54_format.npz"  # sin
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/SinStamping_Corrected_09_12_2020/data_2020_12_09_17_56_format.npz"  # stamping
+
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Experiments_Walk_17_12_2020/data_2020_12_17_14_25_format.npz"  # point feet walk with corrected kinematics
+# data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Experiments_Walk_17_12_2020/data_2020_12_17_14_29_format.npz"  # point feet walk with corrected kinematics
+
+
+data_file_path: "/home/mfourmy/Documents/Phd_LAAS/solo-estimation/data/Mesures_Contact/data_2021_01_19_17_11_format.npz"  # Contact experiment with contact status and mocap leg kin
+
+
+out_npz_file_path: "/home/mfourmy/Documents/Phd_LAAS/data/quadruped_experiments_results/out.npz"
\ No newline at end of file
diff --git a/demos/solo_real_pov_estimation.cpp b/demos/solo_real_pov_estimation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fbae4d51a0f97f30aa05c5329ca754986ce72cb2
--- /dev/null
+++ b/demos/solo_real_pov_estimation.cpp
@@ -0,0 +1,702 @@
+//--------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--------
+#include <iostream>
+#include <fstream>
+
+// #include <random>
+#include <yaml-cpp/yaml.h>
+#include <Eigen/Dense>
+#include <ctime>
+#include "eigenmvn.h"
+
+#include "pinocchio/parsers/urdf.hpp"
+#include "pinocchio/algorithm/joint-configuration.hpp"
+#include "pinocchio/algorithm/kinematics.hpp"
+#include "pinocchio/algorithm/center-of-mass.hpp"
+#include "pinocchio/algorithm/centroidal.hpp"
+#include "pinocchio/algorithm/rnea.hpp"
+#include "pinocchio/algorithm/crba.hpp"
+#include "pinocchio/algorithm/frames.hpp"
+
+// CNPY
+#include <cnpy.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_pose_3d_with_extrinsics.h>
+#include <core/factor/factor_block_absolute.h>
+#include <core/processor/processor_odom_3d.h>
+#include <core/sensor/sensor_odom_3d.h>
+#include "core/tree_manager/tree_manager_sliding_window_dual_rate.h"
+#include "core/yaml/parser_yaml.h"
+
+#include <core/sensor/sensor_pose.h>
+#include <core/processor/processor_pose.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+#include "imu/factor/factor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/capture/capture_leg_odom.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+#include "bodydynamics/factor/factor_inertial_kinematics.h"
+#include "bodydynamics/factor/factor_force_torque_preint.h"
+
+#include "bodydynamics/capture/capture_point_feet_nomove.h"
+#include "bodydynamics/sensor/sensor_point_feet_nomove.h"
+#include "bodydynamics/processor/processor_point_feet_nomove.h"
+
+
+
+// UTILS
+#include "mcapi_utils.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using namespace pinocchio;
+
+typedef pinocchio::SE3Tpl<double> SE3;
+typedef pinocchio::ForceTpl<double> Force;
+
+
+int main (int argc, char **argv) {
+    // retrieve parameters from yaml file
+    YAML::Node config = YAML::LoadFile("/home/mfourmy/Documents/Phd_LAAS/wolf/bodydynamics/demos/solo_real_estimation.yaml");
+    std::string robot_urdf = config["robot_urdf"].as<std::string>();
+    int dimc = config["dimc"].as<int>();
+    int nb_feet = config["nb_feet"].as<int>();
+    double dt = config["dt"].as<double>();
+    double min_t = config["min_t"].as<double>();
+    double max_t = config["max_t"].as<double>();
+    double solve_every_sec = config["solve_every_sec"].as<double>();
+    bool solve_end = config["solve_end"].as<bool>();
+
+    // Ceres setup
+    int max_num_iterations = config["max_num_iterations"].as<int>();
+    
+    // estimator sensor noises
+    double std_pbc_est = config["std_pbc_est"].as<double>();
+    double std_vbc_est = config["std_vbc_est"].as<double>();
+    double std_f_est = config["std_f_est"].as<double>();
+    double std_tau_est = config["std_tau_est"].as<double>();
+    double std_odom3d_est = config["std_odom3d_est"].as<double>();
+    
+    // priors
+    double std_prior_p = config["std_prior_p"].as<double>();
+    double std_prior_o = config["std_prior_o"].as<double>();
+    double std_prior_v = config["std_prior_v"].as<double>();
+    double std_abs_bias_pbc = config["std_abs_bias_pbc"].as<double>();
+    double std_abs_bias_acc = config["std_abs_bias_acc"].as<double>();
+    double std_abs_bias_gyro = config["std_abs_bias_gyro"].as<double>();    
+    double std_bp_drift = config["std_bp_drift"].as<double>();
+    std::vector<double> bias_imu_prior_vec = config["bias_imu_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector6d> bias_imu_prior(bias_imu_prior_vec.data());
+    std::vector<double> b_p_bi_vec = config["b_p_bi"].as<std::vector<double>>();
+    std::vector<double> b_q_i_vec = config["b_q_i"].as<std::vector<double>>();
+    std::vector<double> l_p_lg_vec = config["l_p_lg"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> b_p_bi(b_p_bi_vec.data());
+    Eigen::Map<Vector4d> b_qvec_i(b_q_i_vec.data());
+    Eigen::Map<Vector3d> l_p_lg(l_p_lg_vec.data());
+    double fz_thresh = config["fz_thresh"].as<double>();
+
+    // MOCAP
+    double std_pose_p = config["std_pose_p"].as<double>();
+    double std_pose_o_deg = config["std_pose_o_deg"].as<double>();
+    double std_mocap_extr_p = config["std_mocap_extr_p"].as<double>();
+    double std_mocap_extr_o_deg = config["std_mocap_extr_o_deg"].as<double>();
+    bool unfix_extr_sensor_pose = config["unfix_extr_sensor_pose"].as<bool>();
+    bool time_tolerance_mocap = config["time_tolerance_mocap"].as<double>();
+    double time_shift_mocap = config["time_shift_mocap"].as<double>();
+    
+
+    std::string data_file_path = config["data_file_path"].as<std::string>();
+    std::string out_npz_file_path = config["out_npz_file_path"].as<std::string>();
+
+    std::vector<std::string> ee_names = {"FL_ANKLE", "FR_ANKLE", "HL_ANKLE", "HR_ANKLE"};
+    unsigned int nbc = ee_names.size();
+    // Base to IMU transform
+    Quaterniond b_q_i(b_qvec_i);
+    assert(b_q_i.normalized().isApprox(b_q_i));
+    Quaterniond i_q_b = b_q_i.conjugate();
+    SE3 b_T_i(b_q_i, b_p_bi);
+    SE3 i_T_b = b_T_i.inverse();
+    Matrix3d b_R_i =  b_T_i.rotation();
+    Vector3d i_p_ib = i_T_b.translation();
+    Matrix3d i_R_b =  i_T_b.rotation();
+
+    // initialize some vectors and fill them with dicretized data
+    cnpy::npz_t arr_map = cnpy::npz_load(data_file_path);
+
+    //load it into a new array
+    cnpy::NpyArray t_npyarr = arr_map["t"];
+    cnpy::NpyArray imu_acc_npyarr = arr_map["imu_acc"];
+    // cnpy::NpyArray o_a_oi_npyarr = arr_map["o_a_oi"];
+    cnpy::NpyArray o_q_i_npyarr = arr_map["o_q_i"];
+    cnpy::NpyArray i_omg_oi_npyarr = arr_map["i_omg_oi"];
+    cnpy::NpyArray qa_npyarr = arr_map["qa"];
+    cnpy::NpyArray dqa_npyarr = arr_map["dqa"];
+    cnpy::NpyArray tau_npyarr = arr_map["tau"];
+    cnpy::NpyArray l_forces_npyarr = arr_map["l_forces"];
+    // cnpy::NpyArray w_pose_wm_npyarr = arr_map["w_pose_wm"];
+    cnpy::NpyArray m_v_wm_npyarr = arr_map["m_v_wm"];
+    cnpy::NpyArray w_v_wm_npyarr = arr_map["w_v_wm"];
+    // cnpy::NpyArray o_R_i_npyarr = arr_map["o_R_i"];
+    cnpy::NpyArray w_p_wm_npyarr = arr_map["w_p_wm"];
+    cnpy::NpyArray w_q_m_npyarr = arr_map["w_q_m"];
+    // cnpy::NpyArray contact_npyarr = arr_map["contact"];
+    // cnpy::NpyArray b_p_bl_mocap_npyarr = arr_map["b_p_bl_mocap"];
+
+    // cnpy::NpyArray w_R_m_npyarr = arr_map["w_R_m"];
+    double* t_arr = t_npyarr.data<double>();
+    double* imu_acc_arr = imu_acc_npyarr.data<double>();
+    // double* o_a_oi_arr = o_a_oi_npyarr.data<double>();
+    double* i_omg_oi_arr = i_omg_oi_npyarr.data<double>();
+    double* qa_arr = qa_npyarr.data<double>();
+    double* dqa_arr = dqa_npyarr.data<double>();
+    double* tau_arr = tau_npyarr.data<double>();
+    double* l_forces_arr = l_forces_npyarr.data<double>();
+    // double* w_pose_wm_arr = w_pose_wm_npyarr.data<double>();
+    double* m_v_wm_arr = m_v_wm_npyarr.data<double>();
+    double* w_v_wm_arr = w_v_wm_npyarr.data<double>();
+    double* o_q_i_arr = o_q_i_npyarr.data<double>();
+    // double* o_R_i_arr = o_R_i_npyarr.data<double>();
+    double* w_p_wm_arr = w_p_wm_npyarr.data<double>();
+    double* w_q_m_arr = w_q_m_npyarr.data<double>();
+    
+    // double* contact_arr = contact_npyarr.data<double>();
+    // double* b_p_bl_mocap_arr = b_p_bl_mocap_npyarr.data<double>();
+
+    // double* w_R_m_arr = w_R_m_npyarr.data<double>();
+    unsigned int N = t_npyarr.shape[0];
+    if (max_t > 0){
+        N = N < int(max_t/dt) ? N : int(max_t/dt);
+    }
+
+    // Creating measurements from simulated trajectory
+    // Load the urdf model
+    Model model;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model);
+    std::cout << "model name: " << model.name << std::endl;
+    Data data(model);
+
+    // Recover end effector frame ids
+    std::vector<int> ee_ids;
+    std::cout << "Frame ids" << std::endl;
+    for (auto ee_name: ee_names){
+        ee_ids.push_back(model.getFrameId(ee_name));
+        std::cout << ee_name << std::endl;
+    }
+
+    /////////////////////////
+    // WOLF enters the scene
+    // SETUP PROBLEM/SENSORS/PROCESSORS
+    std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+    ProblemPtr problem = Problem::create("POV", 3);
+
+    // add a tree manager for sliding window optimization 
+    ParserYaml parser = ParserYaml("demos/tree_manager.yaml", bodydynamics_root_dir);
+    ParamsServer server = ParamsServer(parser.getParams());
+    auto tree_manager = TreeManagerSlidingWindow::create("tree_manager", server);
+    problem->setTreeManager(tree_manager);
+
+    //////////////////////
+    // COMPUTE INITIAL STATE
+    TimeStamp t0(t_arr[0]);
+    Eigen::Map<Matrix<double,12, 1>> qa(qa_arr);
+    Eigen::Map<Matrix<double,12, 1>> dqa(dqa_arr);
+    Eigen::Map<Vector4d> o_q_i(o_q_i_arr);  // initialize with IMU orientation
+    Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr);  // Hyp: b=i (not the case)
+    Eigen::Map<Vector3d> w_p_wm(w_p_wm_arr);
+    Eigen::Map<Vector4d> w_qvec_wm(w_q_m_arr);
+    w_qvec_wm.normalize();  // not close enough to wolf eps sometimes
+
+    // initial state
+    VectorXd q(19); q << 0,0,0, o_q_i, qa; // TODO: put current base orientation estimate? YES
+    VectorXd dq(18); dq << 0,0,0, i_omg_oi, dqa; // TODO: put current base velocity estimate? YES
+
+    //////////////////////
+
+    // CERES WRAPPER
+    SolverCeresPtr solver = std::make_shared<SolverCeres>(problem);
+    solver->getSolverOptions().max_num_iterations = max_num_iterations;
+
+    //===================================================== INITIALIZATION
+    // SENSOR + PROCESSOR IMU
+    SensorBasePtr sen_imu_base = problem->installSensor("SensorImu", "SenImu", (Vector7d() << 0,0,0, 0,0,0,1).finished(), bodydynamics_root_dir + "/demos/sensor_imu_solo12.yaml");
+    SensorImuPtr sen_imu = std::static_pointer_cast<SensorImu>(sen_imu_base);
+    ProcessorBasePtr proc_ftp_base = problem->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/demos/processor_imu_solo12.yaml");
+    ProcessorImuPtr proc_imu = std::static_pointer_cast<ProcessorImu>(proc_ftp_base);
+
+    // // SENSOR + PROCESSOR POINT FEET NOMOVE
+    // ParamsSensorPointFeetNomovePtr intr_pfn = std::make_shared<ParamsSensorPointFeetNomove>();
+    // intr_pfn->std_foot_nomove_ = std_odom3d_est;
+    // VectorXd extr;  // default size for dynamic vector is 0-0 -> what we need
+    // SensorBasePtr sen_pfnm_base = problem->installSensor("SensorPointFeetNomove", "SenPfnm", extr, intr_pfn);
+    // SensorPointFeetNomovePtr sen_pfnm = std::static_pointer_cast<SensorPointFeetNomove>(sen_pfnm_base);
+    // ParamsProcessorPointFeetNomovePtr params_pfnm = std::make_shared<ParamsProcessorPointFeetNomove>();
+    // ProcessorBasePtr proc_pfnm_base = problem->installProcessor("ProcessorPointFeetNomove", "PPointFeetNomove", sen_pfnm, params_pfnm);
+
+    // SENSOR + PROCESSOR POSE (for mocap)
+    // pose sensor and proc (to get extrinsics in the prb)
+    auto intr_sp = std::make_shared<ParamsSensorPose>();
+    intr_sp->std_p = std_pose_p;
+    intr_sp->std_o = toRad(std_pose_o_deg);
+    Vector7d extr_pose; extr_pose << i_p_ib, i_q_b.coeffs();
+    auto sensor_pose = problem->installSensor("SensorPose", "pose", extr_pose, intr_sp);
+    auto params_proc = std::make_shared<ParamsProcessorPose>();
+    params_proc->time_tolerance = time_tolerance_mocap;
+    auto proc_pose = problem->installProcessor("ProcessorPose", "pose", sensor_pose, params_proc);
+    // somehow by default the sensor extrinsics is fixed
+    Matrix3d cov_sp_p = pow(std_mocap_extr_p, 2)*Matrix3d::Identity();
+    Matrix3d cov_sp_o = pow(std_mocap_extr_o_deg/60, 2)*Matrix3d::Identity();
+    sensor_pose->addPriorParameter('P', extr_pose.head<3>(), cov_sp_p);
+    sensor_pose->addPriorParameter('O', extr_pose.tail<4>(), cov_sp_o);
+    if (unfix_extr_sensor_pose){
+        sensor_pose->unfixExtrinsics();
+    }
+    else{
+        sensor_pose->fixExtrinsics();
+    }
+
+
+
+    // SETPRIOR RETRO-ENGINEERING
+    // We are not using setPrior because of processors initial captures problems so we have to
+    // - Manually create the first KF and its pose and velocity priors
+    // - call setOrigin on processors MotionProvider since the flag prior_is_set is not true when doing installProcessor (later)
+
+    VectorComposite x_origin("POV", {w_p_wm, w_qvec_wm, Vector3d::Zero()});
+    VectorComposite std_origin("POV", {std_prior_p*Vector3d::Ones(), std_prior_o*Vector3d::Ones(), std_prior_v*Vector3d::Ones()});
+    // FrameBasePtr KF1 = problem->setPriorFactor(x_origin, std_origin, t0);
+    FrameBasePtr KF1 = problem->setPriorInitialGuess(x_origin, t0);  // if mocap used
+    
+    proc_imu->setOrigin(KF1);
+
+    //////////////////////////////////////////
+    // INITIAL BIAS FACTORS
+    // Add absolute factor on Imu biases to simulate a fix()
+    Vector6d std_abs_imu; std_abs_imu << std_abs_bias_acc*Vector3d::Ones(), std_abs_bias_gyro*Vector3d::Ones();
+    Matrix6d Q_bi = std_abs_imu.array().square().matrix().asDiagonal();    
+    CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1, "bi0", t0);
+    FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", bias_imu_prior, Q_bi);
+    FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1->getCaptureOf(sen_imu)->getSensorIntrinsic(), nullptr, false);   
+    KF1->getCaptureOf(sen_imu)->getStateBlock('I')->setState(bias_imu_prior);
+    sen_imu->getStateBlock('I')->setState(bias_imu_prior);
+    // sen_imu->fixIntrinsics();
+
+
+    ///////////////////////////////////////////
+    // process measurements in sequential order
+    double ts_since_last_kf = 0;
+
+    std::vector<Vector3d> i_p_il_vec_prev;
+    std::vector<Vector4d> i_qvec_l_vec_prev;
+    std::vector<bool> feet_in_contact_prev;
+
+    //////////////////////////////////////////
+    // Vectors to store estimates at the time of data processing 
+    // fbk -> feedback: to check if the estimation is stable enough for feedback control
+    std::vector<Vector3d> p_ob_fbk_v; 
+    std::vector<Vector4d> q_ob_fbk_v; 
+    std::vector<Vector3d> v_ob_fbk_v; 
+    // Allocate on the heap to prevent stack overflow for large datasets
+    double* o_p_ob_fbk_carr = new double[3*N];
+    double* o_q_b_fbk_carr = new double[4*N];
+    double* o_v_ob_fbk_carr = new double[3*N];
+    double* imu_bias_fbk_carr = new double[6*N];
+    double* extr_mocap_fbk_carr = new double[7*N];
+
+    std::vector<Vector3d> i_omg_oi_v; 
+    //////////////////////////////////////////
+
+    unsigned int nb_kf = 1;
+    for (unsigned int i=0; i < N; i++){
+        TimeStamp ts(t_arr[i]);
+
+        ////////////////////////////////////
+        // Retrieve current state
+        VectorComposite curr_state = problem->getState();
+        Vector4d o_qvec_i_curr = curr_state['O'];
+        Quaterniond o_q_i_curr(o_qvec_i_curr); 
+        Vector3d o_v_oi_curr = curr_state['V']; 
+
+        ////////////////////////////////////
+        ////////////////////////////////////
+        // Get measurements
+        // retrieve traj data
+        Eigen::Map<Vector3d> imu_acc(imu_acc_arr+3*i);
+        Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr+3*i);  // Hyp: b=i (not the case)
+        Eigen::Map<Matrix<double,12, 1>> tau(tau_arr+12*i);
+        Eigen::Map<Matrix<double,12, 1>> qa(qa_arr+12*i);
+        Eigen::Map<Matrix<double,12, 1>> dqa(dqa_arr+12*i);
+        // Eigen::Map<Matrix<double,4, 1>> contact_gtr(contact_arr+4*i);  // int conversion does not work!
+        // Eigen::Map<Matrix<double,12, 1>> b_p_bl_mocap(b_p_bl_mocap_arr+12*i);  // int conversion does not work!
+        Eigen::Map<Vector3d> w_p_wm(w_p_wm_arr+3*i);
+        Eigen::Map<Vector4d> w_qvec_wm(w_q_m_arr+4*i);
+        w_qvec_wm.normalize();  // not close enough to wolf eps sometimes
+
+        // store i_omg_oi for later computation of o_v_ob from o_v_oi
+        i_omg_oi_v.push_back(i_omg_oi);
+
+        Vector6d imu_bias = sen_imu->getStateBlockDynamic('I', t_arr[i])->getState();
+        Vector3d i_omg_oi_unbiased = (i_omg_oi - imu_bias.tail<3>());
+
+
+        ////////////////////////////////////
+        // IMU
+        Vector6d acc_gyr_meas; acc_gyr_meas << imu_acc, i_omg_oi;
+        Matrix6d acc_gyr_cov = sen_imu->getNoiseCov();
+
+        ////////////////////////////////////
+        // Kinematics + forces
+        // SE3 o_T_i(o_q_i_curr, Vector3d::Zero());
+        // Matrix3d o_R_i = o_T_i.rotation();
+        // Matrix3d i_R_o = o_R_i.transpose();
+        // Vector3d i_v_oi_curr = i_R_o * o_v_oi_curr; 
+        // Vector3d i_acc = imu_acc + i_R_o * gravity();
+        // Vector3d i_asp_i = i_acc - i_omg_oi_unbiased.cross(i_v_oi_curr);
+
+        // VectorXd q(19); q << 0,0,0, o_q_i_curr.coeffs(), qa;
+        // VectorXd dq(18); dq << i_v_oi_curr, i_omg_oi_unbiased, dqa;
+        // VectorXd ddq(model.nv); ddq.setZero();
+        // ddq.head<3>() = i_asp_i;
+        // // TODO: add other terms to compute forces (ddqa...)
+
+        // // update and retrieve kinematics
+        // forwardKinematics(model, data, q);
+        // updateFramePlacements(model, data);
+
+        // // compute all linear jacobians in feet frames (only for quadruped)
+        // MatrixXd Jlinvel(12, model.nv); Jlinvel.setZero();
+        // for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+        //     MatrixXd Jee(6, model.nv); Jee.setZero();
+        //     computeFrameJacobian(model, data, q, ee_ids[i_ee], Jee);
+        //     Jlinvel.block(3*i_ee, 0, 3, model.nv) = Jee.topRows<3>();
+        // }
+
+        // // estimate forces from torques
+        // VectorXd tau_cf = rnea(model, data, q, dq, ddq);  // compute total torques applied on the joints (and free flyer)
+        // tau_cf.tail(model.nv-6) -= tau;  // remove measured joint torque (not on the free flyer)
+
+        // VectorXd forces = Jlinvel_reduced.transpose().lu().solve(tau_joints); // works only for exactly determined system, removing free flyer from the system -> 12x12 J mat 
+        // Solve in Least square sense (3 ways):
+        // VectorXd forces = Jlinvel.transpose().bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(tau_cf);  // SVD
+        // VectorXd l_forces = Jlinvel.transpose().colPivHouseholderQr().solve(tau_cf);  // QR
+        // (A.transpose() * A).ldlt().solve(A.transpose() * b)  // solve the normal equation (twice less accurate than other 2)
+
+        // Or else, retrieve from preprocessed dataset
+        // Eigen::Map<Matrix<double,12, 1>> l_forces(l_forces_arr+12*i);
+
+        // Vector3d o_f_tot = Vector3d::Zero();
+        // std::vector<Vector3d> l_f_l_vec;
+        // std::vector<Vector3d> o_f_l_vec;
+        // std::vector<Vector3d> i_p_il_vec;
+        // std::vector<Vector4d> i_qvec_l_vec;
+        // // needing relative kinematics measurements
+        // VectorXd q_static = q; q_static.head<7>() << 0,0,0, 0,0,0,1;
+        // VectorXd dq_static = dq; dq_static.head<6>() << 0,0,0, 0,0,0;
+        // forwardKinematics(model, data, q_static, dq_static);
+        // updateFramePlacements(model, data);
+        // // std::cout << "" << std::endl;
+        // for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+        //     // std::cout << i_ee << std::endl;
+        //     auto b_T_l = data.oMf[ee_ids[i_ee]];
+
+        //     // overides with value from mocap
+        //     // std::cout << b_T_l.translation().transpose() << std::endl;
+        //     b_T_l.translation(b_p_bl_mocap.segment<3>(3*i_ee));
+        //     // std::cout << b_T_l.translation().transpose() << std::endl;
+
+        //     auto i_T_l = i_T_b * b_T_l;
+        //     Vector3d i_p_il = i_T_l.translation();                       // meas
+        //     Matrix3d i_R_l = i_T_l.rotation();
+        //     Vector4d i_qvec_l = Quaterniond(i_R_l).coeffs();  // meas
+        //     i_p_il = i_p_il +  i_R_l*l_p_lg;
+
+        //     Vector3d l_f_l = l_forces.segment(3*i_ee, 3);  // meas
+        //     // Vector3d o_f_l = o_R_i*i_R_l * l_f_l;     // for contact test
+
+        //     l_f_l_vec.push_back(l_f_l);
+        //     // o_f_l_vec.push_back(o_f_l);
+        //     i_p_il_vec.push_back(i_p_il);
+        //     i_qvec_l_vec.push_back(i_qvec_l);
+        // }
+
+        CaptureImuPtr CImu = std::make_shared<CaptureImu>(ts, sen_imu, acc_gyr_meas, acc_gyr_cov);
+        CImu->process();
+
+        // 1) detect feet in contact
+        // int nb_feet_in_contact = 0;
+        // std::unordered_map<int, Vector3d> kin_incontact;
+        // for (unsigned int i_ee=0; i_ee<nbc; i_ee++){
+        //     // basic contact detection based on normal foot vel, things break if no foot is in contact
+        //     // std::cout << "F" << ee_names[i_ee] << " norm: " << wrench_meas_v[i_ee][i].norm() << std::endl;
+        //     // nb_feet: just for debugging/testing kinematic factor
+
+        //     // if ((o_f_l_vec[i_ee](2) > fz_thresh) && (nb_feet_in_contact < nb_feet)){
+        //     if (contact_gtr(i_ee)){
+        //         nb_feet_in_contact++;
+        //         kin_incontact.insert({i_ee, i_p_il_vec[i_ee]});
+        //     }
+        // }
+
+        // CapturePointFeetNomovePtr CPF = std::make_shared<CapturePointFeetNomove>(ts, sen_pfnm, kin_incontact);
+        // CPF->process();
+        
+        // Vector7d pose_meas; pose_meas << w_p_wm, w_qvec_wm;
+        Vector7d pose_meas; pose_meas << w_p_wm, w_qvec_wm;
+        CapturePosePtr CP = std::make_shared<CapturePose>(ts+time_shift_mocap, sensor_pose, pose_meas, sensor_pose->getNoiseCov());
+        CP->process();
+
+        // ts_since_last_kf += dt;
+        // if ((solve_every_sec > 0) && (ts_since_last_kf >= solve_every_sec)){
+        //     // problem->print(4,true,true,true);
+        //     auto kf = problem->emplaceFrame(ts);
+        //     problem->keyFrameCallback(kf, nullptr, 0.0005);
+        //     std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        //     std::cout << report << std::endl;
+        //     ts_since_last_kf = 0;
+        //     // std::cout << "Extr SPose: " << sensor_pose->getP()->getState().transpose() << " " << sensor_pose->getO()->getState().transpose() << std::endl;
+        // }
+
+        // solve every new KF
+        if (problem->getTrajectory()->getFrameMap().size() > nb_kf ){
+            std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+            std::cout << ts << "  ";
+            std::cout << report << std::endl;
+            nb_kf = problem->getTrajectory()->getFrameMap().size();
+        }
+
+        // Store current state estimation
+        VectorComposite state_fbk = problem->getState(ts);
+        Vector3d o_p_oi = state_fbk['P'];
+        Vector4d o_qvec_i = state_fbk['O'];
+        Vector3d o_v_oi = state_fbk['V'];
+
+        // IMU to base frame 
+        Matrix3d o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        // o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        Matrix3d o_R_b = o_R_i * i_R_b;
+        Vector4d o_qvec_b = Quaterniond(o_R_b).coeffs();
+        Vector3d o_p_ob = o_p_oi + o_R_i * i_p_ib;
+        Vector3d o_v_ob = o_v_oi + o_R_i * i_omg_oi_unbiased.cross(o_R_i*b_p_bi);
+        // Vector3d o_p_ob = o_p_oi;
+        // Vector3d o_v_ob = o_v_oi;
+
+        imu_bias = sen_imu->getIntrinsic()->getState();
+        Vector7d extr_mocap_est; extr_mocap_est << sensor_pose->getP()->getState(), sensor_pose->getO()->getState();
+
+        mempcpy(o_p_ob_fbk_carr+3*i, o_p_ob.data(),             3*sizeof(double));
+        mempcpy(o_q_b_fbk_carr +4*i, o_qvec_b.data(),           4*sizeof(double));
+        mempcpy(o_v_ob_fbk_carr+3*i, o_v_ob.data(),             3*sizeof(double));
+        mempcpy(imu_bias_fbk_carr+6*i, imu_bias.data(),         6*sizeof(double));
+        mempcpy(extr_mocap_fbk_carr+7*i, extr_mocap_est.data(), 7*sizeof(double));
+
+        p_ob_fbk_v.push_back(o_p_ob);
+        q_ob_fbk_v.push_back(o_qvec_b);
+        v_ob_fbk_v.push_back(o_v_ob);
+    }
+    
+
+
+    ///////////////////////////////////////////
+    //////////////// SOLVING //////////////////
+    ///////////////////////////////////////////
+    if (solve_end){
+        std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        problem->print(4,true,true,true);
+        std::cout << report << std::endl;
+    }
+
+    double* o_p_ob_carr = new double[3*N];
+    double* o_q_b_carr = new double[4*N];
+    double* imu_bias_carr = new double[6*N];
+    double* o_v_ob_carr = new double[6*N];
+    for (unsigned int i=0; i < N; i++) { 
+        VectorComposite state_est = problem->getState(t_arr[i]);
+        Vector3d i_omg_oi = i_omg_oi_v[i];
+        Vector3d o_p_oi = state_est['P'];
+        Vector4d o_qvec_i = state_est['O'];
+        Vector3d o_v_oi = state_est['V'];
+
+        auto sb = sen_imu->getStateBlockDynamic('I', t_arr[i]);
+        Vector6d imu_bias = sb->getState();
+        // std::cout << t_arr[i] << ", imu_bias: " << imu_bias.transpose() << std::endl;
+        Vector3d i_omg_oi_unbiased = (i_omg_oi - imu_bias.tail<3>());
+
+        Matrix3d o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        Matrix3d o_R_b = o_R_i * i_R_b;
+        Vector4d o_qvec_b = Quaterniond(o_R_b).coeffs();
+        Vector3d o_p_ob = o_p_oi + o_R_i * i_p_ib;
+        Vector3d o_v_ob = o_v_oi + o_R_i * i_omg_oi_unbiased.cross(i_R_b*b_p_bi);
+        // Vector3d o_p_ob = o_p_oi;
+        // Vector3d o_v_ob = o_v_oi;
+
+        mempcpy(o_p_ob_carr+3*i, o_p_ob.data(),     3*sizeof(double));
+        mempcpy(o_q_b_carr +4*i, o_qvec_b.data(),   4*sizeof(double));
+        mempcpy(o_v_ob_carr+3*i, o_v_ob.data(),     3*sizeof(double));
+        mempcpy(imu_bias_carr+6*i, imu_bias.data(), 6*sizeof(double));
+    }
+
+
+    // Compute Covariances
+    unsigned int Nkf = problem->getTrajectory()->getFrameMap().size();
+    double* tkf_carr  = new double[1*Nkf];
+    double* Qp_carr  = new double[3*Nkf];
+    double* Qo_carr  = new double[3*Nkf];
+    double* Qv_carr  = new double[3*Nkf];
+    double* Qbi_carr = new double[6*Nkf];
+    double* Qm_carr  = new double[6*Nkf];
+    // factor errors
+    double* fac_imu_err_carr = new double[9*Nkf];
+    double* fac_pose_err_carr  = new double[6*Nkf];
+    int i = 0;
+    for (auto& elt: problem->getTrajectory()->getFrameMap()){
+        std::cout << "Traj " << i << "/" << problem->getTrajectory()->getFrameMap().size() << std::endl;
+        tkf_carr[i] = elt.first.get();
+        auto kf = elt.second;
+        VectorComposite kf_state = kf->getState();
+        
+        // compute covariances of KF and capture stateblocks
+        Eigen::MatrixXd Qp =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qo =  Eigen::Matrix3d::Identity();  // global or local?
+        Eigen::MatrixXd Qv =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbi = Eigen::Matrix6d::Identity();
+        Eigen::MatrixXd Qmp =  Eigen::Matrix3d::Identity();  // extr mocap
+        Eigen::MatrixXd Qmo =  Eigen::Matrix3d::Identity();  // extr mocap
+        
+        solver->computeCovariances(SolverManager::CovarianceBlocksToBeComputed::ALL);  // should not be computed every time !! see below
+        problem->getCovarianceBlock(kf->getStateBlock('P'), Qp);
+        problem->getCovarianceBlock(kf->getStateBlock('O'), Qo);
+        problem->getCovarianceBlock(kf->getStateBlock('V'), Qv);
+
+        CaptureBasePtr cap_imu = kf->getCaptureOf(sen_imu); 
+
+        std::vector<StateBlockPtr> extr_sb_vec = {sensor_pose->getP(), sensor_pose->getO()};
+        solver->computeCovariances(extr_sb_vec);  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(sensor_pose->getP(), Qmp);
+        problem->getCovarianceBlock(sensor_pose->getO(), Qmo);
+        // std::cout << "Qbp\n" << Qbp << std::endl;
+        solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi);
+
+        // diagonal components
+        Vector3d Qp_diag = Qp.diagonal(); 
+        Vector3d Qo_diag = Qo.diagonal(); 
+        Vector3d Qv_diag = Qv.diagonal(); 
+        Vector6d Qbi_diag = Qbi.diagonal(); 
+        Vector6d Qm_diag; Qm_diag << Qmp.diagonal(), Qmo.diagonal(); 
+        
+        memcpy(Qp_carr  + 3*i, Qp_diag.data(),  3*sizeof(double)); 
+        memcpy(Qo_carr  + 3*i, Qo_diag.data(),  3*sizeof(double)); 
+        memcpy(Qv_carr  + 3*i, Qv_diag.data(),  3*sizeof(double)); 
+        memcpy(Qbi_carr + 6*i, Qbi_diag.data(), 6*sizeof(double)); 
+        memcpy(Qm_carr  + 6*i, Qm_diag.data(),  6*sizeof(double)); 
+
+        // Factor errors
+        // std::cout << "Factors " << kf->getTimeStamp().get() << std::endl;
+        FactorBasePtrList fac_lst; 
+        kf->getFactorList(fac_lst);
+        Vector9d imu_err = Vector9d::Zero();
+        Vector6d pose_err = Vector6d::Zero();
+        for (auto fac: fac_lst){
+            if (fac->getProcessor() == proc_imu){
+                auto f = std::dynamic_pointer_cast<FactorImu>(fac);
+                // Matrix6d R_bias_drift = f->getMeasurementSquareRootInformationUpper().bottomRightCorner<6,6>();
+                // Matrix6d R_bias_drift = f->getFeature()->getMeasurementSquareRootInformationUpper().bottomRightCorner<6,6>();
+                // std::cout << R_bias_drift << std::endl;
+                // MatrixXd R_bias_drift = f->getFeature()->getMeasurementSquareRootInformationUpper();
+                // MatrixXd Cov_bias_drift = f->getFeature()->getMeasurementCovariance();
+                // std::cout << "R_bias_drift" << std::endl;
+                // std::cout << R_bias_drift << std::endl;
+                // std::cout << "Cov_bias_drift" << std::endl;
+                // std::cout << Cov_bias_drift << std::endl;
+
+                if (f){
+                    imu_err = f->error();
+                }
+            }
+            if (fac->getProcessor() == proc_pose){
+                auto f = std::dynamic_pointer_cast<FactorPose3dWithExtrinsics>(fac);
+                if (f){
+                    pose_err = f->error();
+                }
+            }
+        }
+        memcpy(fac_imu_err_carr + 9*i, imu_err.data(), 9*sizeof(double)); 
+        memcpy(fac_pose_err_carr + 6*i, pose_err.data(),  6*sizeof(double)); 
+
+
+        i++;
+    }
+
+    // Save trajectories in npz file
+    // save ground truth    
+    cnpy::npz_save(out_npz_file_path, "t", t_arr, {N}, "w");             // "w" overwrites any file with same name
+    cnpy::npz_save(out_npz_file_path, "w_p_wm", w_p_wm_arr, {N,3}, "a"); // "a" appends to the file we created above
+    cnpy::npz_save(out_npz_file_path, "w_q_m",  w_q_m_arr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "w_v_wm", w_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "m_v_wm", m_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_i",  o_q_i_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "qa",     qa_arr, {N,12}, "a");
+
+    // save estimates
+    cnpy::npz_save(out_npz_file_path, "o_p_ob_fbk", o_p_ob_fbk_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_b_fbk",  o_q_b_fbk_carr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_ob_fbk", o_v_ob_fbk_carr, {N,3}, "a");
+    
+    cnpy::npz_save(out_npz_file_path, "o_p_ob", o_p_ob_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_b",  o_q_b_carr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_ob", o_v_ob_carr, {N,3}, "a");
+    
+    // and biases/extrinsics
+    cnpy::npz_save(out_npz_file_path, "imu_bias_fbk", imu_bias_fbk_carr, {N,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "imu_bias", imu_bias_carr, {N,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "extr_mocap_fbk", extr_mocap_fbk_carr, {N,7}, "a");
+
+    // covariances
+    cnpy::npz_save(out_npz_file_path, "tkf", tkf_carr, {Nkf}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qp", Qp_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qo", Qo_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qv", Qv_carr,   {Nkf,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qbi", Qbi_carr, {Nkf,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "Qm", Qm_carr,   {Nkf,6}, "a");
+    cnpy::npz_save(out_npz_file_path, "fac_imu_err", fac_imu_err_carr, {Nkf,9}, "a");
+    cnpy::npz_save(out_npz_file_path, "fac_pose_err", fac_pose_err_carr, {Nkf,6}, "a");
+
+}
diff --git a/demos/solo_real_povcdl_estimation.cpp b/demos/solo_real_povcdl_estimation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0e4e6a4a6ce958563c3b4e84dfe3f31feb42f464
--- /dev/null
+++ b/demos/solo_real_povcdl_estimation.cpp
@@ -0,0 +1,876 @@
+//--------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--------
+#include <iostream>
+#include <fstream>
+// #include <random>
+#include <yaml-cpp/yaml.h>
+#include <Eigen/Dense>
+#include <ctime>
+#include "eigenmvn.h"
+
+#include "pinocchio/parsers/urdf.hpp"
+#include "pinocchio/algorithm/joint-configuration.hpp"
+#include "pinocchio/algorithm/kinematics.hpp"
+#include "pinocchio/algorithm/center-of-mass.hpp"
+#include "pinocchio/algorithm/centroidal.hpp"
+#include "pinocchio/algorithm/rnea.hpp"
+#include "pinocchio/algorithm/crba.hpp"
+#include "pinocchio/algorithm/frames.hpp"
+
+// CNPY
+#include<cnpy.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_block_absolute.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/capture/capture_leg_odom.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+#include "bodydynamics/factor/factor_inertial_kinematics.h"
+#include "bodydynamics/factor/factor_force_torque_preint.h"
+
+#include "bodydynamics/capture/capture_point_feet_nomove.h"
+#include "bodydynamics/sensor/sensor_point_feet_nomove.h"
+#include "bodydynamics/processor/processor_point_feet_nomove.h"
+
+// UTILS
+#include "mcapi_utils.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using namespace pinocchio;
+
+typedef pinocchio::SE3Tpl<double> SE3;
+typedef pinocchio::ForceTpl<double> Force;
+
+
+int main (int argc, char **argv) {
+    // retrieve parameters from yaml file
+    YAML::Node config = YAML::LoadFile("/home/mfourmy/Documents/Phd_LAAS/wolf/bodydynamics/demos/solo_real_estimation.yaml");
+    std::string robot_urdf = config["robot_urdf"].as<std::string>();
+    int dimc = config["dimc"].as<int>();
+    double mass_offset = config["mass_offset"].as<double>();
+    double dt = config["dt"].as<double>();
+    double min_t = config["min_t"].as<double>();
+    double max_t = config["max_t"].as<double>();
+    double solve_every_sec = config["solve_every_sec"].as<double>();
+    bool solve_end = config["solve_end"].as<bool>();
+
+    // Ceres setup
+    int max_num_iterations = config["max_num_iterations"].as<int>();
+
+    // estimator sensor noises
+    double std_pbc_est    = config["std_pbc_est"].as<double>();
+    double std_vbc_est    = config["std_vbc_est"].as<double>();
+    double std_f_est      = config["std_f_est"].as<double>();
+    double std_tau_est    = config["std_tau_est"].as<double>();
+    double std_odom3d_est = config["std_odom3d_est"].as<double>();
+    
+    // priors
+    double std_prior_p = config["std_prior_p"].as<double>();
+    double std_prior_o = config["std_prior_o"].as<double>();
+    double std_prior_v = config["std_prior_v"].as<double>();
+    double std_abs_bias_pbc  = config["std_abs_bias_pbc"].as<double>();
+    double std_abs_bias_acc  = config["std_abs_bias_acc"].as<double>();
+    double std_abs_bias_gyro = config["std_abs_bias_gyro"].as<double>();    
+    double std_bp_drift      = config["std_bp_drift"].as<double>();
+    std::vector<double> bias_pbc_prior_vec = config["bias_pbc_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> bias_pbc_prior(bias_pbc_prior_vec.data());
+    std::vector<double> bias_imu_prior_vec = config["bias_imu_prior"].as<std::vector<double>>();
+    Eigen::Map<Vector6d> bias_imu_prior(bias_imu_prior_vec.data());
+
+    double fz_thresh = config["fz_thresh"].as<double>();
+    std::vector<double> b_p_bi_vec = config["b_p_bi"].as<std::vector<double>>();
+    std::vector<double> b_q_i_vec = config["b_q_i"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> b_p_bi(b_p_bi_vec.data());
+    Eigen::Map<Vector4d> b_qvec_i(b_q_i_vec.data());
+
+    std::vector<double> artificial_bias_p_vec = config["artificial_bias_p"].as<std::vector<double>>();
+    Eigen::Map<Vector3d> artificial_bias_p(artificial_bias_p_vec.data());
+
+    std::string data_file_path = config["data_file_path"].as<std::string>();
+    std::string out_npz_file_path = config["out_npz_file_path"].as<std::string>();
+
+    std::vector<std::string> ee_names = {"FL_ANKLE", "FR_ANKLE", "HL_ANKLE", "HR_ANKLE"};
+    unsigned int nbc = ee_names.size();
+    // Base to IMU transform
+    Quaterniond b_q_i(b_qvec_i);
+    SE3 b_T_i(b_q_i, b_p_bi);
+    SE3 i_T_b = b_T_i.inverse();
+    Matrix3d b_R_i =  b_T_i.rotation();
+    Vector3d i_p_ib = i_T_b.translation();
+    Matrix3d i_R_b =  i_T_b.rotation();
+
+    // initialize some vectors and fill them with dicretized data
+    cnpy::npz_t arr_map = cnpy::npz_load(data_file_path);
+
+    //load it into a new array
+    cnpy::NpyArray t_npyarr = arr_map["t"];
+    cnpy::NpyArray imu_acc_npyarr = arr_map["imu_acc"];
+    // cnpy::NpyArray o_a_oi_npyarr = arr_map["o_a_oi"];
+    cnpy::NpyArray o_q_i_npyarr = arr_map["o_q_i"];
+    cnpy::NpyArray i_omg_oi_npyarr = arr_map["i_omg_oi"];
+    cnpy::NpyArray qa_npyarr = arr_map["qa"];
+    cnpy::NpyArray dqa_npyarr = arr_map["dqa"];
+    cnpy::NpyArray tau_npyarr = arr_map["tau"];
+    cnpy::NpyArray l_forces_npyarr = arr_map["l_forces"];
+    // cnpy::NpyArray w_pose_wm_npyarr = arr_map["w_pose_wm"];
+    cnpy::NpyArray m_v_wm_npyarr = arr_map["m_v_wm"];
+    cnpy::NpyArray w_v_wm_npyarr = arr_map["w_v_wm"];
+    // cnpy::NpyArray o_R_i_npyarr = arr_map["o_R_i"];
+    cnpy::NpyArray w_p_wm_npyarr = arr_map["w_p_wm"];
+    cnpy::NpyArray w_q_m_npyarr = arr_map["w_q_m"];
+    // cnpy::NpyArray w_R_m_npyarr = arr_map["w_R_m"];
+    double* t_arr = t_npyarr.data<double>();
+    double* imu_acc_arr = imu_acc_npyarr.data<double>();
+    // double* o_a_oi_arr = o_a_oi_npyarr.data<double>();
+    double* i_omg_oi_arr = i_omg_oi_npyarr.data<double>();
+    double* qa_arr = qa_npyarr.data<double>();
+    double* dqa_arr = dqa_npyarr.data<double>();
+    double* tau_arr = tau_npyarr.data<double>();
+    double* l_forces_arr = l_forces_npyarr.data<double>();
+    // double* w_pose_wm_arr = w_pose_wm_npyarr.data<double>();
+    double* m_v_wm_arr = m_v_wm_npyarr.data<double>();
+    double* w_v_wm_arr = w_v_wm_npyarr.data<double>();
+    double* o_q_i_arr = o_q_i_npyarr.data<double>();
+    // double* o_R_i_arr = o_R_i_npyarr.data<double>();
+    double* w_p_wm_arr = w_p_wm_npyarr.data<double>();
+    double* w_q_m_arr = w_q_m_npyarr.data<double>();
+    // double* w_R_m_arr = w_R_m_npyarr.data<double>();
+    unsigned int N = t_npyarr.shape[0];
+    if (max_t > 0){
+        N = N < int(max_t/dt) ? N : int(max_t/dt);
+    }
+
+    // Creating measurements from simulated trajectory
+    // Load the urdf model
+    Model model;
+    pinocchio::urdf::buildModel(robot_urdf, JointModelFreeFlyer(), model);
+    std::cout << "model name: " << model.name << std::endl;
+    // set gravity of the model to the one decided by wolf
+    model.gravity.linear() = wolf::gravity();
+    // to accomodate the difference between real robot and model masses
+    model.inertias[1].mass() = model.inertias[1].mass() + mass_offset;
+
+    // add artificial lever to test bias estimation on real data
+    VectorXd q_toto(19); q_toto << 0,0,0, 0,0,0,1, 0,0,0,0,0,0,0,0,0,0,0,0; // TODO: put current base orientation estimate? YES
+
+    Data data(model);
+    
+    centerOfMass(model, data, q_toto);        
+    std::cout << "artificial_bias_p: " << artificial_bias_p.transpose() << std::endl;
+    std::cout << "model.inertias[1].lever(): " << model.inertias[1].lever().transpose() << std::endl;
+    std::cout << "data.com[0]: " << data.com[0].transpose() << std::endl;
+    model.inertias[1] = pinocchio::Inertia(model.inertias[1].mass(), artificial_bias_p, model.inertias[1].inertia());
+    centerOfMass(model, data, q_toto);        
+    std::cout << "model.inertias[1].lever(): " << model.inertias[1].lever().transpose() << std::endl;
+    std::cout << "data.com[0]: " << data.com[0].transpose() << std::endl;
+
+
+    // Recover end effector frame ids
+    std::vector<int> ee_ids;
+    std::cout << "Frame ids" << std::endl;
+    for (auto ee_name: ee_names){
+        ee_ids.push_back(model.getFrameId(ee_name));
+        std::cout << ee_name << std::endl;
+    }
+
+    /////////////////////////
+    // WOLF enters the scene
+    // SETUP PROBLEM/SENSORS/PROCESSORS
+    std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+    ProblemPtr problem = Problem::create("POVCDL", 3);
+
+    //////////////////////
+    // COMPUTE INITIAL STATE
+    TimeStamp t0(t_arr[0]);
+    Eigen::Map<Matrix<double,12, 1>> qa(qa_arr);
+    Eigen::Map<Matrix<double,12, 1>> dqa(dqa_arr);
+    Eigen::Map<Vector4d> o_q_i(o_q_i_arr);  // initialize with IMU orientation
+    Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr);  // Hyp: b=i (not the case)
+    Eigen::Map<Vector3d> w_p_wm(w_p_wm_arr);
+    Eigen::Map<Vector4d> w_qvec_wm(w_q_m_arr);
+    w_qvec_wm.normalize();  // not close enough to wolf eps sometimes
+
+    // initial state
+    VectorXd q(19); q << 0,0,0, o_q_i, qa; // TODO: put current base orientation estimate? YES
+    VectorXd dq(18); dq << 0,0,0, i_omg_oi, dqa; // TODO: put current base velocity estimate? YES
+
+
+    centerOfMass(model, data, q, dq);        
+    // Vector3d w_p_wc = data.com[0]; 
+    // Vector3d w_v_wc = data.vcom[0];
+
+    // gesticulation in base frame: just compute centroidal momentum with static q and vq
+    // Vector3d w_Lc = computeCentroidalMomentum(model, data, q, dq).angular();
+
+    // hyp: robot not moving initially (no vel and angular momentum)
+    VectorXd x_origin; x_origin.resize(19);
+    // x_origin << w_p_wm, w_qvec_wm, 0,0,0, w_p_wc, 0,0,0, 0,0,0;
+    x_origin << 0,0,0, o_q_i, 0,0,0, 0,0,0, 0,0,0, 0,0,0;
+    //////////////////////
+    //////////////////////
+
+    // CERES WRAPPER
+    SolverCeresPtr solver = std::make_shared<SolverCeres>(problem);
+    solver->getSolverOptions().max_num_iterations = max_num_iterations;
+    
+    //===================================================== INITIALIZATION
+    // SENSOR + PROCESSOR INERTIAL KINEMATICS
+    ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+    intr_ik->std_pbc = std_pbc_est;
+    intr_ik->std_vbc = std_vbc_est;
+    VectorXd extr0; // default size for dynamic vector is 0-0 -> what we need
+    SensorBasePtr sen_ikin_base = problem->installSensor("SensorInertialKinematics", "SenIK", extr0, intr_ik);
+    // SensorBasePtr sen_ikin_base = problem->installSensor("SensorInertialKinematics", "SenIK", extr,  bodydynamics_root_dir + "/demos/sensor_inertial_kinematics.yaml");  // TODO: does not work!
+    SensorInertialKinematicsPtr sen_ikin = std::static_pointer_cast<SensorInertialKinematics>(sen_ikin_base);
+
+    ParamsProcessorInertialKinematicsPtr params_ik = std::make_shared<ParamsProcessorInertialKinematics>();
+    params_ik->sensor_angvel_name = "SenImu";
+    params_ik->std_bp_drift = std_bp_drift;
+    ProcessorBasePtr proc_ikin_base = problem->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", sen_ikin, params_ik);
+    // ProcessorBasePtr proc_ikin_base = problem->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", "SenIK", bodydynamics_root_dir + "/demos/processor_inertial_kinematics.yaml");
+    ProcessorInertialKinematicsPtr proc_inkin = std::static_pointer_cast<ProcessorInertialKinematics>(proc_ikin_base);
+
+    // SENSOR + PROCESSOR IMU
+    SensorBasePtr sen_imu_base = problem->installSensor("SensorImu", "SenImu", (Vector7d() << 0, 0, 0, 0, 0, 0, 1).finished(), bodydynamics_root_dir + "/demos/sensor_imu_solo12.yaml");
+    SensorImuPtr sen_imu = std::static_pointer_cast<SensorImu>(sen_imu_base);
+    ProcessorBasePtr proc_ftp_base = problem->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/demos/processor_imu_solo12.yaml");
+    ProcessorImuPtr proc_imu = std::static_pointer_cast<ProcessorImu>(proc_ftp_base);
+
+    // SENSOR + PROCESSOR FT PREINT
+    ParamsSensorForceTorquePtr intr_ft = std::make_shared<ParamsSensorForceTorque>();
+    intr_ft->std_f = std_f_est;
+    intr_ft->std_tau = std_tau_est;
+    intr_ft->mass = data.mass[0];
+    std::cout << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << "\n\nROBOT MASS: " << intr_ft->mass << std::endl;
+    SensorBasePtr sen_ft_base = problem->installSensor("SensorForceTorque", "SenFT", VectorXd(0), intr_ft);
+    // SensorBasePtr sen_ft_base = problem->installSensor("SensorForceTorque", "SenFT", VectorXd(0), bodydynamics_root_dir + "/demos/sensor_ft.yaml");
+    SensorForceTorquePtr sen_ft = std::static_pointer_cast<SensorForceTorque>(sen_ft_base);
+    ParamsProcessorForceTorquePreintPtr params_sen_ft = std::make_shared<ParamsProcessorForceTorquePreint>();
+    params_sen_ft->sensor_ikin_name = "SenIK";
+    params_sen_ft->sensor_angvel_name = "SenImu";
+    params_sen_ft->nbc = nbc;
+    params_sen_ft->dimc = dimc;
+    params_sen_ft->time_tolerance = 0.0005;
+    params_sen_ft->unmeasured_perturbation_std = 0.000001;
+    params_sen_ft->max_time_span = 1000;
+    params_sen_ft->max_buff_length = 500;
+    params_sen_ft->dist_traveled = 20000.0;
+    params_sen_ft->angle_turned = 1000;
+    params_sen_ft->voting_active = false;
+    ProcessorBasePtr proc_ft_base = problem->installProcessor("ProcessorForceTorquePreint", "PFTPreint", sen_ft, params_sen_ft);
+    // ProcessorBasePtr proc_ft_base = problem->installProcessor("ProcessorForceTorquePreint", "PFTPreint", "SenFT", bodydynamics_root_dir + "/demos/processor_ft_preint.yaml");
+    ProcessorForceTorquePreintPtr proc_ftpreint = std::static_pointer_cast<ProcessorForceTorquePreint>(proc_ft_base);
+
+    // SENSOR + PROCESSOR POINT FEET NOMOVE
+    ParamsSensorPointFeetNomovePtr intr_pfn = std::make_shared<ParamsSensorPointFeetNomove>();
+    intr_pfn->std_foot_nomove_ = std_odom3d_est;
+    VectorXd extr;  // default size for dynamic vector is 0-0 -> what we need
+    SensorBasePtr sen_pfnm_base = problem->installSensor("SensorPointFeetNomove", "SenPfnm", extr, intr_pfn);
+    SensorPointFeetNomovePtr sen_pfnm = std::static_pointer_cast<SensorPointFeetNomove>(sen_pfnm_base);
+    ParamsProcessorPointFeetNomovePtr params_pfnm = std::make_shared<ParamsProcessorPointFeetNomove>();
+    ProcessorBasePtr proc_pfnm_base = problem->installProcessor("ProcessorPointFeetNomove", "PPointFeetNomove", sen_pfnm, params_pfnm);
+
+
+    // SETPRIOR RETRO-ENGINEERING
+    // We are not using setPrior because of processors initial captures problems so we have to
+    // - Manually create the first KF and its pose and velocity priors
+    // - call setOrigin on processors MotionProvider since the flag prior_is_set is not true when doing installProcessor (later)
+
+    MatrixXd P_origin(9,9); P_origin.setIdentity();
+    P_origin.block<3,3>(0,0)   = pow(std_prior_p, 2) * Matrix3d::Identity();
+    P_origin.block<3,3>(3,3)   = pow(std_prior_o, 2) * Matrix3d::Identity();
+    P_origin.block<3,3>(6,6)   = pow(std_prior_v, 2) * Matrix3d::Identity();
+    FrameBasePtr KF1 = problem->emplaceFrame(t0, x_origin);
+    // Prior pose factor
+    CapturePosePtr pose_prior_capture = CaptureBase::emplace<CapturePose>(KF1, t0, nullptr, x_origin.head(7), P_origin.topLeftCorner(6, 6));
+    pose_prior_capture->emplaceFeatureAndFactor();
+    // Prior velocity factor
+    CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF1, "Vel0", t0);
+    FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin.segment<3>(7), P_origin.block<3, 3>(6, 6));
+    FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, featV0, KF1->getV(), nullptr, false);
+
+    // Special trick to make things work in the IMU + IKin + FTPreint case
+    // 0. Call keyFrameCallback of processorIKin so that its kf_buffer contains the first KF0
+    // 1. Call setOrigin processorIMU  to generate a fake captureIMU -> generates b_imu
+    // 2. process one IKin capture corresponding to the same KF, the factor needing b_imu -> generates a b_pbc
+    // 3. set processorForceTorquePreint origin which requires both b_imu and b_pbc
+    proc_inkin->keyFrameCallback(KF1, 0.0005);
+    proc_imu->setOrigin(KF1);
+    // proc_legodom->setOrigin(KF1);
+
+    // INERTIAL KINEMATICS CAPTURE DATA ZERO MOTION
+    VectorXd q_static = q;    q_static.head<7>() << 0,0,0, 0,0,0,1;
+    VectorXd dq_static = dq; dq_static.head<6>() << 0,0,0, 0,0,0;
+    centerOfMass(model, data, q_static, dq_static);        
+    Vector3d b_p_bc = data.com[0];  // meas   
+    Vector3d i_p_ic = i_T_b.act(b_p_bc);
+    Vector3d i_v_ic = i_R_b*data.vcom[0];  // meas
+    Matrix3d b_Iw = compute_Iw(model, data, q_static, b_p_bc);
+    Matrix3d i_Iw = i_R_b*b_Iw*b_R_i;  // !! rotate on both sides to keep a symmetric matrix     
+    // Vector3d b_Lc_gesti = computeCentroidalMomentum(model, data, q_static, dq_static).angular();
+    auto b_centro_momentum_gesti = computeCentroidalMomentum(model, data, q_static, dq_static);
+    auto i_centro_momentum_gesti = i_T_b.act(b_centro_momentum_gesti);
+    Vector3d i_Lc_gesti = i_centro_momentum_gesti.angular();
+
+    // momentum parameters
+    VectorXd meas_ikin(9); meas_ikin << i_p_ic, i_v_ic, i_omg_oi;
+    auto CIKin0 = std::make_shared<CaptureInertialKinematics>(t0, sen_ikin, meas_ikin, i_Iw, i_Lc_gesti);
+    CIKin0->process();
+    proc_ftpreint->setOrigin(KF1);
+
+    ////////////////////////////////////////////
+    // INITIAL BIAS FACTORS
+    // Add absolute factor on Imu biases to simulate a fix()
+    Vector6d std_abs_imu; std_abs_imu << std_abs_bias_acc*Vector3d::Ones(), std_abs_bias_gyro*Vector3d::Ones();
+    Matrix6d Q_bi = std_abs_imu.array().square().matrix().asDiagonal();    
+    CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1, "bi0", t0);
+    FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", bias_imu_prior, Q_bi);
+    FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1->getCaptureOf(sen_imu)->getSensorIntrinsic(), nullptr, false);  
+    sen_imu->getStateBlock('I')->setState(bias_imu_prior);
+
+    // Add loose absolute factor on initial bp bias since dynamical trajectories 
+    Matrix3d Q_bp = pow(std_abs_bias_pbc, 2)* Matrix3d::Identity();
+    CaptureBasePtr cap_bp0 = CaptureBase::emplace<CaptureBase>(KF1, "bp0", t0);
+    FeatureBasePtr feat_bp0 = FeatureBase::emplace<FeatureBase>(cap_bp0, "bp0", bias_pbc_prior, Q_bp);
+    FactorBasePtr fac_bp0 = FactorBase::emplace<FactorBlockAbsolute>(feat_bp0, feat_bp0, KF1->getCaptureOf(sen_ikin)->getSensorIntrinsic(), nullptr, false); 
+    sen_ikin->getStateBlock('I')->setState(bias_pbc_prior);
+
+
+    ///////////////////////////////////////////
+    // process measurements in sequential order
+    double ts_since_last_kf = 0;
+
+    // for point feet factor
+    std::vector<Vector3d> i_p_il_vec_origin;
+    FrameBasePtr KF_origin = KF1;
+
+    //////////////////////////////////////////
+    // Vectors to store estimates at the time of data processing 
+    // fbk -> feedback: to check if the estimation is stable enough for feedback control
+    std::vector<Vector3d> p_ob_fbk_v; 
+    std::vector<Vector4d> q_ob_fbk_v; 
+    std::vector<Vector3d> v_ob_fbk_v; 
+    std::vector<Vector3d> c_ob_fbk_v; 
+    std::vector<Vector3d> cd_ob_fbk_v; 
+    std::vector<Vector3d> Lc_ob_fbk_v; 
+
+    std::vector<Vector3d> i_omg_oi_v; 
+    //////////////////////////////////////////
+
+    unsigned int nb_kf = 1;
+    for (unsigned int i=0; i < N; i++){
+        TimeStamp ts(t_arr[i]);
+
+        ////////////////////////////////////
+        // Retrieve current state 
+        VectorComposite curr_state = problem->getState();
+        Vector4d o_qvec_i_curr = curr_state['O'];
+        Quaterniond o_q_i_curr(o_qvec_i_curr); 
+        Vector3d o_v_oi_curr = curr_state['V']; 
+
+        ////////////////////////////////////
+        ////////////////////////////////////
+        // Get measurements
+        // retrieve traj data
+        Eigen::Map<Vector3d> imu_acc(imu_acc_arr+3*i);
+        Eigen::Map<Vector3d> i_omg_oi(i_omg_oi_arr+3*i);  // Hyp: b=i (not the case)
+        Eigen::Map<Matrix<double,12, 1>> tau(tau_arr+12*i);
+        Eigen::Map<Matrix<double,12, 1>> qa(qa_arr+12*i);
+        Eigen::Map<Matrix<double,12, 1>> dqa(dqa_arr+12*i);
+
+        // store i_omg_oi for later computation of o_v_ob from o_v_oi
+        i_omg_oi_v.push_back(i_omg_oi);
+
+        ////////////////////////////////////
+        // IMU
+        // imu_acc << -wolf::gravity(); 
+        // i_omg_oi << 0,0,0;
+        Vector6d acc_gyr_meas; acc_gyr_meas << imu_acc, i_omg_oi;
+        Matrix6d acc_gyr_cov = sen_imu->getNoiseCov();
+
+        ////////////////////////////////////
+        // Kinematics + forces
+        SE3 o_T_i(o_q_i_curr, Vector3d::Zero()); // TODO: put current orientation estimate here
+        Matrix3d o_R_i = o_T_i.rotation();
+        Matrix3d i_R_o = o_R_i.transpose();
+        Vector3d i_v_oi_curr = i_R_o * o_v_oi_curr; 
+        Vector3d i_acc = imu_acc + i_R_o * gravity();
+        Vector3d i_asp_i = i_acc - i_omg_oi.cross(i_v_oi_curr);
+
+        VectorXd q(19); q << 0,0,0, o_q_i_curr.coeffs(), qa;
+        // VectorXd dq(18); dq << i_v_oi_curr, i_omg_oi, dqa;
+        VectorXd dq(18); dq << 0,0,0, i_omg_oi, dqa;
+        VectorXd ddq(model.nv); ddq.setZero();
+        ddq.head<3>() = i_asp_i;
+        // TODO: add other terms to compute forces (ddqa...)
+
+        // update and retrieve kinematics
+        forwardKinematics(model, data, q);
+        updateFramePlacements(model, data);
+
+        // compute all linear jacobians in feet frames (only for quadruped)
+        MatrixXd Jlinvel(12, model.nv); Jlinvel.setZero();
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            MatrixXd Jee(6, model.nv); Jee.setZero();
+            computeFrameJacobian(model, data, q, ee_ids[i_ee], Jee);
+            Jlinvel.block(3*i_ee, 0, 3, model.nv) = Jee.topRows<3>();
+        }
+
+        // estimate forces from torques
+        VectorXd tau_cf = rnea(model, data, q, dq, ddq);  // compute total torques applied on the joints (and free flyer)
+        tau_cf.tail(model.nv-6) -= tau;  // remove measured joint torque (not on the free flyer)
+
+        // VectorXd forces = Jlinvel_reduced.transpose().lu().solve(tau_joints); // works only for exactly determined system, removing free flyer from the system -> 12x12 J mat 
+        // Solve in Least square sense (3 ways):
+        // VectorXd forces = Jlinvel.transpose().bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(tau_cf);  // SVD
+        // VectorXd l_forces = Jlinvel.transpose().colPivHouseholderQr().solve(tau_cf);  // QR
+        // (A.transpose() * A).ldlt().solve(A.transpose() * b)  // solve the normal equation (twice less accurate than other 2)
+
+        // Or else, retrieve from preprocessed dataset
+        Eigen::Map<Matrix<double,12, 1>> l_forces(l_forces_arr+12*i);
+
+
+        Vector3d o_f_tot = Vector3d::Zero();
+        std::vector<Vector3d> l_f_l_vec;
+        std::vector<Vector3d> o_f_l_vec;
+        std::vector<Vector3d> i_p_il_vec;
+        std::vector<Vector4d> i_qvec_l_vec;
+        // needing relative kinematics measurements
+        VectorXd q_static = q; q_static.head<7>() << 0,0,0, 0,0,0,1;
+        forwardKinematics(model, data, q_static);
+        updateFramePlacements(model, data);
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            auto b_T_l = data.oMf[ee_ids[i_ee]];
+            auto i_T_l = i_T_b * b_T_l;
+            Vector3d i_p_il = i_T_l.translation();                       // meas
+            Vector4d i_qvec_l = Quaterniond(i_T_l.rotation()).coeffs();  // meas
+            Vector3d l_f_l = l_forces.segment(3*i_ee, 3);                // meas
+            Vector3d o_f_l = o_R_i*i_T_l.rotation() * l_f_l;     // for contact test
+            o_f_tot += o_f_l;
+
+            // // put the measure to a constant for unit testing 
+            // i_p_il << 0,0,0;
+            // i_qvec_l << 0,0,0,1;
+            // l_f_l << -(intr_ft->mass-0.01)*wolf::gravity()/4;
+
+            l_f_l_vec.push_back(l_f_l);
+            o_f_l_vec.push_back(o_f_l);
+            i_p_il_vec.push_back(i_p_il);
+            i_qvec_l_vec.push_back(i_qvec_l);
+        }
+        // i_p_il_vec[0] <<  1, 0, 0;
+        // i_p_il_vec[1] << -1, 0, 0;
+        // i_p_il_vec[2] <<  0, 1, 0;
+        // i_p_il_vec[3] <<  0,-1, 0;
+
+        // initialization for point feet factor
+        if (i == 0){
+            i_p_il_vec_origin = i_p_il_vec;
+        }
+        // std::cout << "o_f_tot:       " << o_f_tot.transpose() << std::endl;
+        // std::cout << "o_f_tot + m*g: " << (o_f_tot + data.mass[0]*model.gravity.linear()).transpose() << std::endl;
+
+
+        //////////////////////////////////////////////
+        // COM relative position and velocity measures
+        centerOfMass(model, data, q_static, dq_static);        
+        Vector3d b_p_bc = data.com[0];  
+        // std::cout << "b_p_bc: " << b_p_bc.transpose() << std::endl; 
+        Vector3d i_p_ic = i_T_b.act(b_p_bc);  // meas
+        // velocity due to gesticulation only in base frame 
+        // -> set world frame = base frame and set body frame spatial vel to zero 
+        Vector3d i_v_ic = i_R_b*data.vcom[0];  // meas
+        // Centroidal inertia and gesticulation kinetic momentum 
+        Matrix3d b_Iw = compute_Iw(model, data, q_static, b_p_bc);
+        Matrix3d i_Iw = i_R_b*b_Iw*b_R_i;  // !! rotate on both sides to keep a symmetric matrix 
+        // gesticulation in base frame: just compute centroidal momentum with static q and vq
+        // Vector3d b_Lc_gesti = computeCentroidalMomentum(model, data, q_static, dq_static).angular();
+        auto b_centro_momentum_gesti = computeCentroidalMomentum(model, data, q_static, dq_static);
+        auto i_centro_momentum_gesti = i_T_b.act(b_centro_momentum_gesti);
+        Vector3d i_Lc_gesti = i_centro_momentum_gesti.angular();
+
+        // Inertial kinematics
+        meas_ikin << i_p_ic, i_v_ic, i_omg_oi;
+
+        ////////////////////
+        // Force Torque
+        ////////////////////
+
+        VectorXd f_meas(nbc*3); 
+        VectorXd tau_meas(nbc*3);
+        VectorXd kin_meas(nbc*7);
+        for (unsigned int i_ee=0; i_ee < nbc; i_ee++){
+            f_meas.segment<3>(3*i_ee) = l_f_l_vec[i_ee]; 
+            kin_meas.segment<3>(i_ee*3)         = i_p_il_vec[i_ee];
+            kin_meas.segment<4>(nbc*3 + i_ee*4) = i_qvec_l_vec[i_ee];
+        }
+
+        VectorXd cap_ftp_data(dimc*nbc+3+3+nbc*7); 
+        MatrixXd Qftp(dimc*nbc+3+3,dimc*nbc+3+3);  // kin not in covariance mat
+        cap_ftp_data << f_meas, i_p_ic, i_omg_oi, kin_meas;
+        Qftp.setIdentity();
+        Qftp.block(0,0, nbc*3,nbc*3) *= pow(std_f_est, 2);
+        Qftp.block<3, 3>(nbc*dimc, nbc*dimc, 3,3) *= pow(sen_ikin->getPbcNoiseStd(), 2);
+        Qftp.block<3, 3>(nbc*dimc+3,nbc*dimc+3, 3,3) = acc_gyr_cov.bottomRightCorner<3,3>();
+        ////////////////////
+
+        CaptureImuPtr CImu = std::make_shared<CaptureImu>(ts, sen_imu, acc_gyr_meas, acc_gyr_cov);
+        CImu->process();
+        auto CIKin = std::make_shared<CaptureInertialKinematics>(ts, sen_ikin, meas_ikin, i_Iw, i_Lc_gesti);
+        CIKin->process();
+        auto CFTpreint = std::make_shared<CaptureForceTorquePreint>(ts, sen_ft, CIKin, CImu, cap_ftp_data, Qftp);
+        CFTpreint->process();
+
+
+        // 1) detect feet in contact
+        std::vector<int> feet_in_contact;
+        int nb_feet_in_contact = 0;
+        std::unordered_map<int, Vector3d> kin_incontact;
+        for (unsigned int i_ee=0; i_ee<nbc; i_ee++){
+            // basic contact detection based on normal foot vel, things break if no foot is in contact
+            // std::cout << "F" << ee_names[i_ee] << " norm: " << wrench_meas_v[i_ee][i].norm() << std::endl;
+            // std::cout << "o_f_l_vec[i_ee]: " << o_f_l_vec[i_ee].transpose() << std::endl;
+            if (o_f_l_vec[i_ee](2) > fz_thresh){
+                feet_in_contact.push_back(i_ee);
+                nb_feet_in_contact++;
+                kin_incontact.insert({i_ee, i_p_il_vec[i_ee]});
+            }
+        }
+
+        CapturePointFeetNomovePtr CPF = std::make_shared<CapturePointFeetNomove>(ts, sen_pfnm, kin_incontact);
+        CPF->process();
+
+
+
+
+        // add zero vel artificial factor
+        if (problem->getTrajectory()->getFrameMap().size() > nb_kf){
+            auto kf_pair = problem->getTrajectory()->getFrameMap().rbegin();
+            std::cout << "New KF " << kf_pair->first << std::endl;
+            auto kf = kf_pair->second;
+
+            // CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(kf, "Vel0", kf_pair->first);
+            // FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", Vector3d::Zero(), 0.00001*Matrix3d::Ones());
+            // FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, featV0, kf->getV(), nullptr, false);
+
+            nb_kf++;
+            KF_origin = kf;
+        }
+
+
+
+        ts_since_last_kf += dt;
+        if ((solve_every_sec > 0) && (ts_since_last_kf >= solve_every_sec)){
+            // problem->print(4,true,true,true);
+            std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+            std::cout << report << std::endl;
+            ts_since_last_kf = 0;
+        }
+
+        // Store current state estimation
+        VectorComposite state_fbk = problem->getState(ts);
+        Vector3d o_p_oi_new = state_fbk['P'];
+        Vector4d o_qvec_i_new = state_fbk['O'];
+        Vector3d o_v_oi_new = state_fbk['V'];
+
+        Matrix3d o_R_i_new = Quaterniond(o_qvec_i_new).toRotationMatrix();
+        Matrix3d o_R_b_new = o_R_i_new * i_R_b;
+        Vector4d o_qvec_b_new = Quaterniond(o_R_b_new).coeffs();
+        Vector3d o_p_ob_new = o_p_oi_new + o_R_i_new * i_p_ib;
+        Vector3d o_v_ob_new = o_v_oi_new + o_R_b_new * i_omg_oi.cross(b_p_bi);
+
+        p_ob_fbk_v.push_back(o_p_ob_new);
+        q_ob_fbk_v.push_back(o_qvec_b_new);
+        v_ob_fbk_v.push_back(o_v_ob_new);
+        c_ob_fbk_v.push_back(state_fbk['C']);
+        cd_ob_fbk_v.push_back(state_fbk['D']);
+        Lc_ob_fbk_v.push_back(state_fbk['L']);
+    }
+    
+
+
+    ///////////////////////////////////////////
+    //////////////// SOLVING //////////////////
+    ///////////////////////////////////////////
+    problem->print(4,true,true,true);
+    if (solve_end){
+        std::string report = solver->solve(SolverManager::ReportVerbosity::BRIEF);
+        problem->print(4,true,true,true);
+        std::cout << report << std::endl;
+    }
+
+    //////////////////////////////////////
+    //////////////////////////////////////
+    //            STORE DATA            //
+    //////////////////////////////////////
+    //////////////////////////////////////
+    std::fstream file_est; 
+    std::fstream file_fbk; 
+    std::fstream file_kfs; 
+    std::fstream file_cov; 
+    file_est.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/est.csv", std::fstream::out); 
+    file_fbk.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/fbk.csv", std::fstream::out); 
+    file_kfs.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/kfs.csv", std::fstream::out); 
+    file_cov.open("/home/mfourmy/Documents/Phd_LAAS/phd_misc/centroidalkin/cov.csv", std::fstream::out); 
+    std::string header_est = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz,cx,cy,cz,cdx,cdy,cdz,Lx,Ly,Lz\n";
+    file_est << header_est;
+    file_fbk << header_est;
+    std::string header_kfs = "t,px,py,pz,qx,qy,qz,qw,vx,vy,vz,bax,bay,baz,bwx,bwy,bwz,cx,cy,cz,cdx,cdy,cdz,Lx,Ly,Lz,bpx,bpy,bpz\n";
+    file_kfs << header_kfs;
+    std::string header_cov = "t,Qpx,Qpy,Qpz,Qqx,Qqy,Qqz,Qvx,Qvy,Qvz,Qbax,Qbay,Qbaz,Qbwx,Qbwy,Qbwz,Qcx,Qcy,Qcz,Qcdx,Qcdy,Qcdz,QLx,QLy,QLz,Qbpx,Qbpy,Qbpz\n";
+    file_cov << header_cov; 
+
+    VectorComposite state_est;
+    double o_p_ob_carr[3*N];
+    double o_q_b_carr[4*N];
+    double o_v_ob_carr[3*N];
+    double o_p_oc_carr[3*N];
+    double o_v_oc_carr[3*N];
+    double o_Lc_carr[3*N];
+    for (unsigned int i=0; i < N; i++) { 
+        state_est = problem->getState(t_arr[i]);
+        Vector3d i_omg_oi = i_omg_oi_v[i];
+        Vector3d o_p_oi = state_est['P'];
+        Vector4d o_qvec_i = state_est['O'];
+        Vector3d o_v_oi = state_est['V'];
+
+        Vector6d bias_acc_gyro = sen_imu->getStateBlockDynamic('I', t_arr[i])->getState();
+
+        Matrix3d o_R_i = Quaterniond(o_qvec_i).toRotationMatrix();
+        Matrix3d o_R_b = o_R_i * i_R_b;
+        Vector4d o_qvec_b = Quaterniond(o_R_b).coeffs();
+        // std::cout << "o_p_oi:                  " << o_p_oi.transpose() << std::endl;
+        // std::cout << "i_p_ib:                  " << i_p_ib.transpose() << std::endl;
+        // std::cout << "o_R_i * i_p_ib:          " << (o_R_i * i_p_ib).transpose() << std::endl;
+        // std::cout << "o_p_oi + o_R_i * i_p_ib: " << (o_p_oi + o_R_i * i_p_ib).transpose() << std::endl;
+        Vector3d o_p_ob = o_p_oi + o_R_i * i_p_ib;
+        Vector3d o_v_ob = o_v_oi + o_R_b * (i_omg_oi - bias_acc_gyro.tail<3>()).cross(b_p_bi);
+
+        Vector3d o_p_oc = state_est['C'];
+        Vector3d o_v_oc = state_est['D'];
+        Vector3d o_Lc = state_est['L'];
+        
+        mempcpy(o_p_ob_carr+3*i, o_p_ob.data(),   3*sizeof(double));
+        mempcpy(o_q_b_carr +4*i, o_qvec_b.data(), 4*sizeof(double));
+        mempcpy(o_v_ob_carr+3*i, o_v_ob.data(),   3*sizeof(double));
+        mempcpy(o_p_oc_carr+3*i, o_p_oc.data(),   3*sizeof(double));
+        mempcpy(o_v_oc_carr+3*i, o_v_oc.data(),   3*sizeof(double));
+        mempcpy(o_Lc_carr  +3*i, o_Lc.data(),     3*sizeof(double));
+
+        file_est << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << o_p_ob(0) << ","
+                 << o_p_ob(1) << ","
+                 << o_p_ob(2) << "," 
+                 << o_qvec_b(0) << "," 
+                 << o_qvec_b(1) << "," 
+                 << o_qvec_b(2) << "," 
+                 << o_qvec_b(3) << "," 
+                 << o_v_ob(0) << "," 
+                 << o_v_ob(1) << "," 
+                 << o_v_ob(2) << "," 
+
+                 << o_p_oc(0) << ","
+                 << o_p_oc(1) << ","
+                 << o_p_oc(2) << "," 
+                 << o_v_oc(0) << "," 
+                 << o_v_oc(1) << "," 
+                 << o_v_oc(2) << "," 
+                 << o_Lc(0) << "," 
+                 << o_Lc(1) << "," 
+                 << o_Lc(2)
+                 << "\n"; 
+    }
+
+
+    VectorComposite kf_state;
+    CaptureBasePtr cap_ikin;
+    VectorComposite bp_bias_est;
+    CaptureBasePtr cap_imu;
+    VectorComposite bi_bias_est;
+    for (auto& elt: problem->getTrajectory()->getFrameMap()){
+        auto kf = elt.second;
+        kf_state = kf->getState();
+        cap_ikin = kf->getCaptureOf(sen_ikin); 
+        bp_bias_est = cap_ikin->getState();
+        cap_imu = kf->getCaptureOf(sen_imu); 
+        bi_bias_est = cap_imu->getState();
+
+        file_kfs << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                    << kf->getTimeStamp().get() << ","
+                    << kf_state['P'](0) << ","
+                    << kf_state['P'](1) << ","
+                    << kf_state['P'](2) << "," 
+                    << kf_state['O'](0) << "," 
+                    << kf_state['O'](1) << "," 
+                    << kf_state['O'](2) << "," 
+                    << kf_state['O'](3) << "," 
+                    << kf_state['V'](0) << "," 
+                    << kf_state['V'](1) << "," 
+                    << kf_state['V'](2) << "," 
+                    << bi_bias_est['I'](0) << ","
+                    << bi_bias_est['I'](1) << ","
+                    << bi_bias_est['I'](2) << ","
+                    << bi_bias_est['I'](3) << ","
+                    << bi_bias_est['I'](4) << ","
+                    << bi_bias_est['I'](5) << ","
+                    << kf_state['C'](0) << "," 
+                    << kf_state['C'](1) << "," 
+                    << kf_state['C'](2) << "," 
+                    << kf_state['D'](0) << "," 
+                    << kf_state['D'](1) << "," 
+                    << kf_state['D'](2) << "," 
+                    << kf_state['L'](0) << "," 
+                    << kf_state['L'](1) << "," 
+                    << kf_state['L'](2) << ","
+                    << bp_bias_est['I'](0) << ","
+                    << bp_bias_est['I'](1) << ","
+                    << bp_bias_est['I'](2)
+                    << "\n"; 
+
+        // compute covariances of KF and capture stateblocks
+        Eigen::MatrixXd Qp =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qo =  Eigen::Matrix3d::Identity();  // global or local?
+        Eigen::MatrixXd Qv =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbi =  Eigen::Matrix6d::Identity();
+        Eigen::MatrixXd Qc =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qd =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd QL =  Eigen::Matrix3d::Identity();
+        Eigen::MatrixXd Qbp = Eigen::Matrix3d::Identity();
+        // solver->computeCovariances(kf->getStateBlockVec());     // !! does not work as intended...
+        
+        solver->computeCovariances(SolverManager::CovarianceBlocksToBeComputed::ALL);  // should not be computed every time !! see below
+        problem->getCovarianceBlock(kf->getStateBlock('P'), Qp);
+        problem->getCovarianceBlock(kf->getStateBlock('O'), Qo);
+        problem->getCovarianceBlock(kf->getStateBlock('V'), Qv);
+        problem->getCovarianceBlock(kf->getStateBlock('C'), Qc);
+        problem->getCovarianceBlock(kf->getStateBlock('D'), Qd);
+        problem->getCovarianceBlock(kf->getStateBlock('L'), QL);
+
+        solver->computeCovariances(cap_ikin->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_ikin->getSensorIntrinsic(), Qbp);
+        // std::cout << "Qbp\n" << Qbp << std::endl;
+        solver->computeCovariances(cap_imu->getStateBlockVec());  // not computed by ALL and destroys other computed covs -> issue to raise
+        problem->getCovarianceBlock(cap_imu->getSensorIntrinsic(), Qbi);
+
+        file_cov << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                        << kf->getTimeStamp().get() << ","
+                        << Qp(0,0) << ","
+                        << Qp(1,1) << ","
+                        << Qp(2,2) << ","
+                        << Qo(0,0) << ","
+                        << Qo(1,1) << ","
+                        << Qo(2,2) << ","
+                        << Qv(0,0) << ","
+                        << Qv(1,1) << ","
+                        << Qv(2,2) << ","
+                        << Qbi(0,0) << ","
+                        << Qbi(1,1) << ","
+                        << Qbi(2,2) << ","
+                        << Qbi(3,3) << ","
+                        << Qbi(4,4) << ","
+                        << Qbi(5,5) << ","
+                        << Qc(0,0) << ","
+                        << Qc(1,1) << ","
+                        << Qc(2,2) << ","
+                        << Qd(0,0) << ","
+                        << Qd(1,1) << ","
+                        << Qd(2,2) << ","
+                        << QL(0,0) << ","
+                        << QL(1,1) << ","
+                        << QL(2,2) << ","
+                        << Qbp(0,0) << ","
+                        << Qbp(1,1) << ","
+                        << Qbp(2,2)
+                        << "\n"; 
+    }
+
+    for (unsigned int i=0; i < N; i++) { 
+        file_fbk << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
+                 << t_arr[i] << ","
+                 << p_ob_fbk_v[i](0) << ","
+                 << p_ob_fbk_v[i](1) << ","
+                 << p_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](0) << "," 
+                 << q_ob_fbk_v[i](1) << "," 
+                 << q_ob_fbk_v[i](2) << "," 
+                 << q_ob_fbk_v[i](3) << "," 
+                 << v_ob_fbk_v[i](0) << "," 
+                 << v_ob_fbk_v[i](1) << "," 
+                 << v_ob_fbk_v[i](2) << ","
+                 << c_ob_fbk_v[i](0) << "," 
+                 << c_ob_fbk_v[i](1) << "," 
+                 << c_ob_fbk_v[i](2) << "," 
+                 << cd_ob_fbk_v[i](0) << "," 
+                 << cd_ob_fbk_v[i](1) << "," 
+                 << cd_ob_fbk_v[i](2) << "," 
+                 << Lc_ob_fbk_v[i](0) << "," 
+                 << Lc_ob_fbk_v[i](1) << "," 
+                 << Lc_ob_fbk_v[i](2)
+                 << "\n";    
+    }
+
+    file_est.close();
+    file_kfs.close();
+    file_cov.close();
+
+
+    // Save trajectories in npz file
+    // save ground truth
+    cnpy::npz_save(out_npz_file_path, "t", t_arr, {N}, "w");             // "w" overwrites any file with same name
+    cnpy::npz_save(out_npz_file_path, "w_p_wm", w_p_wm_arr, {N,3}, "a"); // "a" appends to the file we created above
+    cnpy::npz_save(out_npz_file_path, "w_q_m",  w_q_m_arr,  {N,4}, "a"); 
+    cnpy::npz_save(out_npz_file_path, "w_v_wm", w_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "m_v_wm", m_v_wm_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_i", o_q_i_arr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "qa", qa_arr, {N,12}, "a");
+
+    // save estimates
+    cnpy::npz_save(out_npz_file_path, "o_p_ob", o_p_ob_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_q_b",  o_q_b_carr,  {N,4}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_ob", o_v_ob_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_p_oc", o_p_oc_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_v_oc", o_v_oc_carr, {N,3}, "a");
+    cnpy::npz_save(out_npz_file_path, "o_Lc",   o_Lc_carr,   {N,3}, "a");
+
+}
diff --git a/demos/tree_manager.yaml b/demos/tree_manager.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ceb0dd77261e7361210a690b2ba563aaa58e7779
--- /dev/null
+++ b/demos/tree_manager.yaml
@@ -0,0 +1,7 @@
+config:
+  problem:
+    tree_manager:
+      n_fix_first_frames: 3
+      n_frames: 100000000000
+      type: TreeManagerSlidingWindow
+      viral_remove_empty_parent: true
diff --git a/include/bodydynamics/capture/capture_force_torque_preint.h b/include/bodydynamics/capture/capture_force_torque_preint.h
new file mode 100644
index 0000000000000000000000000000000000000000..12f4eb58c9e2a8b8d89e7923bea393e93bae48a5
--- /dev/null
+++ b/include/bodydynamics/capture/capture_force_torque_preint.h
@@ -0,0 +1,75 @@
+//--------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--------
+#ifndef CAPTURE_FORCE_TORQUE_PREINT_H
+#define CAPTURE_FORCE_TORQUE_PREINT_H
+
+//Wolf includes
+#include "bodydynamics/math/force_torque_delta_tools.h"
+#include "core/capture/capture_base.h"
+#include "core/capture/capture_motion.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+
+#include <core/state_block/state_composite.h>
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(CaptureForceTorquePreint);
+
+class CaptureForceTorquePreint : public CaptureMotion
+{
+    public:
+        /* 
+        capture _data
+        name, size: description
+        f1  , 3 : force of foot 1 in foot 1 frame
+        f2  , 3 : force of foot 2 in foot 2 frame
+        tau1, 3 : torque of foot 1 in foot 1 frame
+        tau2, 3 : torque of foot 2 in foot 2 frame
+        pbc , 3 : position of COM relative to the base in base
+        wb  , 3 : angular velocity of body frame in body frame 
+        pbl1, 3 : position of foot 1 relative to the base in base frame
+        pbl2, 3 : position of foot 2 relative to the base in base frame
+        qbl1, 4 : orientation of foot 1 in base frame
+        qbl2, 4 : orientation of foot 2 in base frame
+        */
+        CaptureForceTorquePreint(
+                    const TimeStamp& _init_ts,
+                    SensorBasePtr _sensor_ptr,
+                    CaptureInertialKinematicsPtr _capture_IK_ptr,  // to get the pbc bias
+                    CaptureMotionPtr _capture_motion_ptr,          // to get the gyro bias
+                    const Eigen::VectorXd& _data,
+                    const Eigen::MatrixXd& _data_cov,
+                    CaptureBasePtr _capture_origin_ptr = nullptr);
+    
+        ~CaptureForceTorquePreint() override;
+
+        CaptureBasePtr getIkinCaptureOther(){ return cap_ikin_other_; }
+        CaptureBasePtr getGyroCaptureOther(){ return cap_gyro_other_;}
+
+    private:
+        CaptureBasePtr cap_ikin_other_;
+        CaptureBasePtr cap_gyro_other_;
+};
+
+} // namespace wolf
+
+#endif // CAPTURE_FORCE_TORQUE_PREINT_H
diff --git a/include/bodydynamics/capture/capture_inertial_kinematics.h b/include/bodydynamics/capture/capture_inertial_kinematics.h
new file mode 100644
index 0000000000000000000000000000000000000000..70baff8f311f6fc17d19305559c959d62ef079a2
--- /dev/null
+++ b/include/bodydynamics/capture/capture_inertial_kinematics.h
@@ -0,0 +1,64 @@
+//--------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--------
+#ifndef CAPTURE_INERTIAL_KINEMATICS_H
+#define CAPTURE_INERTIAL_KINEMATICS_H
+
+//Wolf includes
+#include "bodydynamics/math/force_torque_delta_tools.h"
+#include "core/capture/capture_motion.h"
+#include "core/capture/capture_base.h"
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(CaptureInertialKinematics);
+
+class CaptureInertialKinematics : public CaptureBase
+{
+    public:
+
+        CaptureInertialKinematics(const TimeStamp& _init_ts,
+                                  SensorBasePtr _sensor_ptr,
+                                  const Eigen::Vector9d& _data,    // pbc, vbc, wb
+                                  const Eigen::Matrix3d & _B_I_q,  // Centroidal inertia matrix expressed in body frame
+                                  const Eigen::Vector3d & _B_Lc_m, // Centroidal angular momentum expressed in body frame
+                                  const Vector3d& _bias);
+
+        CaptureInertialKinematics(const TimeStamp& _init_ts,
+                                  SensorBasePtr _sensor_ptr,
+                                  const Eigen::Vector9d& _data,     // pbc, vbc, wb
+                                  const Eigen::Matrix3d & _B_I_q,   // Centroidal inertia matrix expressed in body frame
+                                  const Eigen::Vector3d & _B_Lc_m); // Centroidal angular momentum expressed in body frame
+    
+        ~CaptureInertialKinematics() override;
+
+        const Eigen::Vector9d& getData()    {return data_;}
+        const Eigen::Matrix3d& getBIq()     {return B_I_q_;}
+        const Eigen::Vector3d& getBLcm()    {return B_Lc_m_;}
+
+    private:
+        Eigen::Vector9d data_;
+        Eigen::Matrix3d B_I_q_;
+        Eigen::Vector3d B_Lc_m_;
+};
+} // namespace wolf
+
+#endif // CAPTURE_INERTIAL_KINEMATICS_H
diff --git a/include/bodydynamics/capture/capture_leg_odom.h b/include/bodydynamics/capture/capture_leg_odom.h
new file mode 100644
index 0000000000000000000000000000000000000000..b1d8652e0f3f9bd8c0dbbb001bc10714fa3cf8b9
--- /dev/null
+++ b/include/bodydynamics/capture/capture_leg_odom.h
@@ -0,0 +1,74 @@
+//--------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--------
+/**
+ * \file gtest_capture_leg_odom.h
+ *
+ *  Created on: May 12, 2020
+ *      \author: mfourmy
+ */
+
+
+#ifndef CAPTURE_LEG_ODOM_H
+#define CAPTURE_LEG_ODOM_H
+
+//Wolf includes
+#include "core/capture/capture_base.h"
+#include "core/capture/capture_motion.h"
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(CaptureLegOdom);
+
+typedef enum
+{
+    POINT_FEET_RELATIVE_KIN,
+    POINT_FEET_DIFFERENTIAL_KIN,
+    HUMANOID_FEET_RELATIVE_KIN
+} CaptureLegOdomType;
+
+
+class CaptureLegOdom : public CaptureMotion
+{
+    public:
+        /* 
+        capture _data
+        */
+        CaptureLegOdom(
+                    const TimeStamp& _init_ts,
+                    SensorBasePtr _sensor_ptr,
+                    const Eigen::MatrixXd& _data_kin,
+                    Eigen::Matrix6d _data_cov, // for the moment only 6x6 mat, see if more involved computations are necessary (contact confidence...)
+                    double dt,
+                    CaptureLegOdomType lo_type); 
+    
+        ~CaptureLegOdom() override;
+
+};
+
+// inline Eigen::VectorXd CaptureLegOdom::correctDelta(const VectorXd& _delta, const VectorXd& _delta_error_biases) const
+// {
+//     return 
+// }
+
+} // namespace wolf
+
+#endif // CAPTURE_LEG_ODOM_H
diff --git a/include/bodydynamics/capture/capture_point_feet_nomove.h b/include/bodydynamics/capture/capture_point_feet_nomove.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e57c0cdd71d3cc2cd85f196a51e16df57dce844
--- /dev/null
+++ b/include/bodydynamics/capture/capture_point_feet_nomove.h
@@ -0,0 +1,56 @@
+//--------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--------
+/**
+ *
+ *  Created on: Oct 23, 2020
+ *      \author: mfourmy
+ */
+
+
+#ifndef CAPTURE_POINT_FEET_NOMOVE_H
+#define CAPTURE_POINT_FEET_NOMOVE_H
+
+//Wolf includes
+#include "core/capture/capture_base.h"
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(CapturePointFeetNomove);
+
+class CapturePointFeetNomove : public CaptureBase
+{
+    public:
+        CapturePointFeetNomove(
+                    const TimeStamp& _init_ts,
+                    SensorBasePtr _sensor_ptr,
+                    const std::unordered_map<int, Eigen::Vector3d>& kin_incontact
+                    ); 
+    
+        ~CapturePointFeetNomove() override;
+        
+        // <foot in contact number (0,1,2,3), b_p_bf>
+        std::unordered_map<int, Eigen::Vector3d> kin_incontact_;
+};
+
+} // namespace wolf
+
+#endif // CAPTURE_POINT_FEET_NOMOVE_H
diff --git a/include/bodydynamics/factor/factor_force_torque.h b/include/bodydynamics/factor/factor_force_torque.h
index 305669b8d8f661412589118e7fad7ca92fe5c351..85d74de654cfd2976eae06ab640086fec74870aa 100644
--- a/include/bodydynamics/factor/factor_force_torque.h
+++ b/include/bodydynamics/factor/factor_force_torque.h
@@ -1,3 +1,24 @@
+//--------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--------
 /**
  * \file factor_force_torque.h
  *
@@ -12,9 +33,11 @@
 #ifndef FACTOR_FORCE_TORQUE_H_
 #define FACTOR_FORCE_TORQUE_H_
 
-#include "core/math/rotations.h"
-#include "core/factor/factor_autodiff.h"
-#include "core/feature/feature_base.h"
+#include <core/math/rotations.h>
+#include <core/math/covariance.h>
+#include <core/factor/factor_autodiff.h>
+#include <core/feature/feature_base.h>
+
 #include "bodydynamics/feature/feature_force_torque.h"
 
 namespace wolf
@@ -27,16 +50,12 @@ class FactorForceTorque : public FactorAutodiff<FactorForceTorque, 9, 3,3,3, 3,3
     public:
         FactorForceTorque(const FeatureBasePtr&   _feat,
                           const FrameBasePtr&     _frame_other,
+                          const StateBlockPtr&    _sb_bp_other,
                           const ProcessorBasePtr& _processor_ptr,
                           bool                    _apply_loss_function,
                           FactorStatus            _status = FAC_ACTIVE);
 
-        virtual ~FactorForceTorque() { /* nothing */ }
-
-        virtual std::string getTopology() const override
-        {
-            return std::string("GEOM");
-        }
+        ~FactorForceTorque() override { /* nothing */ }
 
        /*
         _ck   : COM position in world frame, time k
@@ -71,12 +90,13 @@ class FactorForceTorque : public FactorAutodiff<FactorForceTorque, 9, 3,3,3, 3,3
         //                   const Eigen::VectorXd& _bp, 
         //                   Eigen::Matrix<double, 9, 15>& _J_ny_nz) const;
 
-        void computeMeasurementCovariance(const FeatureForceTorquePtr& feat, Eigen::Matrix<double,15,15>& cov);
+        void retrieveMeasurementCovariance(const FeatureForceTorquePtr& feat, Eigen::Matrix<double,15,15>& cov);
 
+        StateBlockPtr sb_bp_other_;
         double mass_;
         double dt_;
         Eigen::Matrix<double,15,15> cov_meas_;
-        // Eigen::Matrix<double, 9, 15> J_ny_nz_;
+        // Eigen::Matrix<double, 9, 15> J_ny_nz_;  // cannot be modified in operator() since const function 
         // Eigen::Matrix9d errCov_;
 };
 
@@ -88,43 +108,43 @@ namespace wolf {
 FactorForceTorque::FactorForceTorque(
                             const FeatureBasePtr&   _feat,
                             const FrameBasePtr&     _frame_other,
+                            const StateBlockPtr&    _sb_bp_other,
                             const ProcessorBasePtr& _processor_ptr,
                             bool                    _apply_loss_function,
                             FactorStatus            _status) :
     FactorAutodiff("FactorForceTorque",
+                   TOP_GEOM,
+                   _feat,
                     _frame_other,              // _frame_other_ptr
-                    nullptr,              // _capture_other_ptr
-                    nullptr,              // _feature_other_ptr
-                    nullptr,              // _landmark_other_ptr
+                    nullptr,                   // _capture_other_ptr
+                    nullptr,                   // _feature_other_ptr
+                    nullptr,                   // _landmark_other_ptr
                     _processor_ptr,
                     _apply_loss_function,
                     _status,
-                    _feat->getFrame()->getStateBlock("C"),  // COM position
-                    _feat->getFrame()->getStateBlock("D"),  // COM velocity (bad name)
-                    _feat->getFrame()->getStateBlock("L"),  // Angular momentum
-                    _frame_other->getStateBlock("C"),  // COM position
-                    _frame_other->getStateBlock("D"),  // COM velocity (bad name)
-                    _frame_other->getStateBlock("L"),  // Angular momentum
-                    _frame_other->getStateBlock("B"),  // BC relative position bias (bad name)
-                    _frame_other->getStateBlock("O")   // Base orientation
-    )
+                    _feat->getFrame()->getStateBlock('C'),  // COM position, current
+                    _feat->getFrame()->getStateBlock('D'),  // COM velocity (bad name), current
+                    _feat->getFrame()->getStateBlock('L'),  // Angular momentum, current
+                    _frame_other->getStateBlock('C'),       // COM position, previous
+                    _frame_other->getStateBlock('D'),       // COM velocity (bad name), previous
+                    _frame_other->getStateBlock('L'),       // Angular momentum, previous
+                    _sb_bp_other,  // BC relative position bias, previous
+                    _frame_other->getStateBlock('O')        // Base orientation, previous
+    ),
+    sb_bp_other_(_sb_bp_other)
 {
     FeatureForceTorquePtr feat = std::static_pointer_cast<FeatureForceTorque>(_feat);
     mass_ = feat->getMass();
     dt_   = feat->getDt();
-    computeMeasurementCovariance(feat, cov_meas_);
-    // Eigen::Matrix<double, 9, 15> J_ny_nz; J_ny_nz.setZero();
-    // computeJac(feat, mass_, dt_, _feat->getFrame()->getStateBlock("B")->getState(), J_ny_nz);
-    // std::cout << cov_meas << std::endl;
-    // errCov_ = J_ny_nz_ * cov_meas * J_ny_nz_.transpose(); // not necessary
+    retrieveMeasurementCovariance(feat, cov_meas_);
 }
 
 
-void FactorForceTorque::computeMeasurementCovariance(const FeatureForceTorquePtr& feat, Eigen::Matrix<double,15,15>& cov)
+void FactorForceTorque::retrieveMeasurementCovariance(const FeatureForceTorquePtr& feat, Eigen::Matrix<double,15,15>& cov)
 {
     cov.setZero();
-    cov.block<6,6>(0,0) = feat->getCovForcesMeas();  // reset some extra zeros
-    cov.block<6,6>(6,6) = feat->getCovTorquesMeas();  // reset some extra zeros
+    cov.block<6,6>(0,0) =   feat->getCovForcesMeas();  // reset some extra zeros
+    cov.block<6,6>(6,6) =   feat->getCovTorquesMeas();  // reset some extra zeros
     cov.block<3,3>(12,12) = feat->getCovPbcMeas();
 }
 
@@ -232,10 +252,8 @@ bool FactorForceTorque::operator () (
 
     // Recompute the error variable covariance according to the new bias bp estimation
 
-    // computeJac(feat, mass_, dt_, feat->getFrame()->getStateBlock("B")->getState(), J_ny_nz_);
-    // recomputeJac(feat, dt_, getFeature()->getFrame()->getStateBlock("B")->getState(), J_ny_nz_);
-    Eigen::Matrix<double, 9, 15> J_ny_nz; J_ny_nz.setZero();
-    computeJac(feat, mass_, dt_, feat->getFrame()->getStateBlock("B")->getState(), J_ny_nz);
+    Eigen::Matrix<double, 9, 15> J_ny_nz;
+    computeJac(feat, mass_, dt_, sb_bp_other_->getState(), J_ny_nz);  // bp
     Eigen::Matrix9d errCov = J_ny_nz * cov_meas_ * J_ny_nz.transpose();
     errCov.block<6,6>(0,0) = errCov.block<6,6>(0,0) + 1e-12 * Eigen::Matrix6d::Identity();
 
@@ -250,33 +268,12 @@ bool FactorForceTorque::operator () (
 
     err_cd = qkm.conjugate()*(cdk - cdkm - wolf::gravity()*dt_)
            - (1/mass_) * (bql1 * f1 * dt_ + bql2 * f2 * dt_);
-
-    // std::cout << "err_cd.conjugate()\n" << err_cd << std::endl;
-    // std::cout << "qkm.conjugate()\n" << qkm.coeffs().conjugate() << std::endl;
-    // std::cout << "cdk.conjugate()\n" << ck.conjugate() << std::endl;
-    // std::cout << "cdkm.conjugate()\n" << ckm.conjugate() << std::endl;
-    // std::cout << "wolf::gravity()*dt_\n" << (wolf::gravity()*dt_).conjugate() << std::endl;
-    // std::cout << "(1/mass_) * (bql1 * f1 * dt_ + bql2 * f2 * dt_)\n" << ((1/mass_) * (bql1 * f1 * dt_ + bql2 * f2 * dt_)).conjugate() << std::endl;
-    // std::cout << "mass_\n" << mass_ << std::endl;
-    // std::cout << "dt_\n" << dt_ << std::endl;
-    // std::cout << "wolf::gravity()\n" << wolf::gravity() << std::endl;
-
     
     err_Lc = qkm.conjugate()*(Lck - Lckm)
            - (  bql1 * tau1 + (pbl1 - pbc + bpkm).cross(bql1 * f1) 
               + bql2 * tau2 + (pbl2 - pbc + bpkm).cross(bql2 * f2));
-
-    // std::cout << "\n\n\ncov_meas_\n" << cov_meas_ << std::endl;
-    // std::cout << "\n\n\nerrCov\n" << errCov << std::endl;
-    // std::cout << "\n\n\nerrCov.inverse()\n" << errCov.inverse() << std::endl;
-    // Eigen::FullPivLU<Eigen::Matrix<double,9,15>> lu_decompJ_ny_nz(J_ny_nz);
-    // std::cout << "\n\nJ_ny_nz rank: " << lu_decompJ_ny_nz.rank() << std::endl;
-    // Eigen::FullPivLU<Eigen::Matrix9d> lu_decomperrCov(errCov);
-    // std::cout << "errCov rank: " << lu_decomperrCov.rank() << std::endl;
-    
-    
     
-    res = feat->computeSqrtUpper(errCov.inverse()) * err;
+    res = wolf::computeSqrtUpper(errCov.inverse()) * err;
 
     return true;
 }
diff --git a/include/bodydynamics/factor/factor_force_torque_preint.h b/include/bodydynamics/factor/factor_force_torque_preint.h
new file mode 100644
index 0000000000000000000000000000000000000000..841bb63d6f94ffa9dc4ec115e5f4a3c3dd06d20a
--- /dev/null
+++ b/include/bodydynamics/factor/factor_force_torque_preint.h
@@ -0,0 +1,343 @@
+//--------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--------
+#ifndef FACTOR_FORCE_TORQUE_PREINT_THETA_H_
+#define FACTOR_FORCE_TORQUE_PREINT_THETA_H_
+
+//Wolf includes
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/feature/feature_force_torque_preint.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "core/factor/factor_autodiff.h"
+#include "core/math/rotations.h"
+
+//Eigen include
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(FactorForceTorquePreint);
+
+//class
+class FactorForceTorquePreint : public FactorAutodiff<FactorForceTorquePreint, 12, 3,3,3,4,3,6, 3,3,3,4>
+{
+    public:
+        FactorForceTorquePreint(const FeatureForceTorquePreintPtr& _ftr_ptr,
+                                const CaptureForceTorquePreintPtr& _cap_origin_ptr, // gets to bp1, bw1
+                                const ProcessorBasePtr&            _processor_ptr,
+                                const CaptureBasePtr&               _cap_ikin_other,
+                                const CaptureBasePtr&               _cap_gyro_other,
+                                bool                               _apply_loss_function,
+                                FactorStatus                       _status = FAC_ACTIVE);
+
+        ~FactorForceTorquePreint() override = default;
+
+        /** \brief : compute the residual from the state blocks being iterated by the solver.
+            -> computes the expected measurement
+            -> corrects actual measurement with new bias
+            -> compares the corrected measurement with the expected one
+            -> weights the result with the covariance of the noise (residual = sqrt_info_matrix * err;)
+        */
+        template<typename T>
+        bool operator ()(const T* const _c1,
+                         const T* const _cd1,
+                         const T* const _Lc1,
+                         const T* const _q1,
+                         const T* const _bp1,
+                         const T* const _bw1,
+                         const T* const _c2,
+                         const T* const _cd2,
+                         const T* const _Lc2,
+                         const T* const _q2,
+                         T* _res) const;
+
+        /** \brief : compute the residual from the state blocks being iterated by the solver. (same as operator())
+            -> computes the expected measurement
+            -> corrects actual measurement with new bias
+            -> compares the corrected measurement with the expected one
+            -> weights the result with the covariance of the noise (residual = sqrt_info_matrix * err;)
+         * params :
+         * _c1 : COM position at time t1 in world frame
+         * _cd1: COM velocity at time t1 in world frame
+         * _Lc1: Angular momentum at time t1 in world frame
+         * _q1 : Base orientation at time t1
+         * _bp1: COM position measurement  at time t1 in body frame
+         * _bw1: gyroscope bias at time t1 in body frame 
+         * _c2 : COM position at time t2 in world frame
+         * _cd2: COM velocity at time t2 in world frame
+         * _Lc2: Angular momentum at time t2 in world frame
+         * _q2 : Base orientation at time t2
+         * _res: Factor residuals (c,cd,Lc,o) O is rotation vector... NOT A QUATERNION
+        */
+        template<typename D1, typename D2, typename D3, typename D4>
+        bool residual(const MatrixBase<D1> &     _c1,
+                      const MatrixBase<D1> &     _cd1,
+                      const MatrixBase<D1> &     _Lc1,
+                      const QuaternionBase<D2> & _q1,
+                      const MatrixBase<D1> &     _bp1,
+                      const MatrixBase<D1> &     _bw1,
+                      const MatrixBase<D1> &     _c2,
+                      const MatrixBase<D1> &     _cd2,
+                      const MatrixBase<D1> &     _Lc2,
+                      const QuaternionBase<D3> & _q2,
+                      MatrixBase<D4> &           _res) const;
+
+        // Matrix<double,12,1> residual() const;
+        // double cost() const;
+
+    private:
+        /// Preintegrated delta
+        Vector3d    dc_preint_;
+        Vector3d    dcd_preint_;
+        Vector3d    dLc_preint_;
+        Quaterniond dq_preint_;
+
+        // Biases used during preintegration
+        Vector3d pbc_bias_preint_;
+        Vector3d gyro_bias_preint_;
+
+        // Jacobians of preintegrated deltas wrt biases
+        Matrix3d J_dLc_pb_;
+        Matrix3d J_dc_wb_;
+        Matrix3d J_dcd_wb_;
+        Matrix3d J_dLc_wb_;
+        Matrix3d J_dq_wb_;
+
+        /// Metrics
+        double dt_; ///< delta-time and delta-time-squared between keyframes
+        double mass_; ///< constant total robot mass
+        
+};
+
+///////////////////// IMPLEMENTATION ////////////////////////////
+
+inline FactorForceTorquePreint::FactorForceTorquePreint(
+                            const FeatureForceTorquePreintPtr& _ftr_ptr,
+                            const CaptureForceTorquePreintPtr& _cap_origin_ptr,
+                            const ProcessorBasePtr&            _processor_ptr,
+                            const CaptureBasePtr&              _cap_ikin_other,
+                            const CaptureBasePtr&              _cap_gyro_other,
+                            bool                               _apply_loss_function,
+                            FactorStatus                       _status) :
+                FactorAutodiff<FactorForceTorquePreint, 12, 3,3,3,4,3,6, 3,3,3,4>(
+                        "FactorForceTorquePreint",
+                        TOP_MOTION,
+                        _ftr_ptr,
+                        _cap_origin_ptr->getFrame(),
+                        _cap_origin_ptr,
+                        nullptr,
+                        nullptr,
+                        _processor_ptr,
+                        _apply_loss_function,
+                        _status,
+                        _cap_origin_ptr->getFrame()->getStateBlock('C'),
+                        _cap_origin_ptr->getFrame()->getStateBlock('D'),
+                        _cap_origin_ptr->getFrame()->getStateBlock('L'),
+                        _cap_origin_ptr->getFrame()->getO(),
+                        _cap_ikin_other->getSensorIntrinsic(),
+                        _cap_gyro_other->getSensorIntrinsic(),
+                        _ftr_ptr->getFrame()->getStateBlock('C'),
+                        _ftr_ptr->getFrame()->getStateBlock('D'),
+                        _ftr_ptr->getFrame()->getStateBlock('L'),
+                        _ftr_ptr->getFrame()->getO()
+                        ),
+        dc_preint_(_ftr_ptr->getMeasurement().head<3>()), // dc, dcd, dLc, dq at preintegration time
+        dcd_preint_(_ftr_ptr->getMeasurement().segment<3>(3)),
+        dLc_preint_(_ftr_ptr->getMeasurement().segment<3>(6)),
+        dq_preint_(_ftr_ptr->getMeasurement().data()+9),
+        pbc_bias_preint_( std::static_pointer_cast<FeatureForceTorquePreint>(_ftr_ptr)->getPbcBiasPreint()),
+        gyro_bias_preint_(std::static_pointer_cast<FeatureForceTorquePreint>(_ftr_ptr)->getGyroBiasPreint()),
+        J_dLc_pb_(_ftr_ptr->getJacobianBias().block<3,3>(6,0)), // Jac dLc wrt pbc bias
+        J_dc_wb_ (_ftr_ptr->getJacobianBias().block<3,3>(0,3)), // Jac dc wrt gyro bias
+        J_dcd_wb_(_ftr_ptr->getJacobianBias().block<3,3>(3,3)), // Jac dc wrt gyro bias
+        J_dLc_wb_(_ftr_ptr->getJacobianBias().block<3,3>(6,3)), // Jac dcd wrt gyro bias
+        J_dq_wb_ (_ftr_ptr->getJacobianBias().block<3,3>(9,3)), // Jac dLc wrt gyro bias
+        dt_(_ftr_ptr->getFrame()->getTimeStamp() - _cap_origin_ptr->getFrame()->getTimeStamp()),
+        mass_(std::static_pointer_cast<SensorForceTorque>(_ftr_ptr->getCapture()->getSensor())->getMass())
+{
+//
+}
+
+template<typename T>
+inline bool FactorForceTorquePreint::operator ()(const T* const _c1,
+                                                 const T* const _cd1,
+                                                 const T* const _Lc1,
+                                                 const T* const _q1,
+                                                 const T* const _bp1,
+                                                 const T* const _bw1,
+                                                 const T* const _c2,
+                                                 const T* const _cd2,
+                                                 const T* const _Lc2,
+                                                 const T* const _q2,
+                                                 T* _res) const
+{
+    Map<const Matrix<T,3,1> > c1(_c1);
+    Map<const Matrix<T,3,1> > cd1(_cd1);
+    Map<const Matrix<T,3,1> > Lc1(_Lc1);
+    Map<const Quaternion<T> > q1(_q1);
+    Map<const Matrix<T,3,1> > bp1(_bp1);
+    Map<const Matrix<T,3,1> > bw1(_bw1 + 3);  // bw1 = bimu = [ba, bw]
+    Map<const Matrix<T,3,1> > c2(_c2);
+    Map<const Matrix<T,3,1> > cd2(_cd2);
+    Map<const Matrix<T,3,1> > Lc2(_Lc2);
+    Map<const Quaternion<T> > q2(_q2);
+    Map<Matrix<T,12,1> > res(_res);
+
+    residual(c1, cd1, Lc1, q1, bp1, bw1, c2, cd2, Lc2, q2, res);
+
+    return true;
+}
+
+template<typename D1, typename D2, typename D3, typename D4>
+inline bool FactorForceTorquePreint::residual(const MatrixBase<D1> &     _c1,
+                                              const MatrixBase<D1> &     _cd1,
+                                              const MatrixBase<D1> &     _Lc1,
+                                              const QuaternionBase<D2> & _q1,
+                                              const MatrixBase<D1> &     _bp1,
+                                              const MatrixBase<D1> &     _bw1,
+                                              const MatrixBase<D1> &     _c2,
+                                              const MatrixBase<D1> &     _cd2,
+                                              const MatrixBase<D1> &     _Lc2,
+                                              const QuaternionBase<D3> & _q2,
+                                              MatrixBase<D4> &           _res) const
+{
+    /*  Help for the Imu residual function
+     *
+     *  Notations:
+     *    D_* : a motion in the Delta manifold                           -- implemented as 10-vectors with [Dp, Dq, Dv]
+     *    T_* : a motion difference in the Tangent space to the manifold -- implemented as  9-vectors with [Dp, Do, Dv]
+     *    b*  : a bias
+     *    J*  : a Jacobian matrix
+     *
+     *  We use the following functions from imu_tools.h:
+     *    D  = betweenStates(x1,x2,dt) : obtain a Delta from two states separated dt=t2-t1
+     *    D2 = plus(D1,T)              : plus operator,  D2 = D1 (+) T
+     *    T  = diff(D1,D2)             : minus operator, T  = D2 (-) D1
+     *
+     *  Two methods are possible (select with #define below this note) :
+     *
+     *  Computations common to methods 1 and 2:
+     *    D_exp  = betweenStates(x1,x2,dt)   // Predict delta from states
+     *    T_step = J_preint * (b - b_preint) // compute the delta correction step due to bias change
+     *
+     *  Method 1:
+     *    D_corr = plus(D_preint, T_step)    // correct the pre-integrated delta with correction step due to bias change
+     *    T_err  = diff(D_exp, D_corr)       // compare against expected delta
+     *    res    = W.sqrt * T_err
+     *
+     *   results in:
+     *    res    = W.sqrt * ( diff ( D_exp, plus(D_preint, J_preint * (b - b_preint) ) ) )
+     *
+     *  Method 2:
+     *    T_diff = diff(D_preint, D_exp)     // compare pre-integrated against expected delta
+     *    T_err  = T_diff - T_step           // the difference should match the correction step due to bias change
+     *    res    = W.sqrt * err
+     *
+     *   results in :
+     *    res    = W.sqrt * ( ( diff ( D_preint , D_exp ) ) - J_preint * (b - b_preint) )
+     *
+     * NOTE: See optimization report at the end of this file for comparisons of both methods.
+     */
+
+    //needed typedefs
+    typedef typename D1::Scalar T;
+
+    EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(D4, 12);
+
+    // 1. Expected delta from states
+    Matrix<T, 3, 1 >   dc_exp;
+    Matrix<T, 3, 1 >   dcd_exp;
+    Matrix<T, 3, 1 >   dLc_exp;
+    Quaternion<T>      dq_exp;
+
+    bodydynamics::betweenStates(_c1, _cd1, _Lc1, _q1, _c2, _cd2, _Lc2, _q2, dt_, dc_exp, dcd_exp, dLc_exp, dq_exp);
+
+    // 2. Corrected integrated delta: delta_corr = delta_preint (+) J_bias * (bias_current - bias_preint)
+
+    // 2.a. Compute the delta step in tangent space:   step = J_bias * (bias - bias_preint)
+    Matrix<T, 3, 1> dc_step  =                                          J_dc_wb_ * (_bw1 - gyro_bias_preint_);
+    Matrix<T, 3, 1> dcd_step =                                         J_dcd_wb_ * (_bw1 - gyro_bias_preint_);
+    Matrix<T, 3, 1> dLc_step = J_dLc_pb_ * (_bp1 - pbc_bias_preint_) + J_dLc_wb_ * (_bw1 - gyro_bias_preint_);
+    Matrix<T, 3, 1> do_step  =                                          J_dq_wb_ * (_bw1 - gyro_bias_preint_);
+
+    // 2.b. Add the correction step to the preintegrated delta:    delta_cor = delta_preint (+) step
+    Matrix<T,3,1> dc_correct;
+    Matrix<T,3,1> dcd_correct;
+    Matrix<T,3,1> dLc_correct;
+    Quaternion<T> dq_correct;
+
+    bodydynamics::plus(dc_preint_.cast<T>(), dcd_preint_.cast<T>(), dLc_preint_.cast<T>(), dq_preint_.cast<T>(),
+              dc_step, dcd_step, dLc_step, do_step,
+              dc_correct, dcd_correct, dLc_correct, dq_correct);
+
+    // 3. Delta error in minimal form: D_err = diff(D_exp , D_corr)
+    // Note the Dt here is zero because it's the delta-time between the same time stamps!
+    Matrix<T, 12, 1> d_error;
+    Map<Matrix<T, 3, 1> >   dc_error (d_error.data()    );
+    Map<Matrix<T, 3, 1> >   dcd_error(d_error.data() + 3);
+    Map<Matrix<T, 3, 1> >   dLc_error(d_error.data() + 6);
+    Map<Matrix<T, 3, 1> >   do_error (d_error.data() + 9);
+
+
+    bodydynamics::diff(dc_exp, dcd_exp, dLc_exp, dq_exp,
+              dc_correct, dcd_correct, dLc_correct, dq_correct, 
+              dc_error, dcd_error, dLc_error, do_error);
+
+    _res = getMeasurementSquareRootInformationUpper() * d_error;
+
+    return true;
+}
+
+// Matrix<double,12,1> FactorForceTorquePreint::residual() const
+// {
+//     Matrix<double,12,1> res;
+
+
+//     FrameBasePtr frm_prev = getFrameOther();
+//     FrameBasePtr frm_curr = getFeature()->getFrame();
+
+//     CaptureBaseWPtrList cap_lst = getCaptureOtherList();  // !! not retrieving the rigt captures...
+//     auto cap_ikin_other = cap_lst.front().lock(); 
+//     auto cap_gyro_other = cap_lst.back().lock();
+
+//     Map<const Matrix<double,3,1> > c1( frm_prev->getStateBlock('C')->getState().data());
+//     Map<const Matrix<double,3,1> > cd1(frm_prev->getStateBlock('D')->getState().data());
+//     Map<const Matrix<double,3,1> > Lc1(frm_prev->getStateBlock('L')->getState().data());
+//     Map<const Quaternion<double> > q1( frm_prev->getStateBlock('O')->getState().data());
+//     Map<const Matrix<double,3,1> > bp1(cap_ikin_other->getStateBlock('I')->getState().data());
+//     Map<const Matrix<double,3,1> > bw1(cap_gyro_other->getStateBlock('I')->getState().data() + 3);  // bw1 = bimu = [ba, bw]
+//     Map<const Matrix<double,3,1> > c2 (frm_curr->getStateBlock('C')->getState().data());
+//     Map<const Matrix<double,3,1> > cd2(frm_curr->getStateBlock('D')->getState().data());
+//     Map<const Matrix<double,3,1> > Lc2(frm_curr->getStateBlock('L')->getState().data());
+//     Map<const Quaternion<double> > q2 (frm_curr->getStateBlock('O')->getState().data());
+
+//     residual(c1, cd1, Lc1, q1, bp1, bw1, c2, cd2, Lc2, q2, res);
+
+//     return res;
+// }
+
+// double FactorForceTorquePreint::cost() const
+// {
+//     Matrix<double,12,1> toto = residual();
+// }
+
+} // namespace wolf
+
+#endif
diff --git a/include/bodydynamics/factor/factor_inertial_kinematics.h b/include/bodydynamics/factor/factor_inertial_kinematics.h
index 28e41ee822442d46fd12767bb363590ac3c36223..c0b36d22bdd65b63b575e6d80863ea2eea978405 100644
--- a/include/bodydynamics/factor/factor_inertial_kinematics.h
+++ b/include/bodydynamics/factor/factor_inertial_kinematics.h
@@ -1,3 +1,24 @@
+//--------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--------
 /**
  * \file factor_inertial_kinematics.h
  *
@@ -25,22 +46,21 @@ class FactorInertialKinematics : public FactorAutodiff<FactorInertialKinematics,
                                  bool                    _apply_loss_function,
                                  FactorStatus            _status = FAC_ACTIVE);
 
-        virtual ~FactorInertialKinematics() { /* nothing */ }
+        ~FactorInertialKinematics() override { /* nothing */ }
 
-        virtual std::string getTopology() const override
-        {
-            return std::string("GEOM");
-        }
+        // to keep track of the imu bias, not the standard way to proceed
+        StateBlockPtr sb_bias_imu_;
 
        /*
+        Factor state blocks:
         _pb: W_p_WB -> base position in world frame
         _qb: W_q_B  -> base orientation in world frame
         _vb: W_v_WB -> base velocity in world frame
         _c: W_p_WC -> COM position in world frame
         _cd: W_v_WC -> COM velocity in world frame
-        _Lc: W_Lc   -> angular momentum at the COM in world frame
+        _Lc: W_Lc   -> angular momentum at the COM in body frame
         _bp: B_b_BC -> bias on relative body->COM position kinematic measurement
-        _baw: 6D IMU bias containing [acc bias, gyro bias]
+        _baw: 6d Imu bias containing [acc bias, gyro bias]
         */
         template<typename T>
         bool operator () (
@@ -53,6 +73,10 @@ class FactorInertialKinematics : public FactorAutodiff<FactorInertialKinematics,
             const T* const _bp,
             const T* const _baw,
             T* _res) const;
+
+        Vector9d residual() const;
+        double cost() const;
+
 };
 
 } /* namespace wolf */
@@ -67,6 +91,8 @@ FactorInertialKinematics::FactorInertialKinematics(
                             bool                    _apply_loss_function,
                             FactorStatus            _status) :
     FactorAutodiff("FactorInertialKinematics",
+                   TOP_GEOM,
+                   _feat,
                     nullptr,              // _frame_other_ptr
                     nullptr,              // _capture_other_ptr
                     nullptr,              // _feature_other_ptr
@@ -77,15 +103,37 @@ FactorInertialKinematics::FactorInertialKinematics(
                     _feat->getFrame()->getP(),
                     _feat->getFrame()->getO(),
                     _feat->getFrame()->getV(),
-                    _feat->getFrame()->getStateBlock("C"),  // COM position
-                    _feat->getFrame()->getStateBlock("D"),  // COM velocity (bad name)
-                    _feat->getFrame()->getStateBlock("L"),  // Angular momentum
-                    _feat->getFrame()->getStateBlock("B"),  // BC relative position bias (bad name)
+                    _feat->getFrame()->getStateBlock('C'),  // COM position
+                    _feat->getFrame()->getStateBlock('D'),  // COM velocity (bad name)
+                    _feat->getFrame()->getStateBlock('L'),  // Angular momentum
+                    _feat->getCapture()->getStateBlock('I'),  // body-to-COM relative position bias
                     _sb_bias_imu
-    )
+    ),
+    sb_bias_imu_(_sb_bias_imu)
 {
 }
 
+Vector9d FactorInertialKinematics::residual() const{
+    Vector9d res;
+    double * pb, * qb, * vb, * c, * cd, * Lc, * bp, * baw;
+    pb = getFrame()->getP()->getState().data();
+    qb = getFrame()->getO()->getState().data();
+    vb = getFrame()->getV()->getState().data();
+    c = getFrame()->getStateBlock('C')->getState().data();
+    cd = getFrame()->getStateBlock('D')->getState().data();
+    Lc = getFrame()->getStateBlock('L')->getState().data();
+    bp = getCapture()->getStateBlock('I')->getState().data();
+    baw = sb_bias_imu_->getState().data();
+
+    operator() (pb, qb, vb, c, cd, Lc, bp, baw, res.data());
+    // std::cout << "res: " << res.transpose() << std::endl;
+    return res;
+}
+
+double FactorInertialKinematics::cost() const{
+    return residual().squaredNorm();
+}
+
 template<typename T>
 bool FactorInertialKinematics::operator () (
                     const T* const _pb,
@@ -98,34 +146,34 @@ bool FactorInertialKinematics::operator () (
                     const T* const _baw,
                     T* _res) const
 {
-    FeatureInertialKinematicsPtr feat = std::static_pointer_cast<FeatureInertialKinematics>(getFeature());
-
     // State variables instanciation
-    Eigen::Map<const Eigen::Matrix<T,3,1> > pb(_pb);
-    Eigen::Map<const Eigen::Quaternion<T> > qb(_qb);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > vb(_vb);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > c(_c);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > cd(_cd);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > Lc(_Lc);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > bp(_bp);
-    Eigen::Map<const Eigen::Matrix<T,6,1> > baw(_baw);
-    Eigen::Map<Eigen::Matrix<T,9,1> > res(_res);
+    Map<const Matrix<T,3,1> > pb(_pb);
+    Map<const Quaternion<T> > qb(_qb);
+    Map<const Matrix<T,3,1> > vb(_vb);
+    Map<const Matrix<T,3,1> > c(_c);
+    Map<const Matrix<T,3,1> > cd(_cd);
+    Map<const Matrix<T,3,1> > Lc(_Lc);
+    Map<const Matrix<T,3,1> > bp(_bp);
+    Map<const Matrix<T,6,1> > baw(_baw);
+
+    Map<Matrix<T,9,1> > res(_res);
+
+    FeatureInertialKinematicsPtr feat = std::static_pointer_cast<FeatureInertialKinematics>(getFeature());
 
     // Measurements retrieval
-    Eigen::Map<const Eigen::Vector3d> pBC_m(getMeasurement().data());      // B_p_BC
-    Eigen::Map<const Eigen::Vector3d> vBC_m(getMeasurement().data() + 3);  // B_v_BC
-    Eigen::Map<const Eigen::Vector3d> w_m(getMeasurement().data() + 6);      // B_wB
+    Map<const Vector3d> pBC_m(getMeasurement().data()    );      // B_p_BC
+    Map<const Vector3d> vBC_m(getMeasurement().data() + 3);  // B_v_BC
+    Map<const Vector3d> w_m  (getMeasurement().data() + 6);      // B_wB
 
     // Error variable instanciation
-    Eigen::Matrix<T, 9, 1> err;
-    Eigen::Map<Eigen::Matrix<T, 3, 1> >   err_c(err.data()    );
-    Eigen::Map<Eigen::Matrix<T, 3, 1> >   err_cd(err.data() + 3);
-    Eigen::Map<Eigen::Matrix<T, 3, 1> >   err_Lc(err.data() + 6);
-
-    err_c = pBC_m - (qb.inverse()*(c - pb) + bp);
-    err_cd = vBC_m + (w_m - baw.tail(3)).cross(pBC_m - bp) - qb.inverse()*(cd - vb);
-    // err_Lc = feat->getBIq() * (w_m - baw.tail(3)) + feat->getBLcm() -  qb.inverse()*Lc;
-    err_Lc = feat->getBLcm() - feat->getBIq()*baw.tail(3) - qb.inverse()*Lc;   // w_m is actually used to compute BLcm beforehand
+    Matrix<T, 9, 1> err;
+    Map<Matrix<T, 3, 1> > err_c (err.data()    );
+    Map<Matrix<T, 3, 1> > err_cd(err.data() + 3);
+    Map<Matrix<T, 3, 1> > err_Lc(err.data() + 6);
+
+    err_c  = pBC_m - (qb.conjugate()*(c - pb) + bp);
+    err_cd = vBC_m + (w_m - baw.tail(3)).cross(pBC_m - bp) - qb.conjugate()*(cd - vb);
+    err_Lc = feat->getBIq() * (w_m - baw.tail(3)) + feat->getBLcm() -  qb.conjugate()*Lc;
 
     res = getMeasurementSquareRootInformationUpper() * err;
 
diff --git a/include/bodydynamics/factor/factor_point_feet_nomove.h b/include/bodydynamics/factor/factor_point_feet_nomove.h
new file mode 100644
index 0000000000000000000000000000000000000000..58e7804f43b3c3e1ffe0de8e85711c3f3d595e06
--- /dev/null
+++ b/include/bodydynamics/factor/factor_point_feet_nomove.h
@@ -0,0 +1,166 @@
+//--------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--------
+/**
+ * \file factor_point_feet_nomove.h
+ *
+ *  Created on: Feb 22, 2020
+ *      \author: mfourmy
+ */
+
+#ifndef FACTOR_POINT_FEET_NOMOVE_H_
+#define FACTOR_POINT_FEET_NOMOVE_H_
+
+#include "core/factor/factor_autodiff.h"
+
+
+// namespace
+// {
+
+// constexpr std::size_t RESIDUAL_SIZE     = 3;
+// constexpr std::size_t POSITION_SIZE     = 3;
+// constexpr std::size_t ORIENTATION_SIZE  = 4;
+
+// }
+
+namespace wolf
+{
+
+WOLF_PTR_TYPEDEFS(FactorPointFeetNomove);
+
+class FactorPointFeetNomove : public FactorAutodiff<FactorPointFeetNomove, 
+                                                    3,
+                                                    3,
+                                                    4,
+                                                    3,
+                                                    4
+                                                    >
+{
+    public:
+        FactorPointFeetNomove(const FeatureBasePtr&   _ftr_current_ptr,
+                              const FrameBasePtr&     _frame_past_ptr,
+                              const ProcessorBasePtr& _processor_ptr,
+                              bool                    _apply_loss_function,
+                              FactorStatus            _status = FAC_ACTIVE);
+
+        ~FactorPointFeetNomove() override { /* nothing */ }
+
+       /*
+        Factor state blocks:
+        _pb: W_p_WB -> base position in world frame
+        _qb: W_q_B  -> base orientation in world frame
+        _pbm: W_p_WB -> base position in world frame, previous KF
+        _qbm: W_q_B  -> base orientation in world frame, previous KF
+        */
+        template<typename T>
+        bool operator () (
+            const T* const _pb,
+            const T* const _qb,
+            const T* const _pbm,
+            const T* const _qbm,
+            T* _res) const;
+
+        // Vector9d residual() const;
+        // double cost() const;
+
+};
+
+} /* namespace wolf */
+
+
+namespace wolf {
+
+FactorPointFeetNomove::FactorPointFeetNomove(
+                            const FeatureBasePtr&   _ftr_current_ptr,
+                            const FrameBasePtr&     _frame_past_ptr,
+                            const ProcessorBasePtr& _processor_ptr,
+                            bool                    _apply_loss_function,
+                            FactorStatus            _status) :
+    FactorAutodiff("FactorPointFeetNomove",
+                   TOP_GEOM,
+                   _ftr_current_ptr,
+                    _frame_past_ptr,      // _frame_other_ptr
+                    nullptr,              // _capture_other_ptr
+                    nullptr,              // _feature_other_ptr
+                    nullptr,              // _landmark_other_ptr
+                    _processor_ptr,
+                    _apply_loss_function,
+                    _status,
+                    _ftr_current_ptr->getFrame()->getP(),
+                    _ftr_current_ptr->getFrame()->getO(),
+                    _frame_past_ptr->getP(),
+                    _frame_past_ptr->getO()
+    )
+{
+}
+
+// Vector9d FactorPointFeetNomove::residual() const{
+//     Vector9d res;
+//     double * pb, * qb, * vb, * c, * cd, * Lc, * bp, * baw;
+//     pb = getFrame()->getP()->getState().data();
+//     qb = getFrame()->getO()->getState().data();
+//     vb = getFrame()->getV()->getState().data();
+//     c = getFrame()->getStateBlock('C')->getState().data();
+//     cd = getFrame()->getStateBlock('D')->getState().data();
+//     Lc = getFrame()->getStateBlock('L')->getState().data();
+//     bp = getCapture()->getStateBlock('I')->getState().data();
+//     baw = sb_bias_imu_->getState().data();
+
+//     operator() (pb, qb, vb, c, cd, Lc, bp, baw, res.data());
+//     // std::cout << "res: " << res.transpose() << std::endl;
+//     return res;
+// }
+
+// double FactorPointFeetNomove::cost() const{
+//     return residual().squaredNorm();
+// }
+
+template<typename T>
+bool FactorPointFeetNomove::operator () (
+                    const T* const _pb,
+                    const T* const _qb,
+                    const T* const _pbm,
+                    const T* const _qbm,
+                    T* _res) const
+{
+    Map<Matrix<T,3,1> > res(_res);
+
+    // State variables instanciation
+    Map<const Matrix<T,3,1> > pb(_pb);
+    Map<const Quaternion<T> > qb(_qb);
+    Map<const Matrix<T,3,1> > pbm(_pbm);
+    Map<const Quaternion<T> > qbm(_qbm);
+
+    // Measurements retrieval
+    Eigen::Matrix<T,6,1> meas = getMeasurement().cast<T>();
+    Matrix<T,3,1> bm_p_bml = meas.topRows(3);    // Bprev_p_BprevL
+    Matrix<T,3,1> b_p_bl   = meas.bottomRows(3); // B_p_BL
+
+    Matrix<T,3,1> err = (pbm + qbm*bm_p_bml) - (pb + qb*b_p_bl);
+
+    res = getMeasurementSquareRootInformationUpper() * err;
+
+    return true;
+}
+
+} // namespace wolf
+
+#endif /* FACTOR__POINT_FEET_NOMOVE_H_ */
diff --git a/include/bodydynamics/feature/feature_force_torque.h b/include/bodydynamics/feature/feature_force_torque.h
index b2434de9b82691a2990765d5a45f26563756f5fd..bbe2fda19d7fcdbde4e88aa564efc3547a6293ef 100644
--- a/include/bodydynamics/feature/feature_force_torque.h
+++ b/include/bodydynamics/feature/feature_force_torque.h
@@ -1,3 +1,24 @@
+//--------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--------
 #ifndef FEATURE_FORCE_TORQUE_H_
 #define FEATURE_FORCE_TORQUE_H_
 
@@ -27,7 +48,7 @@ class FeatureForceTorque : public FeatureBase
             const Eigen::Matrix<double,14,14>& _cov_kin_meas = Eigen::Matrix<double,14,14>::Zero()
             );
 
-        virtual ~FeatureForceTorque();
+        ~FeatureForceTorque() override;
 
         const double & getDt(){return dt_;}
         const double & getMass(){return mass_;}
@@ -50,7 +71,7 @@ class FeatureForceTorque : public FeatureBase
         void setCovForcesMeas(const Eigen::Matrix6d& _cov_forces_meas){cov_forces_meas_ = _cov_forces_meas;}
         void setCovTorquesMeas(const Eigen::Matrix6d& _cov_torques_meas){cov_torques_meas_ = _cov_torques_meas;}
         void setCovPbcMeas(const Eigen::Matrix3d& _cov_pbc_meas){cov_pbc_meas_ = _cov_pbc_meas;}
-        void getCovKinMeas(const Eigen::Matrix<double,14,14>& _cov_kin_meas){cov_kin_meas_ = _cov_kin_meas;}
+        void setCovKinMeas(const Eigen::Matrix<double,14,14>& _cov_kin_meas){cov_kin_meas_ = _cov_kin_meas;}
     
     private:
         double dt_;
diff --git a/include/bodydynamics/feature/feature_force_torque_preint.h b/include/bodydynamics/feature/feature_force_torque_preint.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a03d2a73e668123b29dd948c7988795ea45f9cf
--- /dev/null
+++ b/include/bodydynamics/feature/feature_force_torque_preint.h
@@ -0,0 +1,95 @@
+//--------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--------
+#ifndef FEATURE_FORCE_TORQUE_PREINT_H_
+#define FEATURE_FORCE_TORQUE_PREINT_H_
+
+//Wolf includes
+#include <core/feature/feature_base.h>
+#include <core/common/wolf.h>
+
+//std includes
+
+namespace wolf {
+
+//WOLF_PTR_TYPEDEFS(CaptureImu);
+WOLF_PTR_TYPEDEFS(FeatureForceTorquePreint);
+
+class FeatureForceTorquePreint : public FeatureBase
+{
+    public:
+
+        /** \brief Constructor from and measures
+         *
+         * \param _measurement the measurement
+         * \param _meas_covariance the noise of the measurement
+         * \param _J_delta_bias Jacobians of preintegrated delta wrt [pbc,gyro] biases
+         * \param _pbc_bias COM position relative to bias bias of origin frame time
+         * \param _gyro_bias gyroscope bias of origin frame time
+         * \param _cap_ftpreint_ptr pointer to parent CaptureMotion (CaptureForceTorquePreint)
+         */
+        FeatureForceTorquePreint(const Eigen::VectorXd& _delta_preintegrated,
+                                 const Eigen::MatrixXd& _delta_preintegrated_covariance,
+                                 const Eigen::Vector6d& _biases_preint,
+                                 const Eigen::Matrix<double,12,6>& _J_delta_biases);
+
+//        /** \brief Constructor from capture pointer
+//         *
+//         * \param _cap_imu_ptr pointer to parent CaptureMotion
+//         */
+//        FeatureForceTorquePreint(CaptureMotionPtr _cap_imu_ptr);
+
+        ~FeatureForceTorquePreint() override = default;
+
+        const Eigen::Vector3d&               getPbcBiasPreint()  const;
+        const Eigen::Vector3d&               getGyroBiasPreint() const;
+        const Eigen::Matrix<double, 12, 6>&  getJacobianBias()   const;
+
+    private:
+
+        // Used biases
+        Eigen::Vector3d pbc_bias_preint_;       ///< Acceleration bias used for delta preintegration
+        Eigen::Vector3d gyro_bias_preint_;      ///< Gyrometer bias used for delta preintegration
+
+        Eigen::Matrix<double, 12, 6> J_delta_bias_;
+
+    public:
+      EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+
+inline const Eigen::Vector3d& FeatureForceTorquePreint::getPbcBiasPreint() const
+{
+    return pbc_bias_preint_;
+}
+
+inline const Eigen::Vector3d& FeatureForceTorquePreint::getGyroBiasPreint() const
+{
+    return gyro_bias_preint_;
+}
+
+inline const Eigen::Matrix<double, 12, 6>& FeatureForceTorquePreint::getJacobianBias() const
+{
+    return J_delta_bias_;
+}
+
+} // namespace wolf
+
+#endif // FEATURE_FORCE_TORQUE_PREINT_H_
diff --git a/include/bodydynamics/feature/feature_inertial_kinematics.h b/include/bodydynamics/feature/feature_inertial_kinematics.h
index 00685a7102cc4d86713c859eb2657035f6be5201..bb9588ffbc24e78162bedddc5df7a914f7d55c63 100644
--- a/include/bodydynamics/feature/feature_inertial_kinematics.h
+++ b/include/bodydynamics/feature/feature_inertial_kinematics.h
@@ -1,3 +1,24 @@
+//--------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--------
 #ifndef FEATURE_INERTIAL_KINEMATICS_H_
 #define FEATURE_INERTIAL_KINEMATICS_H_
 
@@ -14,23 +35,47 @@ class FeatureInertialKinematics : public FeatureBase
 {
     public:
 
-        FeatureInertialKinematics(const Eigen::Vector9d & _measurement, 
-                                  const Eigen::Matrix9d & _meas_uncertainty,
-                                  const Eigen::Matrix3d & _BIq, 
-                                  const Eigen::Vector3d & _BLcm,
-                                  UncertaintyType _uncertainty_type = UNCERTAINTY_IS_INFO);
-        virtual ~FeatureInertialKinematics();
+        FeatureInertialKinematics(const Eigen::Vector9d & _meas_pvw,
+                                  const Eigen::Matrix9d & _Qerr,
+                                  const Eigen::Matrix3d & _B_I_q,  // Centroidal inertia matrix expressed in body frame
+                                  const Eigen::Vector3d & _B_Lc_m, // Centroidal angular momentum expressed in body frame
+                                  UncertaintyType _uncertainty_type = UNCERTAINTY_IS_COVARIANCE);
+        ~FeatureInertialKinematics() override = default;
+
 
         const Eigen::Matrix3d & getBIq(){return BIq_;}
         const Eigen::Vector3d & getBLcm(){return BLcm_;}
         void setBIq(const Eigen::Matrix3d & _BIq){BIq_ = _BIq;}
         void setBLcm(const Eigen::Vector3d & _BLcm){BLcm_ = _BLcm;}
 
+
     private:
         Eigen::Matrix3d BIq_;
         Eigen::Vector3d BLcm_;
+
+    public:
+        EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+
 };
 
+    Eigen::Matrix9d computeIKinJac(const Eigen::Vector3d& _w_unb,
+                                    const Eigen::Vector3d& _p_unb,
+                                    const Eigen::Matrix3d& _Iq);
+
+    Eigen::Matrix9d computeIKinCov(const Eigen::Matrix3d& _Qp,
+                                    const Eigen::Matrix3d& _Qv, 
+                                    const Eigen::Matrix3d& _Qw,
+                                    const Eigen::Vector3d& _w_unb,
+                                    const Eigen::Vector3d& _p_unb,
+                                    const Eigen::Matrix3d& _Iq);
+
+    void recomputeIKinCov(const Eigen::Matrix3d& Qp,
+                            const Eigen::Matrix3d& Qv, 
+                            const Eigen::Matrix3d& Qw,
+                            const Eigen::Vector3d& p_unb,
+                            const Eigen::Vector3d& w_unb,
+                            const Eigen::Matrix3d& Iq);
+
 } // namespace wolf
 
 #endif
diff --git a/include/bodydynamics/math/force_torque_delta_tools.h b/include/bodydynamics/math/force_torque_delta_tools.h
index d778e96b7c3f670c415006af63db85bfd13d3905..fdc3d3a8034f5fa4372b0b0796b27b60ce889eb5 100644
--- a/include/bodydynamics/math/force_torque_delta_tools.h
+++ b/include/bodydynamics/math/force_torque_delta_tools.h
@@ -1,3 +1,24 @@
+//--------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--------
 /*
  * imu_tools.h
  *
@@ -36,7 +57,7 @@
  *   - betweenStates: D = x2 (-) x1, so that x2 = x1 (+) D
  *   - log: got from Delta manifold to tangent space (equivalent to log() in rotations)
  *   - exp_FT: go from tangent space to delta manifold (equivalent to exp() in rotations)
- *   - plus: D2 = D1 (+) exp_IMU(d)
+ *   - plus: D2 = D1 (+) exp_imu(d)
  *   - diff: d = log_FT( D2 (-) D1 )
  *   - body2delta: construct a delta from body magnitudes
  */
@@ -49,10 +70,10 @@ using namespace Eigen;
 template<typename D1, typename D2, typename D3, typename D4>
 inline void identity(MatrixBase<D1>& dc, MatrixBase<D2>& dcd, MatrixBase<D3>& dLc, QuaternionBase<D4>& dq)
 {
-    dc = MatrixBase<D1>::Zero(3,1);
+    dc  = MatrixBase<D1>::Zero(3,1);
     dcd = MatrixBase<D3>::Zero(3,1);
     dLc = MatrixBase<D3>::Zero(3,1);
-    dq = QuaternionBase<D4>::Identity();
+    dq  = QuaternionBase<D4>::Identity();
 }
 
 template<typename D1, typename D2, typename D3, typename D4>
@@ -62,10 +83,10 @@ inline void identity(MatrixBase<D1>& dc, MatrixBase<D2>& dcd, MatrixBase<D3>& dL
     typedef typename D2::Scalar T2;
     typedef typename D3::Scalar T3;
     typedef typename D4::Scalar T4;
-    dc << T1(0), T1(0), T1(0);
+    dc  << T1(0), T1(0), T1(0);
     dcd << T2(0), T2(0), T2(0);
     dLc << T3(0), T3(0), T3(0);
-    dq << T4(0), T4(0), T4(0), T4(1);
+    dq  << T4(0), T4(0), T4(0), T4(1);
 }
 
 template<typename T = double>
@@ -198,435 +219,506 @@ inline void compose(const MatrixBase<D1>& d1,
     MatrixSizeCheck< 12, 12>::check(J_sum_d1);
     MatrixSizeCheck< 12, 12>::check(J_sum_d2);
 
-    // // Some useful temporaries
-    // Matrix<typename D1::Scalar, 3, 3> dR1 = q2R(d1.segment<4>(9));
-    // Matrix<typename D2::Scalar, 3, 3> dR2 = q2R(d2.segment<4>(9));
-    // Matrix<typename D2::Scalar, 3, 1> dc2  = d1.head<3>();
-    // Matrix<typename D2::Scalar, 3, 1> dcd2 = d1.segment<3>(3);
-    // Matrix<typename D2::Scalar, 3, 1> dLc2 = d1.segment<3>(6);
+    // Some useful temporaries
+    Map<const Quaternion<typename D1::Scalar> >     dq1    ( & d1( 9 ) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dc2    ( & d2( 0 ) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dcd2   ( & d2( 3 ) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dLc2   ( & d2( 6 ) );
+    Map<const Quaternion<typename D2::Scalar> >     dq2    ( & d2( 9 ) );
+
+    Matrix<typename D1::Scalar, 3, 3> dR1 = q2R(dq1);
+    Matrix<typename D2::Scalar, 3, 3> dR2 = q2R(dq2);
 
     // // Jac wrt first delta
-    // J_sum_d1.setIdentity();                                     
-    // J_sum_d1.block<3,3>(0,3) = Matrix3d::Identity() * dt;
-    // J_sum_d1.block<3,3>(0,9) = - dR1 * skew(dc2);
-    // J_sum_d1.block<3,3>(3,9) = - dR1 * skew(dcd2);
-    // J_sum_d1.block<3,3>(6,9) = - dR1 * skew(dLc2);
-    // J_sum_d1.block<3,3>(9,9) = dR2.transpose();
-
-    // // Jac wrt second delta
-    // J_sum_d2.setIdentity();
-    // J_sum_d2.block<3,3>(0,0) = dR1;
-    // J_sum_d2.block<3,3>(3,3) = dR1;
-    // J_sum_d2.block<3,3>(6,6) = dR1;
+    J_sum_d1.setIdentity();                                     
+    J_sum_d1.template block<3,3>(0,3) = Matrix3d::Identity() * dt;
+    J_sum_d1.template block<3,3>(0,9) = - dR1 * skew(dc2);
+    J_sum_d1.template block<3,3>(3,9) = - dR1 * skew(dcd2);
+    J_sum_d1.template block<3,3>(6,9) = - dR1 * skew(dLc2);
+    J_sum_d1.template block<3,3>(9,9) = dR2.transpose();
+
+    // // // Jac wrt second delta
+    J_sum_d2.setIdentity();
+    J_sum_d2.template block<3,3>(0,0) = dR1;
+    J_sum_d2.template block<3,3>(3,3) = dR1;
+    J_sum_d2.template block<3,3>(6,6) = dR1;
 
     // compose deltas -- done here to avoid aliasing when calling with input 
     // `d1` and result `sum` referencing the same variable
     compose(d1, d2, dt, sum);
 }
 
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, class T>
-// inline void between(const MatrixBase<D1>& dp1, const QuaternionBase<D2>& dq1, const MatrixBase<D3>& dv1,
-//                     const MatrixBase<D4>& dp2, const QuaternionBase<D5>& dq2, const MatrixBase<D6>& dv2,
-//                     const T dt,
-//                     MatrixBase<D7>& diff_p, QuaternionBase<D8>& diff_q, MatrixBase<D9>& diff_v )
-// {
-//         MatrixSizeCheck<3, 1>::check(dp1);
-//         MatrixSizeCheck<3, 1>::check(dv1);
-//         MatrixSizeCheck<3, 1>::check(dp2);
-//         MatrixSizeCheck<3, 1>::check(dv2);
-//         MatrixSizeCheck<3, 1>::check(diff_p);
-//         MatrixSizeCheck<3, 1>::check(diff_v);
-
-//         diff_p = dq1.conjugate() * ( dp2 - dp1 - dv1*dt );
-//         diff_v = dq1.conjugate() * ( dv2 - dv1 );
-//         diff_q = dq1.conjugate() *   dq2;
-// }
-
-// template<typename D1, typename D2, typename D3, class T>
-// inline void between(const MatrixBase<D1>& d1,
-//                     const MatrixBase<D2>& d2,
-//                     T dt,
-//                     MatrixBase<D3>& d2_minus_d1)
-// {
-//     MatrixSizeCheck<10, 1>::check(d1);
-//     MatrixSizeCheck<10, 1>::check(d2);
-//     MatrixSizeCheck<10, 1>::check(d2_minus_d1);
-
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dp1    ( & d1(0) );
-//     Map<const Quaternion<typename D1::Scalar> >     dq1    ( & d1(3) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dv1    ( & d1(7) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dp2    ( & d2(0) );
-//     Map<const Quaternion<typename D2::Scalar> >     dq2    ( & d2(3) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dv2    ( & d2(7) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_p ( & d2_minus_d1(0) );
-//     Map<Quaternion<typename D3::Scalar> >           diff_q ( & d2_minus_d1(3) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_v ( & d2_minus_d1(7) );
-
-//     between(dp1, dq1, dv1, dp2, dq2, dv2, dt, diff_p, diff_q, diff_v);
-// }
-
-// template<typename D1, typename D2, class T>
-// inline Matrix<typename D1::Scalar, 10, 1> between(const MatrixBase<D1>& d1,
-//                                                   const MatrixBase<D2>& d2,
-//                                                   T dt)
-// {
-//     Matrix<typename D1::Scalar, 10, 1> diff;
-//     between(d1, d2, dt, diff);
-//     return diff;
-// }
-
-// template<typename D1, typename D2, typename D3, class T>
-// inline void composeOverState(const MatrixBase<D1>& x,
-//                              const MatrixBase<D2>& d,
-//                              T dt,
-//                              MatrixBase<D3>& x_plus_d)
-// {
-//     MatrixSizeCheck<10, 1>::check(x);
-//     MatrixSizeCheck<10, 1>::check(d);
-//     MatrixSizeCheck<10, 1>::check(x_plus_d);
-
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   p         ( & x( 0 ) );
-//     Map<const Quaternion<typename D1::Scalar> >     q         ( & x( 3 ) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   v         ( & x( 7 ) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dp        ( & d( 0 ) );
-//     Map<const Quaternion<typename D2::Scalar> >     dq        ( & d( 3 ) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dv        ( & d( 7 ) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         p_plus_d  ( & x_plus_d( 0 ) );
-//     Map<Quaternion<typename D3::Scalar> >           q_plus_d  ( & x_plus_d( 3 ) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         v_plus_d  ( & x_plus_d( 7 ) );
-
-//     p_plus_d = p + v*dt + 0.5*gravity()*dt*dt + q*dp;
-//     v_plus_d = v +            gravity()*dt    + q*dv;
-//     q_plus_d =                                  q*dq; // dq here to avoid possible aliasing between x and x_plus_d
-// }
-
-// template<typename D1, typename D2, class T>
-// inline Matrix<typename D1::Scalar, 10, 1> composeOverState(const MatrixBase<D1>& x,
-//                                                            const MatrixBase<D2>& d,
-//                                                            T dt)
-// {
-//     Matrix<typename D1::Scalar, 10, 1>  ret;
-//     composeOverState(x, d, dt, ret);
-//     return ret;
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, class T>
-// inline void betweenStates(const MatrixBase<D1>& p1, const QuaternionBase<D2>& q1, const MatrixBase<D3>& v1,
-//                           const MatrixBase<D4>& p2, const QuaternionBase<D5>& q2, const MatrixBase<D6>& v2,
-//                           const T dt,
-//                           MatrixBase<D7>& dp, QuaternionBase<D8>& dq, MatrixBase<D9>& dv )
-// {
-//         MatrixSizeCheck<3, 1>::check(p1);
-//         MatrixSizeCheck<3, 1>::check(v1);
-//         MatrixSizeCheck<3, 1>::check(p2);
-//         MatrixSizeCheck<3, 1>::check(v2);
-//         MatrixSizeCheck<3, 1>::check(dp);
-//         MatrixSizeCheck<3, 1>::check(dv);
-
-//         dp = q1.conjugate() * ( p2 - p1 - v1*dt - (T)0.5*gravity().cast<T>()*(T)dt*(T)dt );
-//         dv = q1.conjugate() * ( v2 - v1         -     gravity().cast<T>()*(T)dt );
-//         dq = q1.conjugate() *   q2;
-// }
-
-// template<typename D1, typename D2, typename D3, class T>
-// inline void betweenStates(const MatrixBase<D1>& x1,
-//                           const MatrixBase<D2>& x2,
-//                           T dt,
-//                           MatrixBase<D3>& x2_minus_x1)
-// {
-//     MatrixSizeCheck<10, 1>::check(x1);
-//     MatrixSizeCheck<10, 1>::check(x2);
-//     MatrixSizeCheck<10, 1>::check(x2_minus_x1);
-
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   p1  ( & x1(0) );
-//     Map<const Quaternion<typename D1::Scalar> >     q1  ( & x1(3) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   v1  ( & x1(7) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   p2  ( & x2(0) );
-//     Map<const Quaternion<typename D2::Scalar> >     q2  ( & x2(3) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   v2  ( & x2(7) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         dp  ( & x2_minus_x1(0) );
-//     Map<Quaternion<typename D3::Scalar> >           dq  ( & x2_minus_x1(3) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         dv  ( & x2_minus_x1(7) );
-
-//     betweenStates(p1, q1, v1, p2, q2, v2, dt, dp, dq, dv);
-// }
-
-// template<typename D1, typename D2, class T>
-// inline Matrix<typename D1::Scalar, 10, 1> betweenStates(const MatrixBase<D1>& x1,
-//                                                         const MatrixBase<D2>& x2,
-//                                                         T dt)
-// {
-//     Matrix<typename D1::Scalar, 10, 1> ret;
-//     betweenStates(x1, x2, dt, ret);
-//     return ret;
-// }
-
-// template<typename Derived>
-// Matrix<typename Derived::Scalar, 9, 1> log_IMU(const MatrixBase<Derived>& delta_in)
-// {
-//     MatrixSizeCheck<10, 1>::check(delta_in);
-
-//     Matrix<typename Derived::Scalar, 9, 1> ret;
-
-//     Map<const Matrix<typename Derived::Scalar, 3, 1> >   dp_in  ( & delta_in(0) );
-//     Map<const Quaternion<typename Derived::Scalar> >     dq_in  ( & delta_in(3) );
-//     Map<const Matrix<typename Derived::Scalar, 3, 1> >   dv_in  ( & delta_in(7) );
-//     Map<Matrix<typename Derived::Scalar, 3, 1> >         dp_ret ( & ret(0) );
-//     Map<Matrix<typename Derived::Scalar, 3, 1> >         do_ret ( & ret(3) );
-//     Map<Matrix<typename Derived::Scalar, 3, 1> >         dv_ret ( & ret(6) );
-
-//     dp_ret = dp_in;
-//     dv_ret = dv_in;
-//     do_ret = log_q(dq_in);
-
-//     return ret;
-// }
-
-// template<typename Derived>
-// Matrix<typename Derived::Scalar, 10, 1> exp_IMU(const MatrixBase<Derived>& d_in)
-// {
-//     MatrixSizeCheck<9, 1>::check(d_in);
-
-//     Matrix<typename Derived::Scalar, 10, 1> ret;
-
-//     Map<const Matrix<typename Derived::Scalar, 3, 1> >   dp_in  ( & d_in(0) );
-//     Map<const Matrix<typename Derived::Scalar, 3, 1> >   do_in  ( & d_in(3) );
-//     Map<const Matrix<typename Derived::Scalar, 3, 1> >   dv_in  ( & d_in(6) );
-//     Map<Matrix<typename Derived::Scalar, 3, 1> >         dp     ( &  ret(0) );
-//     Map<Quaternion<typename Derived::Scalar> >           dq     ( &  ret(3) );
-//     Map<Matrix<typename Derived::Scalar, 3, 1> >         dv     ( &  ret(7) );
-
-//     dp = dp_in;
-//     dv = dv_in;
-//     dq = exp_q(do_in);
-
-//     return ret;
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9>
-// inline void plus(const MatrixBase<D1>& dp1, const QuaternionBase<D2>& dq1, const MatrixBase<D3>& dv1,
-//                  const MatrixBase<D4>& dp2, const MatrixBase<D5>& do2, const MatrixBase<D6>& dv2,
-//                  MatrixBase<D7>& plus_p, QuaternionBase<D8>& plus_q, MatrixBase<D9>& plus_v )
-// {
-//         plus_p = dp1 + dp2;
-//         plus_v = dv1 + dv2;
-//         plus_q = dq1 * exp_q(do2);
-// }
-
-// template<typename D1, typename D2, typename D3>
-// inline void plus(const MatrixBase<D1>& d1,
-//                  const MatrixBase<D2>& d2,
-//                  MatrixBase<D3>& d_pert)
-// {
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dp1    ( & d1(0) );
-//     Map<const Quaternion<typename D1::Scalar> >     dq1    ( & d1(3) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dv1    ( & d1(7) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dp2    ( & d2(0) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   do2    ( & d2(3) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dv2    ( & d2(6) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         dp_p ( & d_pert(0) );
-//     Map<Quaternion<typename D3::Scalar> >           dq_p ( & d_pert(3) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         dv_p ( & d_pert(7) );
-
-//     plus(dp1, dq1, dv1, dp2, do2, dv2, dp_p, dq_p, dv_p);
-// }
-
-// template<typename D1, typename D2>
-// inline Matrix<typename D1::Scalar, 10, 1> plus(const MatrixBase<D1>& d1,
-//                                                const MatrixBase<D2>& d2)
-// {
-//     Matrix<typename D1::Scalar, 10, 1> ret;
-//     plus(d1, d2, ret);
-//     return ret;
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9>
-// inline void diff(const MatrixBase<D1>& dp1, const QuaternionBase<D2>& dq1, const MatrixBase<D3>& dv1,
-//                  const MatrixBase<D4>& dp2, const QuaternionBase<D5>& dq2, const MatrixBase<D6>& dv2,
-//                  MatrixBase<D7>& diff_p, MatrixBase<D8>& diff_o, MatrixBase<D9>& diff_v )
-// {
-//         diff_p = dp2 - dp1;
-//         diff_v = dv2 - dv1;
-//         diff_o = log_q(dq1.conjugate() * dq2);
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11>
-// inline void diff(const MatrixBase<D1>& dp1, const QuaternionBase<D2>& dq1, const MatrixBase<D3>& dv1,
-//                  const MatrixBase<D4>& dp2, const QuaternionBase<D5>& dq2, const MatrixBase<D6>& dv2,
-//                  MatrixBase<D7>& diff_p, MatrixBase<D8>& diff_o, MatrixBase<D9>& diff_v ,
-//                  MatrixBase<D10>& J_do_dq1, MatrixBase<D11>& J_do_dq2)
-// {
-//     diff(dp1, dq1, dv1, dp2, dq2, dv2, diff_p, diff_o, diff_v);
-
-//     J_do_dq1    = - jac_SO3_left_inv(diff_o);
-//     J_do_dq2    =   jac_SO3_right_inv(diff_o);
-// }
-
-// template<typename D1, typename D2, typename D3>
-// inline void diff(const MatrixBase<D1>& d1,
-//                  const MatrixBase<D2>& d2,
-//                  MatrixBase<D3>& err)
-// {
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dp1    ( & d1(0) );
-//     Map<const Quaternion<typename D1::Scalar> >     dq1    ( & d1(3) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dv1    ( & d1(7) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dp2    ( & d2(0) );
-//     Map<const Quaternion<typename D2::Scalar> >     dq2    ( & d2(3) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dv2    ( & d2(7) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_p ( & err(0) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_o ( & err(3) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_v ( & err(6) );
-
-//     diff(dp1, dq1, dv1, dp2, dq2, dv2, diff_p, diff_o, diff_v);
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5>
-// inline void diff(const MatrixBase<D1>& d1,
-//                  const MatrixBase<D2>& d2,
-//                  MatrixBase<D3>& dif,
-//                  MatrixBase<D4>& J_diff_d1,
-//                  MatrixBase<D5>& J_diff_d2)
-// {
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dp1    ( & d1(0) );
-//     Map<const Quaternion<typename D1::Scalar> >     dq1    ( & d1(3) );
-//     Map<const Matrix<typename D1::Scalar, 3, 1> >   dv1    ( & d1(7) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dp2    ( & d2(0) );
-//     Map<const Quaternion<typename D2::Scalar> >     dq2    ( & d2(3) );
-//     Map<const Matrix<typename D2::Scalar, 3, 1> >   dv2    ( & d2(7) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_p ( & dif(0) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_o ( & dif(3) );
-//     Map<Matrix<typename D3::Scalar, 3, 1> >         diff_v ( & dif(6) );
-
-//     Matrix<typename D4::Scalar, 3, 3> J_do_dq1, J_do_dq2;
-
-//     diff(dp1, dq1, dv1, dp2, dq2, dv2, diff_p, diff_o, diff_v, J_do_dq1, J_do_dq2);
-
-//     /* d = diff(d1, d2) is
-//      *   dp = dp2 - dp1
-//      *   do = Log(dq1.conj * dq2)
-//      *   dv = dv2 - dv1
-//      *
-//      * With trivial Jacobians for dp and dv, and:
-//      *   J_do_dq1 = - J_l_inv(theta)
-//      *   J_do_dq2 =   J_r_inv(theta)
-//      */
-
-//     J_diff_d1 = - Matrix<typename D4::Scalar, 9, 9>::Identity();// d(p2  - p1) / d(p1) = - Identity
-//     J_diff_d1.block(3,3,3,3) = J_do_dq1;       // d(R1.tr*R2) / d(R1) = - J_l_inv(theta)
-
-//     J_diff_d2.setIdentity();                                    // d(R1.tr*R2) / d(R2) =   Identity
-//     J_diff_d2.block(3,3,3,3) = J_do_dq2;      // d(R1.tr*R2) / d(R1) =   J_r_inv(theta)
-// }
-
-// template<typename D1, typename D2>
-// inline Matrix<typename D1::Scalar, 9, 1> diff(const MatrixBase<D1>& d1,
-//                                               const MatrixBase<D2>& d2)
-// {
-//     Matrix<typename D1::Scalar, 9, 1> ret;
-//     diff(d1, d2, ret);
-//     return ret;
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5>
-// inline void body2delta(const MatrixBase<D1>& a,
-//                        const MatrixBase<D2>& w,
-//                        const typename D1::Scalar& dt,
-//                        MatrixBase<D3>& dp,
-//                        QuaternionBase<D4>& dq,
-//                        MatrixBase<D5>& dv)
-// {
-//     MatrixSizeCheck<3,1>::check(a);
-//     MatrixSizeCheck<3,1>::check(w);
-//     MatrixSizeCheck<3,1>::check(dp);
-//     MatrixSizeCheck<3,1>::check(dv);
-
-//     dp = 0.5 * a * dt * dt;
-//     dv =       a * dt;
-//     dq = exp_q(w * dt);
-// }
-
-// template<typename D1>
-// inline Matrix<typename D1::Scalar, 10, 1> body2delta(const MatrixBase<D1>& body,
-//                                                      const typename D1::Scalar& dt)
-// {
-//     MatrixSizeCheck<6,1>::check(body);
-
-//     typedef typename D1::Scalar T;
-
-//     Matrix<T, 10, 1> delta;
-
-//     Map< Matrix<T, 3, 1>> dp ( & delta(0) );
-//     Map< Quaternion<T>>   dq ( & delta(3) );
-//     Map< Matrix<T, 3, 1>> dv ( & delta(7) );
-
-//     body2delta(body.block(0,0,3,1), body.block(3,0,3,1), dt, dp, dq, dv);
-
-//     return delta;
-// }
-
-// template<typename D1, typename D2, typename D3>
-// inline void body2delta(const MatrixBase<D1>& body,
-//                        const typename D1::Scalar& dt,
-//                        MatrixBase<D2>& delta,
-//                        MatrixBase<D3>& jac_body)
-// {
-//     MatrixSizeCheck<6,1>::check(body);
-//     MatrixSizeCheck<9,6>::check(jac_body);
-
-//     typedef typename D1::Scalar T;
-
-//     delta = body2delta(body, dt);
-
-//     Matrix<T, 3, 1> w = body.block(3,0,3,1);
-
-//     jac_body.setZero();
-//     jac_body.block(0,0,3,3) = 0.5 * dt * dt * Matrix<T, 3, 3>::Identity();
-//     jac_body.block(3,3,3,3) =            dt * jac_SO3_right(w * dt);
-//     jac_body.block(6,0,3,3) =            dt * Matrix<T, 3, 3>::Identity();
-// }
-
-// template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7>
-// void motion2data(const MatrixBase<D1>& a, const MatrixBase<D2>& w, const QuaternionBase<D3>& q, const MatrixBase<D4>& a_b, const MatrixBase<D5>& w_b, MatrixBase<D6>& a_m, MatrixBase<D7>& w_m)
-// {
-//     MatrixSizeCheck<3,1>::check(a);
-//     MatrixSizeCheck<3,1>::check(w);
-//     MatrixSizeCheck<3,1>::check(a_b);
-//     MatrixSizeCheck<3,1>::check(w_b);
-//     MatrixSizeCheck<3,1>::check(a_m);
-//     MatrixSizeCheck<3,1>::check(w_m);
-
-//     // Note: data = (a_m , w_m)
-//     a_m = a + a_b - q.conjugate()*gravity();
-//     w_m = w + w_b;
-// }
-
-// /* Create IMU data from body motion
-//  * Input:
-//  *   motion : [ax, ay, az, wx, wy, wz] the motion in body frame
-//  *   q      : the current orientation wrt horizontal
-//  *   bias   : the bias of the IMU
-//  * Output:
-//  *   return : the data vector as created by the IMU (with motion, gravity, and bias)
-//  */
-// template<typename D1, typename D2, typename D3>
-// Matrix<typename D1::Scalar, 6, 1> motion2data(const MatrixBase<D1>& motion, const QuaternionBase<D2>& q, const MatrixBase<D3>& bias)
-// {
-//     Matrix<typename D1::Scalar, 6, 1>      data;
-//     Map<Matrix<typename D1::Scalar, 3, 1>> a_m (data.data()    );
-//     Map<Matrix<typename D1::Scalar, 3, 1>> w_m (data.data() + 3);
-
-//     motion2data(motion.block(0,0,3,1),
-//                 motion.block(3,0,3,1),
-//                 q,
-//                 bias.block(0,0,3,1),
-//                 bias.block(3,0,3,1),
-//                 a_m,
-//                 w_m   );
-
-//     return  data;
-// }
-
-} // namespace imu
+template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11, typename D12, class T>
+inline void between(const MatrixBase<D1>& dc1, const MatrixBase<D2>& dcd1, const MatrixBase<D3>& dLc1, const QuaternionBase<D4>& dq1,
+                    const MatrixBase<D5>& dc2, const MatrixBase<D6>& dcd2, const MatrixBase<D7>& dLc2, const QuaternionBase<D8>& dq2,
+                    const T dt,
+                    MatrixBase<D9>& diff_c, MatrixBase<D10>& diff_cd, MatrixBase<D11>& diff_Lc, QuaternionBase<D12>& diff_q )
+{
+        MatrixSizeCheck<3, 1>::check(dc1);
+        MatrixSizeCheck<3, 1>::check(dcd1);
+        MatrixSizeCheck<3, 1>::check(dLc1);
+        MatrixSizeCheck<3, 1>::check(dc2);
+        MatrixSizeCheck<3, 1>::check(dcd2);
+        MatrixSizeCheck<3, 1>::check(dLc2);
+        MatrixSizeCheck<3, 1>::check(diff_c);
+        MatrixSizeCheck<3, 1>::check(diff_cd);
+        MatrixSizeCheck<3, 1>::check(diff_Lc);
+
+        diff_c  = dq1.conjugate() * (dc2 - dc1 - dcd1*dt);
+        diff_cd = dq1.conjugate() * (dcd2 - dcd1);
+        diff_Lc = dq1.conjugate() * (dLc2 - dLc1);
+        diff_q  = dq1.conjugate() * dq2;
+}
+
+template<typename D1, typename D2, typename D3, class T>
+inline void between(const MatrixBase<D1>& d1,
+                    const MatrixBase<D2>& d2,
+                    T dt,
+                    MatrixBase<D3>& d2_minus_d1)
+{
+    MatrixSizeCheck<13, 1>::check(d1);
+    MatrixSizeCheck<13, 1>::check(d2);
+    MatrixSizeCheck<13, 1>::check(d2_minus_d1);
+
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dc1     ( & d1(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dcd1    ( & d1(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dLc1    ( & d1(6) );
+    Map<const Quaternion<typename D1::Scalar> >     dq1     ( & d1(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dc2     ( & d2(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dcd2    ( & d2(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dLc2    ( & d2(6) );
+    Map<const Quaternion<typename D2::Scalar> >     dq2     ( & d2(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   diff_c  ( & d2_minus_d1(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   diff_cd ( & d2_minus_d1(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   diff_Lc ( & d2_minus_d1(6) );
+    Map<Quaternion<typename D3::Scalar> >     diff_q  ( & d2_minus_d1(9) );
+
+    between(dc1, dcd1, dLc1, dq1, dc2, dcd2, dLc2, dq2, dt, diff_c, diff_cd, diff_Lc, diff_q);
+}
+
+template<typename D1, typename D2, class T>
+inline Matrix<typename D1::Scalar, 13, 1> between(const MatrixBase<D1>& d1,
+                                                  const MatrixBase<D2>& d2,
+                                                  T dt)
+{
+    Matrix<typename D1::Scalar, 13, 1> diff;
+    between(d1, d2, dt, diff);
+    return diff;
+}
+
+template<typename D1, typename D2, typename D3, class T>
+inline void composeOverState(const MatrixBase<D1>& x,
+                             const MatrixBase<D2>& d,
+                             T dt,
+                             MatrixBase<D3>& x_plus_d)
+{
+    MatrixSizeCheck<13, 1>::check(x);
+    MatrixSizeCheck<13, 1>::check(d);
+    MatrixSizeCheck<13, 1>::check(x_plus_d);
+
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   c     ( & x(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   cd    ( & x(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   Lc    ( & x(6) );
+    Map<const Quaternion<typename D1::Scalar> >     q     ( & x(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dc     ( & d(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dcd    ( & d(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dLc    ( & d(6) );
+    Map<const Quaternion<typename D2::Scalar> >     dq     ( & d(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   c_plus_d  ( & x_plus_d(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   cd_plus_d ( & x_plus_d(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   Lc_plus_d ( & x_plus_d(6) );
+    Map<Quaternion<typename D3::Scalar> >     q_plus_d  ( & x_plus_d(9) );
+
+    c_plus_d = c + cd*dt + 0.5*gravity()*dt*dt + q*dc;
+    cd_plus_d = cd + gravity()*dt + q*dcd;
+    Lc_plus_d = Lc + q*dLc;
+    q_plus_d = q*dq; // dq here to avoid possible aliasing between x and x_plus_d  --> WHAT?
+}
+
+template<typename D1, typename D2, class T>
+inline Matrix<typename D1::Scalar, 13, 1> composeOverState(const MatrixBase<D1>& x,
+                                                           const MatrixBase<D2>& d,
+                                                           T dt)
+{
+    Matrix<typename D1::Scalar, 13, 1>  ret;
+    composeOverState(x, d, dt, ret);
+    return ret;
+}
+
+template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11, typename D12, class T>
+inline void betweenStates(const MatrixBase<D1>& c1, const MatrixBase<D2>& cd1, const MatrixBase<D3>& Lc1, const QuaternionBase<D4>& q1,
+                          const MatrixBase<D5>& c2, const MatrixBase<D6>& cd2, const MatrixBase<D7>& Lc2, const QuaternionBase<D8>& q2,
+                          const T dt,
+                          MatrixBase<D9>& dc, MatrixBase<D10>& dcd, MatrixBase<D11>& dLc, QuaternionBase<D12>& dq)
+{
+        MatrixSizeCheck<3, 1>::check(c1);
+        MatrixSizeCheck<3, 1>::check(cd1);
+        MatrixSizeCheck<3, 1>::check(Lc1);
+        MatrixSizeCheck<3, 1>::check(c2);
+        MatrixSizeCheck<3, 1>::check(cd2);
+        MatrixSizeCheck<3, 1>::check(Lc2);
+        MatrixSizeCheck<3, 1>::check(dc);
+        MatrixSizeCheck<3, 1>::check(dcd);
+        MatrixSizeCheck<3, 1>::check(dLc);
+
+        dc =  q1.conjugate() * ( c2 - c1 - cd1*dt - 0.5*gravity() *dt * dt );
+        dcd = q1.conjugate() * ( cd2 - cd1        -   gravity()*dt );
+        dLc = q1.conjugate() * ( Lc2 - Lc1);
+        dq =  q1.conjugate() *   q2;
+}
+
+template<typename D1, typename D2, typename D3, class T>
+inline void betweenStates(const MatrixBase<D1>& x1,
+                          const MatrixBase<D2>& x2,
+                          T dt,
+                          MatrixBase<D3>& x2_minus_x1)
+{
+    MatrixSizeCheck<13, 1>::check(x1);
+    MatrixSizeCheck<13, 1>::check(x2);
+    MatrixSizeCheck<13, 1>::check(x2_minus_x1);
+
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   c1     ( & x1(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   cd1    ( & x1(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   Lc1    ( & x1(6) );
+    Map<const Quaternion<typename D1::Scalar> >     q1     ( & x1(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   c2    ( & x2(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   cd2   ( & x2(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   Lc2   ( & x2(6) );
+    Map<const Quaternion<typename D2::Scalar> >     q2    ( & x2(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   dc  ( & x2_minus_x1(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   dcd ( & x2_minus_x1(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >   dLc ( & x2_minus_x1(6) );
+    Map<Quaternion<typename D3::Scalar> >     dq  ( & x2_minus_x1(9) );
+
+    betweenStates(c1, cd1, Lc1, q1, c2, cd2, Lc2, q2, dt, dc, dcd, dLc, dq);
+}
+
+template<typename D1, typename D2, class T>
+inline Matrix<typename D1::Scalar, 13, 1> betweenStates(const MatrixBase<D1>& x1,
+                                                        const MatrixBase<D2>& x2,
+                                                        T dt)
+{
+    Matrix<typename D1::Scalar, 13, 1> ret;
+    betweenStates(x1, x2, dt, ret);
+    return ret;
+}
+
+template<typename Derived>
+Matrix<typename Derived::Scalar, 12, 1> log_FT(const MatrixBase<Derived>& delta_in)
+{
+    MatrixSizeCheck<13, 1>::check(delta_in);
+
+    Matrix<typename Derived::Scalar, 12, 1> ret;
+
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dc_in  ( & delta_in(0) );
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dcd_in ( & delta_in(3) );
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dLc_in ( & delta_in(6) );
+    Map<const Quaternion<typename Derived::Scalar> >     dq_in  ( & delta_in(9) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >   dc_ret  ( & ret(0) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >   dcd_ret ( & ret(3) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >   dLc_ret ( & ret(6) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >     do_ret  ( & ret(9) );
+
+    dc_ret  = dc_in;
+    dcd_ret = dcd_in;
+    dLc_ret = dLc_in;
+    do_ret  = log_q(dq_in);
+
+    return ret;
+}
+
+template<typename Derived>
+Matrix<typename Derived::Scalar, 13, 1> exp_FT(const MatrixBase<Derived>& d_alg_in)
+{
+    MatrixSizeCheck<12, 1>::check(d_alg_in);
+
+    Matrix<typename Derived::Scalar, 13, 1> ret;
+
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dc_alg_in  ( & d_alg_in(0) );
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dcd_alg_in ( & d_alg_in(3) );
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   dLc_alg_in ( & d_alg_in(6) );
+    Map<const Matrix<typename Derived::Scalar, 3, 1> >   do_alg_in  ( & d_alg_in(9) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >         dc_ret  ( & ret(0) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >         dcd_ret ( & ret(3) );
+    Map<Matrix<typename Derived::Scalar, 3, 1> >         dLc_ret ( & ret(6) );
+    Map<Quaternion<typename Derived::Scalar> >           dq_ret  ( & ret(9) );
+
+    dc_ret  = dc_alg_in;
+    dcd_ret = dcd_alg_in;
+    dLc_ret = dLc_alg_in;
+    dq_ret  = exp_q(do_alg_in);
+
+    return ret;
+}
+
+template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11, typename D12>
+inline void plus(const MatrixBase<D1>& dc1, const MatrixBase<D2>& dcd1, const MatrixBase<D3>& dLc1, const QuaternionBase<D4>& dq1, 
+                 const MatrixBase<D5>& diff_c2, const MatrixBase<D6>& diff_cd2, const MatrixBase<D7>& diff_Lc2, const MatrixBase<D8>& diff_o2,
+                 MatrixBase<D9>& plus_c, MatrixBase<D10>& plus_cd, MatrixBase<D11>& plus_Lc, QuaternionBase<D12>& plus_q)
+{
+        plus_c  = dc1  + diff_c2;
+        plus_cd = dcd1 + diff_cd2;
+        plus_Lc = dLc1 + diff_Lc2;
+        plus_q  = dq1 * exp_q(diff_o2);
+}
+
+
+// Composite plus
+template<typename D1, typename D2, typename D3>
+inline void plus(const MatrixBase<D1>& d1,
+                 const MatrixBase<D2>& diff2,
+                 MatrixBase<D3>& d)
+{
+    Map<const Matrix<typename D1::Scalar, 3, 1> > dc1      ( & d1(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> > dcd1     ( & d1(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> > dLc1     ( & d1(6) );
+    Map<const Quaternion<typename D1::Scalar> >   dq1      ( & d1(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> > diff_dc2   ( & diff2(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> > diff_dcd2  ( & diff2(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> > diff_dLc2  ( & diff2(6) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> > diff_do2   ( & diff2(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >       d_c  ( & d(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >       d_cd ( & d(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >       d_Lc ( & d(6) );
+    Map<Quaternion<typename D3::Scalar> >         d_q  ( & d(9) );
+
+    plus(dc1, dcd1, dLc1, dq1, 
+         diff_dc2, diff_dcd2, diff_dLc2, diff_do2, 
+         d_c, d_cd, d_Lc, d_q);
+}
+
+template<typename D1, typename D2>
+inline Matrix<typename D1::Scalar, 13, 1> plus(const MatrixBase<D1>& d1,
+                                               const MatrixBase<D2>& d2)
+{
+    Matrix<typename D1::Scalar, 13, 1> ret;
+    plus(d1, d2, ret);
+    return ret;
+}
+
+template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11, typename D12>
+inline void diff(const MatrixBase<D1>& dc1, const MatrixBase<D2>& dcd1, const MatrixBase<D3>& dLc1, const QuaternionBase<D4>& dq1,
+                 const MatrixBase<D5>& dc2, const MatrixBase<D6>& dcd2, const MatrixBase<D7>& dLc2, const QuaternionBase<D8>& dq2,
+                 MatrixBase<D9>& diff_c, MatrixBase<D10>& diff_cd, MatrixBase<D11>& diff_Lc, MatrixBase<D12>& diff_o)
+{
+        diff_c  = dc2 - dc1;
+        diff_cd = dcd2 - dcd1;
+        diff_Lc = dLc2 - dLc1;
+        diff_o  = log_q(dq1.conjugate() * dq2);
+}
+
+template<typename D1, typename D2, typename D3, typename D4, typename D5, typename D6, typename D7, typename D8, typename D9, typename D10, typename D11, typename D12, typename D13, typename D14>
+inline void diff(const MatrixBase<D1>& dc1, const MatrixBase<D2>& dcd1, const MatrixBase<D3>& dLc1, const QuaternionBase<D4>& dq1,
+                 const MatrixBase<D5>& dc2, const MatrixBase<D6>& dcd2, const MatrixBase<D7>& dLc2, const QuaternionBase<D8>& dq2,
+                 MatrixBase<D9>& diff_c, MatrixBase<D10>& diff_cd, MatrixBase<D11>& diff_Lc, MatrixBase<D12>& diff_o,
+                 MatrixBase<D13>& J_do_dq1, MatrixBase<D14>& J_do_dq2)
+{
+    diff(dc1, dcd1, dLc1, dq1, dc2, dcd2, dLc2, dq2, diff_c, diff_cd, diff_Lc, diff_o);
+
+    J_do_dq1 = - jac_SO3_left_inv(diff_o);
+    J_do_dq2 =   jac_SO3_right_inv(diff_o);
+}
+
+template<typename D1, typename D2, typename D3>
+inline void diff(const MatrixBase<D1>& d1,
+                 const MatrixBase<D2>& d2,
+                 MatrixBase<D3>& diff_d1_d2)
+{
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dc1  ( & d1(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dcd1 ( & d1(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dLc1 ( & d1(6) );
+    Map<const Quaternion<typename D1::Scalar> >     dq1  ( & d1(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dc2  ( & d2(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dcd2 ( & d2(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dLc2 ( & d2(6) );
+    Map<const Quaternion<typename D1::Scalar> >     dq2  ( & d2(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_c  ( & diff_d1_d2(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_cd ( & diff_d1_d2(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_Lc ( & diff_d1_d2(6) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_o  ( & diff_d1_d2(9) );
+
+    diff(dc1, dcd1, dLc1, dq1, dc2, dcd2, dLc2, dq2, diff_c, diff_cd, diff_Lc, diff_o);
+}
+
+template<typename D1, typename D2, typename D3, typename D4, typename D5>
+inline void diff(const MatrixBase<D1>& d1,
+                 const MatrixBase<D2>& d2,
+                 MatrixBase<D3>& diff_d1_d2,
+                 MatrixBase<D4>& J_diff_d1,
+                 MatrixBase<D5>& J_diff_d2)
+{
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dc1  ( & d1(0) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dcd1 ( & d1(3) );
+    Map<const Matrix<typename D1::Scalar, 3, 1> >   dLc1 ( & d1(6) );
+    Map<const Quaternion<typename D1::Scalar> >     dq1  ( & d1(9) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dc2  ( & d2(0) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dcd2 ( & d2(3) );
+    Map<const Matrix<typename D2::Scalar, 3, 1> >   dLc2 ( & d2(6) );
+    Map<const Quaternion<typename D1::Scalar> >     dq2  ( & d2(9) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_c  ( & diff_d1_d2(0) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_cd ( & diff_d1_d2(3) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_Lc ( & diff_d1_d2(6) );
+    Map<Matrix<typename D3::Scalar, 3, 1> >         diff_o  ( & diff_d1_d2(9) );
+
+    Matrix<typename D4::Scalar, 3, 3> J_do_dq1, J_do_dq2;
+
+    diff(dc1, dcd1, dLc1, dq1, dc2, dcd2, dLc2, dq2, diff_c, diff_cd, diff_Lc, diff_o, J_do_dq1, J_do_dq2);
+
+    /* d = diff(d1, d2) is
+     *   dc = dc2 - dc1
+     *   dcd = dcd2 - dcd1
+     *   dLc = dLc2 - dLc1
+     *   do = Log(dq1.conj * dq2)
+     *
+     * With trivial Jacobians for dp and dv, and:
+     *   J_do_dq1 = - J_l_inv(theta)
+     *   J_do_dq2 =   J_r_inv(theta)
+     */
+
+    J_diff_d1 = - Matrix<typename D4::Scalar, 12, 12>::Identity();  // d(c2  - c1) / d(c1) = - Identity, for instance
+    J_diff_d1.template block<3,3>(9,9) = J_do_dq1;                  // d(R1.tr*R2) / d(R1) = - J_l_inv(theta)
+    J_diff_d2.setIdentity();                                        // d(c2  - c1) / d(c2) =   Identity, for instance
+    J_diff_d2.template block<3,3>(9,9) = J_do_dq2;                  // d(R1.tr*R2) / d(R2) =   J_r_inv(theta)
+}
+
+template<typename D1, typename D2>
+inline Matrix<typename D1::Scalar, 12, 1> diff(const MatrixBase<D1>& d1,
+                                               const MatrixBase<D2>& d2)
+{
+    Matrix<typename D1::Scalar, 12, 1> ret;
+    diff(d1, d2, ret);
+    return ret;
+}
+
+template<typename D1, typename D2>
+inline void body2delta(const MatrixBase<D1>& body,
+                        const typename D1::Scalar& dt,
+                        const typename D1::Scalar& mass,
+                        int nbc,
+                        int dimc,
+                        MatrixBase<D2>& delta)
+{
+    typedef typename D1::Scalar T;
+
+    delta.setZero();
+    Map< Matrix<T, 3, 1>> dc ( & delta(0) );
+    Map< Matrix<T, 3, 1>> dcd( & delta(3) );
+    Map< Matrix<T, 3, 1>> dLc( & delta(6) );
+    Map< Quaternion<T>>   dq ( & delta(9) );
+
+    Vector3d pbc = body.segment(nbc*dimc, 3);
+    Vector3d wb = body.segment(nbc*dimc + 3, 3);
+
+    // general computation for any 3D or 6D contacts
+    for (int i=0; i<nbc; i++){
+        Vector3d pbli = body.segment(nbc*dimc + 6 + i*3, 3);
+        Vector4d qbli_v = body.segment(nbc*dimc + 6 + nbc*3 + i*4, 4); 
+        Quaterniond qbli(qbli_v);        
+        Vector3d fi = body.segment(i*3, 3);
+        
+        dc += qbli*fi;
+        dcd += qbli*fi;
+        dLc += (pbli - pbc).cross(qbli * fi);
+        if (dimc == 6){
+            Vector3d taui = body.segment(3*nbc + i*3, 3);
+            dLc += qbli*taui;
+        }
+    }
+
+    // formulas for 2 6D contacts:
+    // dc = 0.5 * (qbl1*f1 + qbl2*f2) * dt * dt / mass;
+    // dcd =      (qbl1*f1 + qbl2*f2) * dt     / mass;
+    // dLc = (qbl1*tau1 + (pbl1-pbc).cross(qbl1*f1) 
+    //      + qbl2*tau2 + (pbl2-pbc).cross(qbl2*f2)) * dt;
+
+    dc  *= (0.5*dt*dt/mass);
+    dcd *= (dt/mass);
+    dLc *= dt;
+    dq = exp_q(wb * dt);
+}
+
+template<typename D1>
+inline Matrix<typename D1::Scalar, 13, 1> body2delta(const MatrixBase<D1>& body,
+                                                     const typename D1::Scalar& dt,
+                                                     const typename D1::Scalar& mass,
+                                                     int nbc,
+                                                     int dimc)
+{
+    typedef typename D1::Scalar T;
+    Matrix<T, 13, 1> delta;
+    body2delta(body, dt, mass, nbc, dimc, delta);
+
+    return delta;
+}
+
+template<typename D1, typename D2, typename D3>
+inline void body2delta(const MatrixBase<D1>& body,
+                       const typename D1::Scalar& dt,
+                       const typename D1::Scalar& mass,
+                       int nbc,
+                       int dimc,
+                       MatrixBase<D2>& delta,
+                       MatrixBase<D3>& jac_delta_body)
+{
+    Vector3d pbc = body.segment(nbc*dimc, 3);
+    Vector3d wb = body.segment(nbc*dimc + 3, 3);
+
+    jac_delta_body.setZero();
+    for (int i=0; i<nbc; i++){
+        Vector3d pbli = body.segment(nbc*dimc + 6 + i*3, 3);
+        Vector4d qbli_v = body.segment(nbc*dimc + 6 + nbc*3 + i*4, 4); 
+        Quaterniond qbli(qbli_v);
+        Matrix3d bRli = q2R(qbli);
+        Vector3d fi = body.segment(i*3, 3);
+
+        jac_delta_body.template block<3,3>(0,i*3) = 0.5 * bRli * dt * dt / mass;
+        jac_delta_body.template block<3,3>(3,i*3) = bRli * dt / mass;
+        jac_delta_body.template block<3,3>(6,i*3) = skew(pbli - pbc) * bRli * dt;
+        if (dimc == 6){
+            jac_delta_body.template block<3,3>(6,nbc*3 + i*3) = bRli * dt;
+        }
+        jac_delta_body.template block<3,3>(6,nbc*dimc) +=  skew(bRli*fi) * dt;
+    }
+    jac_delta_body.template block<3,3>(9,nbc*dimc+3) =  dt * jac_SO3_right(wb * dt);
+
+    // formulas for 2 6D contacts:
+    // jac_delta_body.template block<3,3>(0,0) = 0.5 * bRl1 * dt * dt / mass;
+    // jac_delta_body.template block<3,3>(0,3) = 0.5 * bRl2 * dt * dt / mass;
+    // jac_delta_body.template block<3,3>(3,0) =       bRl1 * dt / mass;
+    // jac_delta_body.template block<3,3>(3,3) =       bRl2 * dt / mass;
+    // jac_delta_body.template block<3,3>(6,0) =       skew(pbl1 - pbc) * bRl1 * dt;
+    // jac_delta_body.template block<3,3>(6,3) =       skew(pbl2 - pbc) * bRl2 * dt;
+    // jac_delta_body.template block<3,3>(6,6) =       bRl1 * dt;
+    // jac_delta_body.template block<3,3>(6,9) =       bRl2 * dt;
+    // jac_delta_body.template block<3,3>(6,12) =      (skew(bRl1*f1) + skew(bRl2*f2)) * dt;
+    // jac_delta_body.template block<3,3>(9,15) =   dt * jac_SO3_right(wb * dt);
+
+    // TODO: kinematics uncertainties (bRl1, bRl2, bpl1, bpl2) are not implemented currently
+
+    body2delta(body, dt, mass, nbc, dimc, delta);
+}
+
+template<typename D1, typename D2, typename D3>
+inline void debiasData(const MatrixBase<D1>& _data, const MatrixBase<D2>& _bias, int nbc, int dimc, MatrixBase<D3>& _body)
+{
+    MatrixSizeCheck<6 , 1>::check(_bias);
+    _body = _data;
+    _body.template block<6,1>(nbc*dimc,0) = _data.template block<6,1>(nbc*dimc,0) - _bias;
+}
+
+template<typename D1, typename D2, typename D3, typename D4>
+inline void debiasData(const MatrixBase<D1>& _data, const MatrixBase<D2>& _bias, int nbc, int dimc, MatrixBase<D3>& _body, MatrixBase<D4>& _J_body_bias)
+{
+    debiasData(_data, _bias, nbc, dimc, _body);
+    // MatrixSizeCheck<30, 6>::check(_J_body_bias);  // 30 because the 2 last body quantities are quaternions
+    _J_body_bias.setZero();
+    _J_body_bias.template block<3,3>(nbc*dimc, 0) = -Matrix<typename D1::Scalar, 3, 3>::Identity();
+    _J_body_bias.template block<3,3>(nbc*dimc+3, 3) = -Matrix<typename D1::Scalar, 3, 3>::Identity();
+}
+
+
+
+} // namespace bodydynamics
 } // namespace wolf
 
 #endif /* FORCE_TORQUE_DELTA_TOOLS_H_ */
diff --git a/include/bodydynamics/processor/.blank b/include/bodydynamics/processor/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/include/bodydynamics/processor/processor_force_torque_preint.h b/include/bodydynamics/processor/processor_force_torque_preint.h
new file mode 100644
index 0000000000000000000000000000000000000000..4de0c9357ff363e87790e3429aa9e85b171a8777
--- /dev/null
+++ b/include/bodydynamics/processor/processor_force_torque_preint.h
@@ -0,0 +1,140 @@
+//--------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--------
+#ifndef PROCESSOR_FORCE_TORQUE_PREINT_H
+#define PROCESSOR_FORCE_TORQUE_PREINT_H
+
+// Wolf core
+#include <core/processor/processor_motion.h>
+
+namespace wolf {
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorForceTorquePreint);
+
+struct ParamsProcessorForceTorquePreint : public ParamsProcessorMotion
+{
+        std::string sensor_ikin_name;
+        std::string sensor_angvel_name;
+        int nbc;  // Number of contacts
+        int dimc; // Dimension of the contact: 3D == point feet = 3D force, 6D = humanoid = wrench
+
+        ParamsProcessorForceTorquePreint() = default;
+        ParamsProcessorForceTorquePreint(std::string _unique_name, const ParamsServer& _server):
+            ParamsProcessorMotion(_unique_name, _server)
+        {
+            sensor_ikin_name   = _server.getParam<std::string>(_unique_name + "/sensor_ikin_name");
+            sensor_angvel_name = _server.getParam<std::string>(_unique_name + "/sensor_angvel_name");
+            nbc                = _server.getParam<int>(_unique_name + "/nbc_");
+            dimc               = _server.getParam<int>(_unique_name + "/dimc_");
+        }
+        ~ParamsProcessorForceTorquePreint() override = default;
+        std::string print() const override
+        {
+            return ParamsProcessorMotion::print()                    + "\n"
+                    + "sensor_ikin_name: "           + sensor_ikin_name     + "\n"
+                    + "sensor_angvel_name: "         + sensor_angvel_name   + "\n"
+                    + "nbc_: "                       + std::to_string(nbc)                  + "\n"
+                    + "dimc_: "                      + std::to_string(dimc)                 + "\n";
+
+        }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorForceTorquePreint);
+    
+//class
+class ProcessorForceTorquePreint : public ProcessorMotion{
+    public:
+        ProcessorForceTorquePreint(ParamsProcessorForceTorquePreintPtr _params_motion_force_torque_preint);
+        ~ProcessorForceTorquePreint() override;
+        void configure(SensorBasePtr _sensor) override;
+
+        WOLF_PROCESSOR_CREATE(ProcessorForceTorquePreint, ParamsProcessorForceTorquePreint);
+
+    protected:
+        void computeCurrentDelta(const Eigen::VectorXd& _data,
+                                         const Eigen::MatrixXd& _data_cov,
+                                         const Eigen::VectorXd& _calib,
+                                         const double _dt,
+                                         Eigen::VectorXd& _delta,
+                                         Eigen::MatrixXd& _delta_cov,
+                                         Eigen::MatrixXd& _jacobian_calib) const override;
+        void deltaPlusDelta(const Eigen::VectorXd& _delta_preint,
+                                    const Eigen::VectorXd& _delta,
+                                    const double _dt,
+                                    Eigen::VectorXd& _delta_preint_plus_delta) const override;
+        void deltaPlusDelta(const Eigen::VectorXd& _delta_preint,
+                                    const Eigen::VectorXd& _delta,
+                                    const double _dt,
+                                    Eigen::VectorXd& _delta_preint_plus_delta,
+                                    Eigen::MatrixXd& _jacobian_delta_preint,
+                                    Eigen::MatrixXd& _jacobian_delta) const override;
+        void statePlusDelta(const VectorComposite& _x,
+                                const Eigen::VectorXd& _delta,
+                                const double _dt,
+                                VectorComposite& _x_plus_delta ) const override;
+        Eigen::VectorXd deltaZero() const override;
+        Eigen::VectorXd correctDelta(const Eigen::VectorXd& delta_preint,
+                                             const Eigen::VectorXd& delta_step) const override;
+        VectorXd getCalibration (const CaptureBasePtr _capture = nullptr) const override;
+        void setCalibration(const CaptureBasePtr _capture, const VectorXd& _calibration) override;
+
+        bool voteForKeyFrame() const override;
+        CaptureMotionPtr emplaceCapture(const FrameBasePtr& _frame_own,
+                                                const SensorBasePtr& _sensor,
+                                                const TimeStamp& _ts,
+                                                const VectorXd& _data,
+                                                const MatrixXd& _data_cov,
+                                                const VectorXd& _calib,
+                                                const VectorXd& _calib_preint,
+                                                const CaptureBasePtr& _capture_origin) override;
+        FeatureBasePtr emplaceFeature(CaptureMotionPtr _capture_motion) override;
+        FactorBasePtr emplaceFactor(FeatureBasePtr _feature_motion,
+                                            CaptureBasePtr _capture_origin) override;
+
+    private:
+        ParamsProcessorForceTorquePreintPtr params_motion_force_torque_preint_;
+        SensorBasePtr sensor_ikin_;
+        SensorBasePtr sensor_angvel_;
+        int nbc_;
+        int dimc_;
+};
+
+}
+
+/////////////////////////////////////////////////////////
+// IMPLEMENTATION. Put your implementation includes here
+/////////////////////////////////////////////////////////
+
+namespace wolf{
+
+inline void ProcessorForceTorquePreint::configure(SensorBasePtr _sensor)
+{
+    sensor_ikin_   = _sensor->getProblem()->getSensor(params_motion_force_torque_preint_->sensor_ikin_name);
+    sensor_angvel_ = _sensor->getProblem()->getSensor(params_motion_force_torque_preint_->sensor_angvel_name);
+};
+
+inline Eigen::VectorXd ProcessorForceTorquePreint::deltaZero() const
+{
+    return (Eigen::VectorXd(13) << 0,0,0, 0,0,0,  0,0,0,  0,0,0,1 ).finished(); // com, com vel, ang momentum, orientation
+}
+
+} // namespace wolf
+
+#endif // PROCESSOR_FORCE_TORQUE_PREINT_H
diff --git a/include/bodydynamics/processor/processor_inertial_kinematics.h b/include/bodydynamics/processor/processor_inertial_kinematics.h
new file mode 100644
index 0000000000000000000000000000000000000000..468fd9fa27c9b1983b4723f6938c0651632d8374
--- /dev/null
+++ b/include/bodydynamics/processor/processor_inertial_kinematics.h
@@ -0,0 +1,147 @@
+//--------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--------
+#ifndef PROCESSOR_INERTIAL_KINEMATICS_H
+#define PROCESSOR_INERTIAL_KINEMATICS_H
+
+// Wolf
+#include "core/sensor/sensor_base.h"
+#include "core/processor/processor_base.h"
+
+#include "core/processor/processor_base.h"
+#include "core/factor/factor_base.h"
+#include "core/factor/factor_block_difference.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/feature/feature_inertial_kinematics.h"
+#include "bodydynamics/factor/factor_inertial_kinematics.h"
+
+namespace wolf {
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorInertialKinematics);
+
+struct ParamsProcessorInertialKinematics : public ParamsProcessorBase
+{
+    std::string sensor_angvel_name;
+    double std_bp_drift;
+
+    ParamsProcessorInertialKinematics() = default;
+    ParamsProcessorInertialKinematics(std::string _unique_name, const ParamsServer& _server):
+        ParamsProcessorBase(_unique_name, _server)
+    {
+        sensor_angvel_name = _server.getParam<std::string>(_unique_name + "/sensor_angvel_name");
+        std_bp_drift =       _server.getParam<double>(_unique_name + "/std_bp_drift");
+    }
+    ~ParamsProcessorInertialKinematics() override = default;
+    std::string print() const override
+    {
+        return ParamsProcessorBase::print() + "\n"
+                    + "sensor_angvel_name: "       + sensor_angvel_name            + "\n"
+                    + "std_bp_drift: "            + std::to_string(std_bp_drift) + "\n";
+    }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorInertialKinematics);
+    
+//class
+class ProcessorInertialKinematics : public ProcessorBase{
+    public:
+        ProcessorInertialKinematics(ParamsProcessorInertialKinematicsPtr _params_ikin);
+        ~ProcessorInertialKinematics() override = default;
+        WOLF_PROCESSOR_CREATE(ProcessorInertialKinematics, ParamsProcessorInertialKinematics);
+
+        void configure(SensorBasePtr _sensor) override;
+
+        bool createInertialKinematicsFactor(CaptureInertialKinematicsPtr _cap_ikin, CaptureBasePtr _cap_angvel, CaptureBasePtr _cap_ikin_origin);
+
+        void processCapture(CaptureBasePtr _incoming) override;
+        void processKeyFrame(FrameBasePtr _keyframe_ptr) override;
+        bool triggerInCapture(CaptureBasePtr) const override;
+        bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr) const override;
+        bool storeKeyFrame(FrameBasePtr) override;
+        bool storeCapture(CaptureBasePtr) override;
+        bool voteForKeyFrame() const override;
+
+
+    protected:
+        ParamsProcessorInertialKinematicsPtr params_ikin_;
+        CaptureBasePtr cap_origin_ptr_;
+};
+
+
+
+inline bool ProcessorInertialKinematics::triggerInKeyFrame(FrameBasePtr _keyframe_ptr) const
+{
+    return false;
+}
+
+inline bool ProcessorInertialKinematics::triggerInCapture(CaptureBasePtr _capture) const
+{   
+    return true;
+}
+
+inline bool ProcessorInertialKinematics::storeKeyFrame(FrameBasePtr)
+{
+    return true;
+}
+
+inline bool ProcessorInertialKinematics::storeCapture(CaptureBasePtr)
+{
+    return true;
+}
+
+inline bool ProcessorInertialKinematics::voteForKeyFrame() const
+{
+    return false;
+}
+
+inline void ProcessorInertialKinematics::processKeyFrame(FrameBasePtr _keyframe_ptr)
+{
+}
+
+
+//////////////////////////
+// Covariance computations
+//////////////////////////
+
+
+Eigen::Matrix9d computeIKinJac(const Eigen::Vector3d& w_unb,
+                              const Eigen::Vector3d& p_unb,
+                              const Eigen::Matrix3d& Iq);
+
+Eigen::Matrix9d computeIKinCov(const Eigen::Matrix3d& Qp,
+                              const Eigen::Matrix3d& Qv, 
+                              const Eigen::Matrix3d& Qw,
+                              const Eigen::Vector3d& p_unb,
+                              const Eigen::Vector3d& w_unb,
+                              const Eigen::Matrix3d& Iq);
+
+} /* namespace wolf */
+
+/////////////////////////////////////////////////////////
+// IMPLEMENTATION. Put your implementation includes here
+/////////////////////////////////////////////////////////
+
+
+namespace wolf{
+
+} // namespace wolf
+
+#endif // PROCESSOR_INERTIAL_KINEMATICS_H
diff --git a/include/bodydynamics/processor/processor_point_feet_nomove.h b/include/bodydynamics/processor/processor_point_feet_nomove.h
new file mode 100644
index 0000000000000000000000000000000000000000..49c40be86b331397416bc8bf806aac4a4b6f9419
--- /dev/null
+++ b/include/bodydynamics/processor/processor_point_feet_nomove.h
@@ -0,0 +1,116 @@
+//--------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--------
+#ifndef PROCESSOR_POINT_FEET_NOMOVE_H
+#define PROCESSOR_POINT_FEET_NOMOVE_H
+
+// Wolf
+#include "core/sensor/sensor_base.h"
+#include "core/processor/processor_base.h"
+
+#include "bodydynamics/capture/capture_point_feet_nomove.h"
+#include "bodydynamics/sensor/sensor_point_feet_nomove.h"
+#include "bodydynamics/factor/factor_point_feet_nomove.h"
+
+namespace wolf {
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorPointFeetNomove);
+
+struct ParamsProcessorPointFeetNomove : public ParamsProcessorBase
+{
+    ParamsProcessorPointFeetNomove() = default;
+    ParamsProcessorPointFeetNomove(std::string _unique_name, const ParamsServer& _server):
+        ParamsProcessorBase(_unique_name, _server)
+    {
+    }
+    ~ParamsProcessorPointFeetNomove() override = default;
+    std::string print() const override
+    {
+        return "\n" + ParamsProcessorBase::print() + "\n";    
+    }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorPointFeetNomove);
+    
+//class
+class ProcessorPointFeetNomove : public ProcessorBase{
+    public:
+        ProcessorPointFeetNomove(ParamsProcessorPointFeetNomovePtr _params_pfnomove);
+        ~ProcessorPointFeetNomove() override = default;
+        WOLF_PROCESSOR_CREATE(ProcessorPointFeetNomove, ParamsProcessorPointFeetNomove);
+
+        void configure(SensorBasePtr _sensor) override;
+
+        bool createPointFeetNomoveFactor(CapturePointFeetNomovePtr _cap_pfnomove, CaptureBasePtr _cap_angvel, CaptureBasePtr _cap_pfnomove_origin);
+
+        void processCapture(CaptureBasePtr _incoming) override;
+        void processKeyFrame(FrameBasePtr _keyframe_ptr) override;
+        bool triggerInCapture(CaptureBasePtr) const override;
+        bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr) const override;
+        bool storeKeyFrame(FrameBasePtr) override;
+        bool storeCapture(CaptureBasePtr) override;
+        bool voteForKeyFrame() const override;
+
+        void createFactorIfNecessary();
+
+
+    protected:
+        ParamsProcessorPointFeetNomovePtr params_pfnomove_;
+};
+
+
+
+inline bool ProcessorPointFeetNomove::triggerInKeyFrame(FrameBasePtr _keyframe_ptr) const
+{
+    return true;
+}
+
+inline bool ProcessorPointFeetNomove::triggerInCapture(CaptureBasePtr _capture) const
+{   
+    return true;
+}
+
+inline bool ProcessorPointFeetNomove::storeKeyFrame(FrameBasePtr)
+{
+    return true;
+}
+
+inline bool ProcessorPointFeetNomove::storeCapture(CaptureBasePtr)
+{
+    return true;
+}
+
+inline bool ProcessorPointFeetNomove::voteForKeyFrame() const
+{
+    return false;
+}
+
+} /* namespace wolf */
+
+/////////////////////////////////////////////////////////
+// IMPLEMENTATION. Put your implementation includes here
+/////////////////////////////////////////////////////////
+
+
+namespace wolf{
+
+} // namespace wolf
+
+#endif // PROCESSOR_POINT_FEET_NOMOVE_H
diff --git a/include/bodydynamics/sensor/.blank b/include/bodydynamics/sensor/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/include/bodydynamics/sensor/sensor_force_torque.h b/include/bodydynamics/sensor/sensor_force_torque.h
new file mode 100644
index 0000000000000000000000000000000000000000..eea790dd1b52e104b730e9a7bd20a4d0f52a31af
--- /dev/null
+++ b/include/bodydynamics/sensor/sensor_force_torque.h
@@ -0,0 +1,100 @@
+//--------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--------
+#ifndef SENSOR_FORCE_TORQUE_H
+#define SENSOR_FORCE_TORQUE_H
+
+//wolf includes
+#include "core/sensor/sensor_base.h"
+#include "core/utils/params_server.h"
+#include <iostream>
+
+namespace wolf {
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsSensorForceTorque);
+
+
+struct ParamsSensorForceTorque : public ParamsSensorBase
+{
+        //noise std dev
+        double mass          = 10;      // total mass of the robot (kg)
+        double std_f   = 0.001;   // standard deviation of the force sensors (N)
+        double std_tau = 0.001;   // standard deviation of the torque sensors (N.m)
+
+        ParamsSensorForceTorque() = default;
+        ParamsSensorForceTorque(std::string _unique_name, const ParamsServer& _server):
+            ParamsSensorBase(_unique_name, _server)
+        {
+            mass                 = _server.getParam<double>(_unique_name + "/mass");
+            std_f          = _server.getParam<double>(_unique_name + "/std_f");
+            std_tau        = _server.getParam<double>(_unique_name + "/std_tau");
+        }
+        ~ParamsSensorForceTorque() override = default;
+        std::string print() const override
+        {
+            return ParamsSensorBase::print()                        + "\n"
+                    + "mass: "          + std::to_string(mass)           + "\n"
+                    + "std_f: "   + std::to_string(std_f)    + "\n"
+                    + "std_tau: " + std::to_string(std_tau)  + "\n";
+        }
+};
+
+WOLF_PTR_TYPEDEFS(SensorForceTorque);
+
+class SensorForceTorque : public SensorBase
+{
+
+    protected:
+        //noise std dev
+        double mass_;            // total mass of the robot (kg)
+        double std_f_;     // standard deviation of the force sensors (N)
+        double std_tau_;   // standard deviation of the torque sensors (N.m)
+
+    public:
+
+        SensorForceTorque(const Eigen::VectorXd& _extrinsics, ParamsSensorForceTorquePtr _params);
+        ~SensorForceTorque() override = default;
+
+        WOLF_SENSOR_CREATE(SensorForceTorque, ParamsSensorForceTorque, 0);
+
+        double getMass() const;
+        double getForceNoiseStd() const;
+        double getTorqueNoiseStd() const;
+};
+
+inline double SensorForceTorque::getMass() const
+{
+    return mass_;
+}
+
+inline double SensorForceTorque::getForceNoiseStd() const
+{
+    return std_f_;
+}
+
+inline double SensorForceTorque::getTorqueNoiseStd() const
+{
+    return std_tau_;
+}
+
+} // namespace wolf
+
+#endif // SENSOR_FORCE_TORQUE_H
diff --git a/include/bodydynamics/sensor/sensor_inertial_kinematics.h b/include/bodydynamics/sensor/sensor_inertial_kinematics.h
new file mode 100644
index 0000000000000000000000000000000000000000..76e74adc98c6934efae76c27c4c0efea6527db3e
--- /dev/null
+++ b/include/bodydynamics/sensor/sensor_inertial_kinematics.h
@@ -0,0 +1,88 @@
+//--------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--------
+#ifndef SENSOR_INERTIAL_KINEMATICS_H
+#define SENSOR_INERTIAL_KINEMATICS_H
+
+//wolf includes
+#include "core/sensor/sensor_base.h"
+#include "core/utils/params_server.h"
+#include <iostream>
+
+namespace wolf {
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsSensorInertialKinematics);
+
+
+struct ParamsSensorInertialKinematics : public ParamsSensorBase
+{
+        //noise std dev
+        double std_pbc;   // standard deviation of the com position measurement relative to the base frame (m)
+        double std_vbc;   // standard deviation of the com velocity measurement relative to the base frame (m/s)
+
+        ParamsSensorInertialKinematics() = default;
+        ParamsSensorInertialKinematics(std::string _unique_name, const ParamsServer& _server):
+            ParamsSensorBase(_unique_name, _server)
+        {
+            std_pbc          = _server.getParam<double>(_unique_name + "/std_pbc");
+            std_vbc          = _server.getParam<double>(_unique_name + "/std_vbc");
+        }
+        ~ParamsSensorInertialKinematics() override = default;
+        std::string print() const override
+        {
+            return ParamsSensorBase::print()                                           + "\n"
+                    + "std_pbc: "           + std::to_string(std_pbc)           + "\n"
+                    + "std_vbc: "           + std::to_string(std_vbc)           + "\n";
+        }
+};
+
+WOLF_PTR_TYPEDEFS(SensorInertialKinematics);
+
+class SensorInertialKinematics : public SensorBase
+{
+
+    protected:
+        ParamsSensorInertialKinematicsPtr intr_ikin_;
+
+    public:
+
+        SensorInertialKinematics(const Eigen::VectorXd& _extrinsics, ParamsSensorInertialKinematicsPtr _intr_ikin);
+        ~SensorInertialKinematics() override;
+
+        WOLF_SENSOR_CREATE(SensorInertialKinematics, ParamsSensorInertialKinematics, 0);
+
+        double getPbcNoiseStd() const;
+        double getVbcNoiseStd() const;
+};
+
+inline double SensorInertialKinematics::getPbcNoiseStd() const
+{
+    return intr_ikin_->std_pbc;
+}
+
+inline double SensorInertialKinematics::getVbcNoiseStd() const
+{
+    return intr_ikin_->std_vbc;
+}
+
+} // namespace wolf
+
+#endif // SENSOR_INERTIAL_KINEMATICS_H
diff --git a/include/bodydynamics/sensor/sensor_point_feet_nomove.h b/include/bodydynamics/sensor/sensor_point_feet_nomove.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f58ad65e96d1ca4c249cdd23a4f8e88b03f8060
--- /dev/null
+++ b/include/bodydynamics/sensor/sensor_point_feet_nomove.h
@@ -0,0 +1,80 @@
+//--------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--------
+#ifndef SENSOR_POINT_FEET_NOMOVE_H
+#define SENSOR_POINT_FEET_NOMOVE_H
+
+//wolf includes
+#include "core/sensor/sensor_base.h"
+#include "core/utils/params_server.h"
+#include <iostream>
+
+namespace wolf {
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsSensorPointFeetNomove);
+
+
+struct ParamsSensorPointFeetNomove : public ParamsSensorBase
+{
+        //noise std dev
+        double std_foot_nomove_;   // standard deviation on the assumption that the feet are not moving when in contact
+
+        ParamsSensorPointFeetNomove() = default;
+        ParamsSensorPointFeetNomove(std::string _unique_name, const ParamsServer& _server):
+            ParamsSensorBase(_unique_name, _server)
+        {
+            std_foot_nomove_          = _server.getParam<double>(_unique_name + "/std_foot_nomove");
+        }
+        ~ParamsSensorPointFeetNomove() override = default;
+        std::string print() const override
+        {
+            return "\n" + ParamsSensorBase::print()                                           + "\n"
+                    + "std_foot_nomove: "           + std::to_string(std_foot_nomove_)        + "\n";
+        }
+};
+
+WOLF_PTR_TYPEDEFS(SensorPointFeetNomove);
+
+class SensorPointFeetNomove : public SensorBase
+{
+
+    protected:
+        Matrix3d cov_foot_nomove_;
+
+    public:
+
+        SensorPointFeetNomove(const Eigen::VectorXd& _extrinsics, const ParamsSensorPointFeetNomovePtr& _intr_pfnm);
+        ~SensorPointFeetNomove() override;
+
+        WOLF_SENSOR_CREATE(SensorPointFeetNomove, ParamsSensorPointFeetNomove, 0);
+
+        Matrix3d getCovFootNomove() const;
+};
+
+inline Matrix3d SensorPointFeetNomove::getCovFootNomove() const
+{
+    return cov_foot_nomove_;
+}
+
+
+} // namespace wolf
+
+#endif // SENSOR_POINT_FEET_NOMOVE_H
diff --git a/license_header_2022.txt b/license_header_2022.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0c987025f9dba3e7af993051b9bdf4b5ff400e0d
--- /dev/null
+++ b/license_header_2022.txt
@@ -0,0 +1,17 @@
+// 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/>.
diff --git a/src/capture/.blank b/src/capture/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/capture/capture_force_torque_preint.cpp b/src/capture/capture_force_torque_preint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..83e145e4b85a4895a81dce2251fcddd10681460d
--- /dev/null
+++ b/src/capture/capture_force_torque_preint.cpp
@@ -0,0 +1,49 @@
+//--------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--------
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "core/state_block/state_quaternion.h"
+
+namespace wolf {
+
+CaptureForceTorquePreint::CaptureForceTorquePreint(
+                    const TimeStamp& _init_ts,
+                    SensorBasePtr _sensor_ptr,
+                    CaptureInertialKinematicsPtr _capture_IK_ptr,  // to get the pbc bias
+                    CaptureMotionPtr _capture_motion_ptr,          // to get the gyro bias
+                    const Eigen::VectorXd& _data,
+                    const Eigen::MatrixXd& _data_cov,  // TODO: no uncertainty from kinematics
+                    CaptureBasePtr _capture_origin_ptr) :
+                CaptureMotion("CaptureForceTorquePreint", _init_ts, _sensor_ptr, _data, _data_cov,
+                              _capture_origin_ptr, nullptr, nullptr, nullptr),
+                cap_ikin_other_(_capture_IK_ptr),
+                cap_gyro_other_(_capture_motion_ptr)
+{
+}
+
+CaptureForceTorquePreint::~CaptureForceTorquePreint()
+{
+
+}
+
+} //namespace wolf
diff --git a/src/capture/capture_inertial_kinematics.cpp b/src/capture/capture_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3640f6b966c49cde3ed4fb8f3db94856fbf14bc7
--- /dev/null
+++ b/src/capture/capture_inertial_kinematics.cpp
@@ -0,0 +1,72 @@
+//--------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--------
+#include "core/capture/capture_base.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "core/state_block/state_quaternion.h"
+
+namespace wolf {
+
+CaptureInertialKinematics::CaptureInertialKinematics(const TimeStamp& _init_ts,
+                                                     SensorBasePtr _sensor_ptr,
+                                                     const Eigen::Vector9d& _data,    // pbc, vbc, wb
+                                                     const Eigen::Matrix3d & _B_I_q,  // Centroidal inertia matrix expressed in body frame
+                                                     const Eigen::Vector3d & _B_Lc_m, // Centroidal angular momentum expressed in body frame
+                                                     const Vector3d& _bias_pbc) :
+                                                CaptureBase("CaptureInertialKinematics",
+                                                               _init_ts,
+                                                               _sensor_ptr,
+                                                               nullptr,
+                                                               nullptr,
+                                                               std::make_shared<StateBlock>(_bias_pbc, false)),
+                                                data_(_data),
+                                                B_I_q_(_B_I_q),
+                                                B_Lc_m_(_B_Lc_m)
+{
+    //
+}
+
+
+CaptureInertialKinematics::CaptureInertialKinematics(const TimeStamp& _init_ts,
+                                                     SensorBasePtr _sensor_ptr,
+                                                     const Eigen::Vector9d& _data,      // pbc, vbc, wb
+                                                     const Eigen::Matrix3d & _B_I_q,    // Centroidal inertia matrix expressed in body frame
+                                                     const Eigen::Vector3d & _B_Lc_m) : // Centroidal angular momentum expressed in body frame
+                                                CaptureBase("CaptureInertialKinematics",
+                                                               _init_ts,
+                                                               _sensor_ptr,
+                                                               nullptr,
+                                                               nullptr,
+                                                               std::make_shared<StateBlock>(3, false)),
+                                                data_(_data),
+                                                B_I_q_(_B_I_q),
+                                                B_Lc_m_(_B_Lc_m)
+{
+    //
+}
+
+CaptureInertialKinematics::~CaptureInertialKinematics()
+{
+    //
+}
+
+} //namespace wolf
diff --git a/src/capture/capture_leg_odom.cpp b/src/capture/capture_leg_odom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..69336fa53b1e686d3434bf3a0e821f0315f844c1
--- /dev/null
+++ b/src/capture/capture_leg_odom.cpp
@@ -0,0 +1,135 @@
+//--------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--------
+/**
+ * \file capture_leg_odom.cpp
+ *
+ *  Created on: May 12, 2020
+ *      \author: mfourmy
+ */
+
+
+#include "core/math/rotations.h"
+#include "core/capture/capture_base.h"
+#include "bodydynamics/capture/capture_leg_odom.h"
+
+
+namespace wolf {
+
+// bm: base at time t-dt
+// b: base at current time
+CaptureLegOdom::CaptureLegOdom(const TimeStamp& _init_ts,
+                               SensorBasePtr _sensor_ptr,
+                               const Eigen::MatrixXd& _data_kin, // cols: b_p_bl1m, b_p_bl1, b_p_bl2m, b_p_bl2..., rows: xyz
+                               Eigen::Matrix6d _data_cov, // for the moment only 6x6 mat, see if more involved computations are necessary (contact confidence...)
+                               double dt,
+                               CaptureLegOdomType lo_type) : 
+                                   CaptureMotion("CaptureLegOdom", _init_ts, _sensor_ptr, 
+                                                 Eigen::Vector7d::Zero(), Eigen::Matrix6d::Identity(),  // dummy data and data covariance filled in the constructor
+                                                 nullptr, nullptr, nullptr, nullptr)
+{
+    Vector7d data;
+    if (_data_kin.cols() < 2){
+        // no feet in contact, flying robot -> dummy data with high cov
+        data << 0,0,0, 0,0,0,1;
+        _data_cov = pow(1000, 2) * _data_cov;
+        return;
+    }
+
+    if (lo_type == CaptureLegOdomType::POINT_FEET_RELATIVE_KIN){
+        assert(_data_kin.rows() == 3);
+
+        // relative base-feet position + angular velocity
+        Eigen::Vector3d i_omg_oi = _data_kin.rightCols<1>();
+        Eigen::Quaterniond bm_q_b (v2q(i_omg_oi*dt));
+        std::vector<Vector3d> bm_p_bmb_vec;
+        for (unsigned int i=0; i < (_data_kin.cols()-1); i+=2){
+            Eigen::Vector3d bm_p_bml = _data_kin.col(i);
+            Eigen::Vector3d b_p_bl   = _data_kin.col(i+1);
+            bm_p_bmb_vec.push_back(bm_p_bml - bm_q_b * b_p_bl);
+        }
+        // for the moment simple mean, to be improved
+        Vector3d bm_p_bmb = Vector3d::Zero();
+        for (unsigned int i=0; i < bm_p_bmb_vec.size(); i++){
+            bm_p_bmb += bm_p_bmb_vec[i];
+        }
+
+        bm_p_bmb /= bm_p_bmb_vec.size();
+
+        data.head<3>() = bm_p_bmb; 
+        data.tail<4>() = bm_q_b.coeffs(); 
+
+    }
+    else if(lo_type == CaptureLegOdomType::POINT_FEET_DIFFERENTIAL_KIN){
+        assert(_data_kin.rows() == 10);
+        // TODO: document data matrix organization
+
+        std::vector<Vector3d> b_v_ob_vec;
+        Vector3d i_omg_oi = _data_kin.block<3,1>(0, _data_kin.cols()-1);  // retrieve angular vel first
+        Eigen::Quaterniond bm_q_b (v2q(i_omg_oi*dt));
+        for (unsigned int i=0; i < _data_kin.cols()-1; i++){
+            Vector3d b_p_bl = _data_kin.block<3,1>(0,i);
+            Quaterniond b_q_l(_data_kin.block<4,1>(3,i));
+            Vector3d l_v_bl = _data_kin.block<3,1>(7,i);
+            Vector3d b_v_ob = b_p_bl.cross(i_omg_oi) - b_q_l * l_v_bl;
+            b_v_ob_vec.push_back(b_v_ob);
+        }
+
+        // Simple mean for the moment
+        Vector3d b_v_ob_mean = Vector3d::Zero();
+        for (unsigned int i=0; i < b_v_ob_vec.size(); i++){
+            b_v_ob_mean += b_v_ob_vec[i];
+        }
+        b_v_ob_mean /= b_v_ob_vec.size();
+
+        data.head<3>() = b_v_ob_mean*dt; 
+        data.tail<4>() = bm_q_b.coeffs(); 
+    }
+    else if(lo_type == CaptureLegOdomType::HUMANOID_FEET_RELATIVE_KIN){
+        assert(_data_kin.rows() == 7);
+        // relative transformations
+        // most simple option: odometry of one of the feet
+        Eigen::Vector7d bm_pose_l = _data_kin.col(0);
+        Eigen::Vector7d b_pose_l = _data_kin.col(1);
+        Eigen::Quaterniond bm_q_l(bm_pose_l.tail<4>());
+        Eigen::Quaterniond b_q_l(b_pose_l.tail<4>());
+        Eigen::Vector3d bm_p_bml(bm_pose_l.head<3>());
+        Eigen::Vector3d b_p_bl(b_pose_l.head<3>());
+
+        Eigen::Quaterniond bm_q_b(bm_q_l * b_q_l.inverse());
+        Eigen::Vector3d bm_p_bmb = bm_p_bml - bm_q_b * b_p_bl;
+
+        data.head<3>() = bm_p_bmb; 
+        data.tail<4>() = bm_q_b.coeffs(); 
+    }
+    else {
+        std::cout << "Unkown CaptureLegOdomType! ERROR (TODO)" << std::endl;
+    }
+    setData(data);
+    setDataCovariance(_data_cov);
+}
+
+CaptureLegOdom::~CaptureLegOdom()
+{
+    //
+}
+
+} //namespace wolf
diff --git a/src/capture/capture_point_feet_nomove.cpp b/src/capture/capture_point_feet_nomove.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3d4dca45ced860903bfdfabc3f4a86e088b63859
--- /dev/null
+++ b/src/capture/capture_point_feet_nomove.cpp
@@ -0,0 +1,54 @@
+//--------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--------
+/**
+ * \file capture_point_feet_nomove.cpp
+ *
+ *  Created on: Oct 23, 2020
+ *      \author: mfourmy
+ */
+
+
+#include "core/capture/capture_base.h"
+#include "bodydynamics/capture/capture_point_feet_nomove.h"
+
+
+namespace wolf {
+
+// bm: base at time t-dt
+// b: base at current time
+CapturePointFeetNomove::CapturePointFeetNomove(
+                            const TimeStamp& _init_ts,
+                            SensorBasePtr _sensor_ptr,
+                            const std::unordered_map<int, Eigen::Vector3d>& kin_incontact
+                            ) :
+                        CaptureBase("CapturePointFeetNomove",
+                                    _init_ts, 
+                                    _sensor_ptr),                      
+                        kin_incontact_(kin_incontact)
+{}
+
+CapturePointFeetNomove::~CapturePointFeetNomove()
+{
+    //
+}
+
+} //namespace wolf
diff --git a/src/factor/.blank b/src/factor/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/feature/feature_force_torque.cpp b/src/feature/feature_force_torque.cpp
index 343e7dc0e97dd567610397c8001e3783da1cb7e1..83c12548fd923e7621b56465ecedbcc12b673500 100644
--- a/src/feature/feature_force_torque.cpp
+++ b/src/feature/feature_force_torque.cpp
@@ -1,3 +1,24 @@
+//--------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--------
 #include "bodydynamics/feature/feature_force_torque.h"
 
 namespace wolf {
@@ -5,29 +26,29 @@ namespace wolf {
 WOLF_PTR_TYPEDEFS(FeatureForceTorque);
 
 FeatureForceTorque::FeatureForceTorque(
-    const double& _dt,
-    const double& _mass,
-    const Eigen::Vector6d& _forces_meas,
-    const Eigen::Vector6d& _torques_meas,
-    const Eigen::Vector3d& _pbc_meas,
-    const Eigen::Matrix<double,14,1>& _kin_meas,
-    const Eigen::Matrix6d& _cov_forces_meas,
-    const Eigen::Matrix6d& _cov_torques_meas,
-    const Eigen::Matrix3d& _cov_pbc_meas,
-    const Eigen::Matrix<double,14,14>& _cov_kin_meas) :
-FeatureBase("FORCETORQUE", 42*Eigen::Matrix1d::Identity(), 42*Eigen::Matrix1d::Identity(), UNCERTAINTY_IS_COVARIANCE),  // Pass dummmy values -> meas and cov not stored where it is usually because too big == error prone
-dt_(_dt),
-mass_(_mass),
-forces_meas_(_forces_meas),
-torques_meas_(_torques_meas),
-pbc_meas_(_pbc_meas),
-kin_meas_(_kin_meas),
-cov_forces_meas_(_cov_forces_meas),
-cov_torques_meas_(_cov_torques_meas),
-cov_pbc_meas_(_cov_pbc_meas),
-cov_kin_meas_(_cov_kin_meas)
+            const double& _dt,
+            const double& _mass,
+            const Eigen::Vector6d& _forces_meas,
+            const Eigen::Vector6d& _torques_meas,
+            const Eigen::Vector3d& _pbc_meas,
+            const Eigen::Matrix<double,14,1>& _kin_meas,
+            const Eigen::Matrix6d& _cov_forces_meas,
+            const Eigen::Matrix6d& _cov_torques_meas,
+            const Eigen::Matrix3d& _cov_pbc_meas,
+            const Eigen::Matrix<double,14,14>& _cov_kin_meas) :
+        FeatureBase("FORCETORQUE", 42*Eigen::Matrix1d::Identity(), 42*Eigen::Matrix1d::Identity(), UNCERTAINTY_IS_COVARIANCE),  // Pass dummmy values -> we are bypassing the way meas and cov is usually stored because too many of them == vector is too big -> error prone
+        dt_(_dt),
+        mass_(_mass),
+        forces_meas_(_forces_meas),
+        torques_meas_(_torques_meas),
+        pbc_meas_(_pbc_meas),
+        kin_meas_(_kin_meas),
+        cov_forces_meas_(_cov_forces_meas),
+        cov_torques_meas_(_cov_torques_meas),
+        cov_pbc_meas_(_cov_pbc_meas),
+        cov_kin_meas_(_cov_kin_meas)
 {}
 
 FeatureForceTorque::~FeatureForceTorque(){}
 
-} // namespace wolf
\ No newline at end of file
+} // namespace wolf
diff --git a/src/feature/feature_force_torque_preint.cpp b/src/feature/feature_force_torque_preint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a21081d827843645a62c75508b3ceda10794c71
--- /dev/null
+++ b/src/feature/feature_force_torque_preint.cpp
@@ -0,0 +1,36 @@
+//--------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--------
+#include "bodydynamics/feature/feature_force_torque_preint.h"
+namespace wolf {
+
+FeatureForceTorquePreint::FeatureForceTorquePreint(const Eigen::VectorXd& _delta_preintegrated,
+                       const Eigen::MatrixXd& _delta_preintegrated_covariance,
+                       const Eigen::Vector6d& _biases_preint,
+                       const Eigen::Matrix<double,12,6>& _J_delta_biases) :
+    FeatureBase("FeatureForceTorquePreint", _delta_preintegrated, _delta_preintegrated_covariance),
+    pbc_bias_preint_(_biases_preint.head<3>()),
+    gyro_bias_preint_(_biases_preint.tail<3>()),
+    J_delta_bias_(_J_delta_biases)
+{
+}
+
+} // namespace wolf
diff --git a/src/feature/feature_inertial_kinematics.cpp b/src/feature/feature_inertial_kinematics.cpp
index 68c11faab9a60c64c7b3554ddd7a1e623b57f829..8f80b0ddc96b0029211ed5b66940c1a5af6db117 100644
--- a/src/feature/feature_inertial_kinematics.cpp
+++ b/src/feature/feature_inertial_kinematics.cpp
@@ -1,23 +1,43 @@
+//--------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--------
 
 
 #include "bodydynamics/feature/feature_inertial_kinematics.h"
-
+#include "Eigen/Dense"
+#include "core/math/rotations.h"
 
 namespace wolf {
 
 WOLF_PTR_TYPEDEFS(FeatureInertialKinematics);
 
 FeatureInertialKinematics::FeatureInertialKinematics(
-    const Eigen::Vector9d & _measurement, 
-    const Eigen::Matrix9d & _meas_uncertainty,
+    const Eigen::Vector9d & _meas_pbc_vbc_w,
+    const Eigen::Matrix9d & _Qerr,
     const Eigen::Matrix3d & _BIq, 
     const Eigen::Vector3d & _BLcm,
     UncertaintyType _uncertainty_type) :
-    FeatureBase("INERTIALKINEMATICS", _measurement, _meas_uncertainty, _uncertainty_type),
+    FeatureBase("InertialKinematics", _meas_pbc_vbc_w, _Qerr, _uncertainty_type),
     BIq_(_BIq),
     BLcm_(_BLcm)
 {}
 
-FeatureInertialKinematics::~FeatureInertialKinematics(){}
-
-} // namespace wolf
\ No newline at end of file
+} // namespace wolf
diff --git a/src/processor/.blank b/src/processor/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/processor/processor_force_torque_preint.cpp b/src/processor/processor_force_torque_preint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..994b91a49c911dc5835b1d642ead04d2f5ca0223
--- /dev/null
+++ b/src/processor/processor_force_torque_preint.cpp
@@ -0,0 +1,272 @@
+//--------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
+#include "bodydynamics/math/force_torque_delta_tools.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/feature/feature_force_torque_preint.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+#include "bodydynamics/factor/factor_force_torque_preint.h"
+
+namespace wolf {
+
+int compute_data_size(int nbc, int dimc){
+    // compute the size of the data/body vectors used by processorMotion API depending
+    // on the number of contacts (nbc) and contact dimension (dimc)
+    // result: forces (+torques for 6D contacts) + pbc + wb + contact/base positions + contact/base orientations 
+    return nbc*dimc + 3 + 3 + nbc*3 + nbc*4;
+}
+
+using namespace Eigen;
+
+ProcessorForceTorquePreint::ProcessorForceTorquePreint(ParamsProcessorForceTorquePreintPtr _params_motion_force_torque_preint) :
+        ProcessorMotion("ProcessorForceTorquePreint", "CDLO", 3, 13, 13, 12, 
+                        compute_data_size(_params_motion_force_torque_preint->nbc, _params_motion_force_torque_preint->dimc), 
+                        6, _params_motion_force_torque_preint),
+        params_motion_force_torque_preint_(std::make_shared<ParamsProcessorForceTorquePreint>(*_params_motion_force_torque_preint)),
+        nbc_(_params_motion_force_torque_preint->nbc),
+        dimc_(_params_motion_force_torque_preint->dimc)
+
+{
+    //
+}
+
+ProcessorForceTorquePreint::~ProcessorForceTorquePreint()
+{
+    //
+}
+
+bool ProcessorForceTorquePreint::voteForKeyFrame() const
+{
+    // time span
+    if (getBuffer().back().ts_ - getBuffer().front().ts_ > params_motion_force_torque_preint_->max_time_span)
+    {
+        WOLF_DEBUG( "PM: vote: time span" );
+        return true;
+    }
+    // buffer length
+    if (getBuffer().size() > params_motion_force_torque_preint_->max_buff_length)
+    {
+        WOLF_DEBUG( "PM: vote: buffer length" );
+        return true;
+    }
+    
+    // Some other measure of movement?
+
+    //WOLF_DEBUG( "PM: do not vote" );
+    return false;
+}
+
+
+CaptureMotionPtr ProcessorForceTorquePreint::emplaceCapture(const FrameBasePtr& _frame_own,
+                                              const SensorBasePtr& _sensor,
+                                              const TimeStamp& _ts,
+                                              const VectorXd& _data,
+                                              const MatrixXd& _data_cov,
+                                              const VectorXd& _calib,
+                                              const VectorXd& _calib_preint,
+                                              const CaptureBasePtr& _capture_origin)
+{
+
+    // Here we have to retrieve the origin capture no
+    // !! PROBLEM:
+    // when doing setOrigin, emplaceCapture is called 2 times
+    // - first on the first KF (usually created by setPrior), this one contains the biases
+    // - secondly on the inner frame (last) which does not contains other captures
+    auto capture_ikin = _frame_own->getCaptureOf(sensor_ikin_);
+    auto capture_angvel = _frame_own->getCaptureOf(sensor_angvel_);
+    if ((capture_ikin == nullptr) || (capture_angvel == nullptr)){
+        // We have to retrieve the bias from a former Keyframe: origin
+        capture_ikin = origin_ptr_->getFrame()->getCaptureOf(sensor_ikin_);
+        capture_angvel = origin_ptr_->getFrame()->getCaptureOf(sensor_angvel_); 
+    }
+    auto cap = CaptureBase::emplace<CaptureForceTorquePreint>(
+                    _frame_own,
+                    _ts,
+                    _sensor,
+                    std::static_pointer_cast<CaptureInertialKinematics>(capture_ikin),
+                    std::static_pointer_cast<CaptureMotion>(capture_angvel),
+                    _data,
+                    _data_cov,
+                    _capture_origin);
+
+    // Cannot be recovered from the _calib and _calib_preint which are initialized using calib_size_
+    // which is zero in our case since the calibration stateblocks are not actually in the FTPreint sensor/captures
+
+    auto cap_ftpreint = std::static_pointer_cast<CaptureForceTorquePreint>(cap);
+    Vector6d calib = getCalibration(cap_ftpreint);
+    setCalibration(cap_ftpreint, calib);
+    cap_ftpreint->setCalibrationPreint(calib);
+
+    return cap;
+}
+
+FeatureBasePtr ProcessorForceTorquePreint::emplaceFeature(CaptureMotionPtr _capture_motion)
+{
+    // Retrieving the origin capture and getting the bias from here should be done here?
+    auto feature = FeatureBase::emplace<FeatureForceTorquePreint>(_capture_motion,
+                                                    _capture_motion->getBuffer().back().delta_integr_,
+                                                    _capture_motion->getBuffer().back().delta_integr_cov_ + unmeasured_perturbation_cov_,
+                                                    _capture_motion->getCalibrationPreint(),
+                                                    _capture_motion->getBuffer().back().jacobian_calib_);
+    return feature;
+}
+
+Eigen::VectorXd ProcessorForceTorquePreint::correctDelta (const Eigen::VectorXd& delta_preint,
+                                                          const Eigen::VectorXd& delta_step) const
+{
+    return bodydynamics::plus(delta_preint, delta_step);
+}
+
+VectorXd ProcessorForceTorquePreint::getCalibration (const CaptureBasePtr _capture) const
+{
+
+    VectorXd bias_vec(6);
+
+    if (_capture) // access from capture is quicker
+    {
+        CaptureForceTorquePreintPtr cap_ft(std::static_pointer_cast<CaptureForceTorquePreint>(_capture));
+
+        // get calib part from Ikin capture
+        bias_vec.segment<3>(0) = cap_ft->getIkinCaptureOther()->getSensorIntrinsic()->getState();
+
+        // get calib part from IMU capture
+        bias_vec.segment<3>(3) = cap_ft->getGyroCaptureOther()->getSensorIntrinsic()->getState().tail<3>();  // TODO: only valib for IMU bias sb or any other of the structure [XXXX, gyro_bias]
+    }
+    else // access from sensor is slower
+    {
+        // get calib part from Ikin capture
+        bias_vec.segment<3>(0) = sensor_ikin_->getIntrinsic()->getState();
+
+        // get calib part from IMU capture
+        bias_vec.segment<3>(3) = sensor_angvel_->getIntrinsic()->getState().tail<3>();  // TODO: only valib for IMU bias sb or any other of the structure [XXXX, gyro_bias]
+    }
+    return bias_vec;
+}
+
+void ProcessorForceTorquePreint::setCalibration (const CaptureBasePtr _capture, const VectorXd& _calibration)
+{
+    CaptureForceTorquePreintPtr cap_ft(std::static_pointer_cast<CaptureForceTorquePreint>(_capture));
+
+    // set calib part in Ikin capture
+    cap_ft->getIkinCaptureOther()->getSensorIntrinsic()->setState(_calibration.head<3>());
+
+    // set calib part in IMU capture
+    Vector6d calib_imu  = cap_ft->getGyroCaptureOther()->getSensorIntrinsic()->getState();
+    calib_imu.tail<3>() = _calibration.tail<3>();
+
+    cap_ft->getGyroCaptureOther()->getSensorIntrinsic()->setState(calib_imu);
+}
+
+FactorBasePtr ProcessorForceTorquePreint::emplaceFactor(FeatureBasePtr _feature_motion, CaptureBasePtr _capture_origin)
+{
+    CaptureForceTorquePreintPtr cap_ftpreint_origin = std::static_pointer_cast<CaptureForceTorquePreint>(_capture_origin);
+    FeatureForceTorquePreintPtr ftr_ftpreint = std::static_pointer_cast<FeatureForceTorquePreint>(_feature_motion);
+    CaptureForceTorquePreintPtr cap_ftpreint_new = std::static_pointer_cast<CaptureForceTorquePreint>(ftr_ftpreint->getCapture());
+
+    auto fac_ftpreint = FactorBase::emplace<FactorForceTorquePreint>(
+            ftr_ftpreint,
+            ftr_ftpreint,
+            cap_ftpreint_origin,
+            shared_from_this(),
+            cap_ftpreint_origin->getIkinCaptureOther(),  // to retrieve sb_bp1
+            cap_ftpreint_origin->getGyroCaptureOther(),  // to retrieve sb_w1
+            false);
+
+    return fac_ftpreint;
+}
+
+void ProcessorForceTorquePreint::computeCurrentDelta(
+                                       const Eigen::VectorXd& _data,
+                                       const Eigen::MatrixXd& _data_cov,
+                                       const Eigen::VectorXd& _calib,
+                                       const double _dt,
+                                       Eigen::VectorXd& _delta,
+                                       Eigen::MatrixXd& _delta_cov,
+                                       Eigen::MatrixXd& _jac_delta_calib) const
+{
+    assert(_data.size() == data_size_ && "Wrong data size!");
+
+    // create delta
+    MatrixXd jac_body_bias(data_size_-nbc_,6);
+    VectorXd body(data_size_);
+    bodydynamics::debiasData(_data, _calib, nbc_, dimc_, body, jac_body_bias);
+
+    MatrixXd jac_delta_body(12,data_size_-nbc_);
+    bodydynamics::body2delta(body, _dt, std::static_pointer_cast<SensorForceTorque>(getSensor())->getMass(), 
+                             nbc_, dimc_,
+                             _delta, jac_delta_body); // Jacobians tested in bodydynamics_tools
+
+    // [f], [tau], pbc, wb
+    MatrixXd jac_delta_body_reduced = jac_delta_body.leftCols(nbc_*dimc_+6);
+
+    // compute delta_cov (using uncertainty on f,tau,pbc)
+    _delta_cov = jac_delta_body_reduced * _data_cov * jac_delta_body_reduced.transpose();  // trivially:  jac_body_delta = Identity
+    // _delta_cov = jac_delta_body * _data_cov * jac_delta_body.transpose();  // trivially:  jac_body_delta = Identity
+
+    // compute jacobian_calib
+    _jac_delta_calib = jac_delta_body * jac_body_bias;
+}
+
+void ProcessorForceTorquePreint::deltaPlusDelta(const Eigen::VectorXd& _delta_preint,
+                                  const Eigen::VectorXd& _delta,
+                                  const double _dt,
+                                  Eigen::VectorXd& _delta_preint_plus_delta) const
+{
+    _delta_preint_plus_delta = bodydynamics::compose(_delta_preint, _delta, _dt);
+}
+
+void ProcessorForceTorquePreint::statePlusDelta(const VectorComposite& _x,
+                                  const Eigen::VectorXd& _delta,
+                                  const double _dt,
+                                  VectorComposite& _x_plus_delta) const
+{
+    assert(_delta.size() == 13 && "Wrong _delta vector size");
+    assert(_dt >= 0 && "Time interval _dt is negative!");
+
+    // verbose way : create Eigen vectors, then compute, then convert back to Composite
+
+    auto x = _x.vector("CDLO");
+
+    VectorXd x_plus_delta = bodydynamics::composeOverState(x, _delta, _dt);
+
+    _x_plus_delta = VectorComposite(x_plus_delta, "CDLO", {3,3,3,4});
+}
+
+void ProcessorForceTorquePreint::deltaPlusDelta(const Eigen::VectorXd& _delta_preint,
+                                  const Eigen::VectorXd& _delta,
+                                  const double _dt,
+                                  Eigen::VectorXd& _delta_preint_plus_delta,
+                                  Eigen::MatrixXd& _jacobian_delta_preint,
+                                  Eigen::MatrixXd& _jacobian_delta) const
+{
+    bodydynamics::compose(_delta_preint, _delta, _dt, _delta_preint_plus_delta, _jacobian_delta_preint, _jacobian_delta); // jacobians tested in bodydynamics_tools
+}
+
+} // namespace wolf
+
+// Register in the FactorySensor
+#include "core/processor/factory_processor.h"
+
+namespace wolf
+{
+WOLF_REGISTER_PROCESSOR(ProcessorForceTorquePreint)
+}
diff --git a/src/processor/processor_inertial_kinematics.cpp b/src/processor/processor_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d9dc8630d7dbd048bd8be48e3cb919b52a4c0014
--- /dev/null
+++ b/src/processor/processor_inertial_kinematics.cpp
@@ -0,0 +1,223 @@
+//--------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--------
+/**
+ * \file processor_inertial_kinematics.cpp
+ *
+ *  Created on: Feb 27, 2020
+ *      \author: jsola
+ */
+
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+
+
+namespace wolf{
+
+
+inline ProcessorInertialKinematics::ProcessorInertialKinematics(ParamsProcessorInertialKinematicsPtr _params_ikin) :
+        ProcessorBase("ProcessorInertialKinematics", 3, _params_ikin),
+        params_ikin_(std::make_shared<ParamsProcessorInertialKinematics>(*_params_ikin)),
+        cap_origin_ptr_(nullptr)
+{
+}
+
+void ProcessorInertialKinematics::configure(SensorBasePtr _sensor)
+{
+}
+
+
+inline void ProcessorInertialKinematics::processCapture(CaptureBasePtr _capture)
+{
+    if (!_capture)
+    {
+        WOLF_ERROR("Received capture is nullptr.");
+        return;
+    }
+
+    // nothing to do if any of the two buffer is empty
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("PInertialKinematic: KF pack buffer empty, time ",  _capture->getTimeStamp());
+        return;
+    }
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("PInertialKinematics: Capture buffer empty, time ",  _capture->getTimeStamp());
+        return;
+    }
+
+    // PROBLEM with this method:
+    // if the incoming capture _capture matches one of the stored KF it will not be used to create
+    // a factor until the next one arrives since it is not in the capture buffer yet (storeCapture is
+    // done AFTER processCapture)
+
+    // 1. get corresponding KF
+    FrameBasePtr kf;
+    auto buffer_frame_it = buffer_frame_.getContainer().begin();
+    auto buffer_capture_it = buffer_capture_.getContainer().begin();
+
+    auto sensor_angvel = getProblem()->getSensor(params_ikin_->sensor_angvel_name);
+    while ((buffer_frame_it != buffer_frame_.getContainer().end())
+        && (buffer_capture_it != buffer_capture_.getContainer().end()))
+    {
+
+        bool time_ok = checkTimeTolerance(buffer_frame_it->first, buffer_capture_it->first);
+        if (time_ok) {
+            CaptureBasePtr cap_angvel = buffer_frame_it->second->getCaptureOf(sensor_angvel);
+            auto min_ts = (buffer_frame_it->first < buffer_capture_it->first) ? buffer_frame_it->first : buffer_capture_it->first;
+            if (cap_angvel && cap_angvel->getStateBlock('I')){  // TODO: or only cap_angvel?
+                // cast incoming capture to the InertialKinematics type, add it to the keyframe
+                auto kf = buffer_frame_it->second;
+                auto cap_ikin = std::static_pointer_cast<CaptureInertialKinematics>(buffer_capture_it->second);
+                cap_ikin->link(kf);
+                createInertialKinematicsFactor(cap_ikin,
+                                               cap_angvel,
+                                               cap_origin_ptr_);
+                // update pointer to origin capture (the previous one attached to a KF) if we have created a new factor
+                cap_origin_ptr_ = buffer_capture_it->second;
+                buffer_capture_it++;
+                buffer_frame_it++;
+            }
+            else {
+                // if time ok but no capture angvel yet, there is not gonna be any in the next KF of the buffer
+                break;
+                buffer_capture_it++;
+                buffer_frame_it++;
+            }
+
+            ////////////////
+            // remove everything before (Inclusive if equal) this timestamp -> the cap_angvel is yet to come
+            buffer_frame_.removeUpTo(min_ts);
+            buffer_capture_.removeUpTo(min_ts);
+        }
+        else {
+            // if a time difference between captures and KF pack, we increment the oldest iterator
+            if (buffer_capture_it->first < buffer_frame_it->first){
+                buffer_capture_it++;
+            }
+            else {
+                buffer_frame_it++;
+            }
+        }
+    }
+}
+
+
+inline bool ProcessorInertialKinematics::createInertialKinematicsFactor(CaptureInertialKinematicsPtr _cap_ikin, CaptureBasePtr _cap_angvel, CaptureBasePtr _cap_ikin_origin)
+{
+    // 3. explore all observations in the capture and create feature
+    Eigen::Vector9d data = _cap_ikin->getData();
+    Eigen::Matrix3d B_I_q = _cap_ikin->getBIq();
+    Eigen::Vector3d B_Lc_m = _cap_ikin->getBLcm();
+    auto sensor_ikin = std::static_pointer_cast<SensorInertialKinematics>(getSensor());
+    auto sensor_angvel_base = getProblem()->getSensor(params_ikin_->sensor_angvel_name);
+    Eigen::Matrix3d Qp = pow(sensor_ikin->getPbcNoiseStd(), 2) * Eigen::Matrix3d::Identity();
+    Eigen::Matrix3d Qv = pow(sensor_ikin->getVbcNoiseStd(), 2) * Eigen::Matrix3d::Identity();
+    Eigen::Matrix3d Qw = sensor_angvel_base->getNoiseCov().bottomRightCorner<3,3>();
+    Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp, Qv, Qw,
+                                                data.head<3>() - _cap_ikin->getSensorIntrinsic()->getState(),   // bias value of the capture that was just created
+                                                data.tail<3>() - _cap_angvel->getSensorIntrinsic()->getState().tail<3>(),
+                                                B_I_q);
+    auto feat_ikin = FeatureBase::emplace<FeatureInertialKinematics>(_cap_ikin, data, Q_ikin_err, B_I_q, B_Lc_m);
+
+    // 4. Create inertial kinematics factor
+    auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_ikin,
+                            feat_ikin,
+                            _cap_angvel->getSensorIntrinsic(),
+                            shared_from_this(),
+                            false);
+
+    // 5. Create bias drift factor if an origin capture exists -> zero difference + covariance as integration of a Brownian noise
+    if (_cap_ikin_origin){
+        double dt_drift = _cap_ikin->getTimeStamp() - _cap_ikin_origin->getTimeStamp();
+        Eigen::Matrix3d cov_drift = Eigen::Matrix3d::Identity() * pow(params_ikin_->std_bp_drift, 2) * dt_drift;
+        FeatureBasePtr feat_diff = FeatureBase::emplace<FeatureBase>(_cap_ikin, "ComBiasDrift", Eigen::Vector3d::Zero(), cov_drift, 
+                                                                     FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        StateBlockPtr sb_pbc1 = _cap_ikin_origin->getSensorIntrinsic();
+        StateBlockPtr sb_pbc2 = _cap_ikin->getSensorIntrinsic();
+        FactorBlockDifferencePtr Fac = FactorBase::emplace<FactorBlockDifference>(
+                feat_diff, feat_diff, sb_pbc1, sb_pbc2, nullptr, _cap_ikin_origin
+        );
+    }
+
+    return true;
+}
+
+
+
+//////////////////////////
+// Covariance computations
+//////////////////////////
+
+Eigen::Matrix9d computeIKinJac(const Eigen::Vector3d& b_p_bc_unbiased,
+                               const Eigen::Vector3d& b_omg_b_unbiased,
+                               const Eigen::Matrix3d& Iq)
+{
+    Eigen::Matrix9d J; J.setZero();
+
+    Eigen::Matrix3d wx = skew(b_omg_b_unbiased);
+    Eigen::Matrix3d px = skew(b_p_bc_unbiased);
+    // Starting from top left, to the right and then next row
+    J.topLeftCorner<3,3>() = Eigen::Matrix3d::Identity();
+    // J.block<3,3>(0,3) = Matrix3d::Zero();
+    // J.topRightCorner<3,3>() = Matrix3d::Zero();
+    J.block<3,3>(3,0) = - wx;
+    J.block<3,3>(3,3) = - Eigen::Matrix3d::Identity();
+    J.block<3,3>(3,6) =   px;
+    // J.bottomLeftCorner<3,3>() = Matrix3d::Zero();
+    // J.block<3,3>(6,3) = Matrix3d::Zero();
+    J.bottomRightCorner<3,3>() = -Iq;
+
+    return J;
+}
+
+
+Eigen::Matrix9d computeIKinCov(const Eigen::Matrix3d& Qp,
+                               const Eigen::Matrix3d& Qv,
+                               const Eigen::Matrix3d& Qw,
+                               const Eigen::Vector3d& b_p_bc_unbiased,
+                               const Eigen::Vector3d& b_omg_b_unbiased,
+                               const Eigen::Matrix3d& Iq)
+{
+    Eigen::Matrix9d cov; cov.setZero();
+
+    Eigen::Matrix3d px = skew(b_p_bc_unbiased);
+    Eigen::Matrix3d wx = skew(b_omg_b_unbiased);
+    // Starting from top left, to the right and then next row
+    cov.topLeftCorner<3,3>() = Qp;
+    cov.block<3,3>(0,3) = Qp * wx;
+    // cov.topRightCorner<3,3>() = Matrix3d::Zero();
+    cov.block<3,3>(3,0) = cov.block<3,3>(0,3).transpose();
+    cov.block<3,3>(3,3) = -wx*Qp*wx + Qv - px*Qw*px;
+    cov.block<3,3>(3,6) = -px*Qw*Iq;
+    // cov.bottomLeftCorner<3,3>() = Matrix3d::Zero();
+    cov.block<3,3>(6,3) = Iq*Qw*px;
+    cov.bottomRightCorner<3,3>() = Iq*Qw*Iq;
+
+    return cov;
+}
+
+} /* namespace wolf */
+
+// Register in the FactoryProcessor
+#include "core/processor/factory_processor.h"
+namespace wolf {
+WOLF_REGISTER_PROCESSOR(ProcessorInertialKinematics);
+WOLF_REGISTER_PROCESSOR_AUTO(ProcessorInertialKinematics);
+} // namespace wolf
diff --git a/src/processor/processor_point_feet_nomove.cpp b/src/processor/processor_point_feet_nomove.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..358ad06be77d987125dcd0341bbd10e62c6fdd78
--- /dev/null
+++ b/src/processor/processor_point_feet_nomove.cpp
@@ -0,0 +1,177 @@
+//--------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--------
+/**
+ * \file processor_point_feet_nomove.cpp
+ *
+ *  Created on: Oct 23, 2020
+ *      \author: mfourmy
+ */
+
+#include "bodydynamics/processor/processor_point_feet_nomove.h"
+
+
+namespace wolf{
+
+
+inline ProcessorPointFeetNomove::ProcessorPointFeetNomove(ParamsProcessorPointFeetNomovePtr _params_pfnomove) :
+        ProcessorBase("ProcessorPointFeetNomove", 3, _params_pfnomove),
+        params_pfnomove_(std::make_shared<ParamsProcessorPointFeetNomove>(*_params_pfnomove))
+{
+
+}
+
+void ProcessorPointFeetNomove::configure(SensorBasePtr _sensor)
+{
+}
+
+void ProcessorPointFeetNomove::createFactorIfNecessary(){
+    auto sensor_pfnm = std::static_pointer_cast<SensorPointFeetNomove>(getSensor());
+
+    while (buffer_frame_.size() >= 2)
+    {
+        auto kf1_it = buffer_frame_.getContainer().begin();
+        auto kf2_it = std::next(kf1_it);
+        TimeStamp t1 = kf1_it->first;
+        TimeStamp t2 = kf2_it->first;
+        auto cap1_it = buffer_capture_.selectIterator(t1, getTimeTolerance());
+        auto cap2_it = buffer_capture_.selectIterator(t2, getTimeTolerance());
+
+        // check that the first 2 KF have corresponding captures in the capture buffer  
+        // just quit and assume that you will someday have matching captures
+        if ( (cap1_it == buffer_capture_.getContainer().end()) or (cap2_it == buffer_capture_.getContainer().end()) )
+        {
+            return;
+        }
+        else
+        {
+            // store the initial contact/kinematic meas map
+            auto cap1 = std::static_pointer_cast<CapturePointFeetNomove>(cap1_it->second);
+            auto kin_incontact_from_t1 = cap1->kin_incontact_;
+            std::cout << kin_incontact_from_t1.size() << " ";
+
+            
+            auto cap_it = std::next(cap1_it);
+            auto it_after_capt_t2 = std::next(cap2_it);
+
+            // loop throught the captures between t1 and t2
+            while (cap_it != it_after_capt_t2){
+                // for each new capture, filter the initial contact/kin meas map to track which feet stay in contact throughout
+                auto cap_pfeet = std::static_pointer_cast<CapturePointFeetNomove>(cap_it->second);
+                if (!cap_pfeet){
+
+                }
+                
+                auto kin_incontact_current = cap_pfeet->kin_incontact_;
+                // std::cout << kin_incontact_current.size() << " ";
+
+                for (auto elt_it = kin_incontact_from_t1.cbegin(); elt_it != kin_incontact_from_t1.cend(); /* no increment */){
+                    if (kin_incontact_current.find(elt_it->first) == kin_incontact_current.end()){
+                        // The foot has lost contact with the ground since t1
+                        elt_it = kin_incontact_from_t1.erase(elt_it);  // erase and jump (from stackoverflow)
+                    }
+                    else{
+                        elt_it++;
+                    }
+                }
+                cap_it++;
+            }
+            // std::cout << std::endl;
+
+            if (!kin_incontact_from_t1.empty())
+            {
+                auto cap2 = std::static_pointer_cast<CapturePointFeetNomove>(cap2_it->second);
+                // link (kind of arbitrarily) factor from KF1 to KF2 with a feature and capture on KF2 to mimic capture motion
+                // however, this capture solely does not contain enough information to recreate the factor
+                cap2->link(kf2_it->second);
+                auto kin_incontact_t2 = cap2->kin_incontact_;
+
+                for (auto kin_pair1: kin_incontact_from_t1){
+                    auto kin_pair2_it = kin_incontact_t2.find(kin_pair1.first);
+
+                    Vector6d meas; meas << kin_pair1.second, kin_pair2_it->second;
+                    FeatureBasePtr feat = FeatureBase::emplace<FeatureBase>(cap2, "PointFeet", meas, sensor_pfnm->getCovFootNomove());
+                    FactorPointFeetNomovePtr fac = FactorBase::emplace<FactorPointFeetNomove>(feat, feat, kf1_it->second, nullptr, true);
+                }
+            }
+
+            // Once the factors are created, remove kf1 and all the captures until ts 2 NOT INCLUDING the one at ts 2 since we need it for the next
+            // Note: erase by range does not include the end range iterator
+            buffer_frame_.getContainer().erase(buffer_frame_.getContainer().begin(), kf2_it);  // !! works only if getContainer returns a non const reference
+            buffer_capture_.getContainer().erase(buffer_capture_.getContainer().begin(), cap2_it);
+        }
+    }
+}
+
+
+inline void ProcessorPointFeetNomove::processCapture(CaptureBasePtr _capture)
+{
+    if (!_capture)
+    {
+        WOLF_ERROR("Received capture is nullptr.");
+        return;
+    }
+    // nothing to do if any of the two buffer is empty
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("PInertialKinematic: KF pack buffer empty, time ",  _capture->getTimeStamp());
+        return;
+    }
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("PInertialKinematics: Capture buffer empty, time ",  _capture->getTimeStamp());
+        return;
+    }
+
+    createFactorIfNecessary();
+
+}
+
+inline void ProcessorPointFeetNomove::processKeyFrame(FrameBasePtr _keyframe_ptr)
+{
+    if (!_keyframe_ptr)
+    {
+        WOLF_ERROR("Received KF is nullptr.");
+        return;
+    }
+    // nothing to do if any of the two buffer is empty
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("ProcessorPointFeetNomove: KF pack buffer empty, time ",  _keyframe_ptr->getTimeStamp());
+        return;
+    }
+    if(buffer_frame_.empty()){
+        WOLF_DEBUG("ProcessorPointFeetNomove: Capture buffer empty, time ",  _keyframe_ptr->getTimeStamp());
+        return;
+    }
+
+    createFactorIfNecessary();
+}
+
+
+
+
+
+} /* namespace wolf */
+
+// Register in the FactoryProcessor
+#include "core/processor/factory_processor.h"
+namespace wolf {
+WOLF_REGISTER_PROCESSOR(ProcessorPointFeetNomove);
+WOLF_REGISTER_PROCESSOR_AUTO(ProcessorPointFeetNomove);
+} // namespace wolf
diff --git a/src/sensor/.blank b/src/sensor/.blank
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/sensor/sensor_force_torque.cpp b/src/sensor/sensor_force_torque.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..76704952b7ce2d5971e1a181cd8a6f1ac797702b
--- /dev/null
+++ b/src/sensor/sensor_force_torque.cpp
@@ -0,0 +1,55 @@
+//--------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--------
+#include "bodydynamics/sensor/sensor_force_torque.h"
+
+#include "core/state_block/state_block.h"
+#include "core/state_block/state_quaternion.h"
+
+namespace wolf {
+
+SensorForceTorque::SensorForceTorque(const Eigen::VectorXd& _extrinsics, ParamsSensorForceTorquePtr _params) :
+                SensorBase("SensorForceTorque",
+                           nullptr,
+                           nullptr,
+                           nullptr,
+                           (Eigen::Matrix<double, 12, 1>() << _params->std_f,_params->std_f,_params->std_f,
+                                                              _params->std_f,_params->std_f,_params->std_f,
+                                                              _params->std_tau,_params->std_tau,_params->std_tau,
+                                                              _params->std_tau,_params->std_tau,_params->std_tau).finished(),
+                           false,
+                           false,
+                           true),
+                mass_(_params->mass),
+                std_f_(_params->std_f),
+                std_tau_(_params->std_tau)
+{
+    assert(_extrinsics.size() == 0 && "Wrong extrinsics vector size! Should be 0");
+}
+
+
+} // namespace wolf
+
+// Register in the FactorySensor
+#include "core/sensor/factory_sensor.h"
+namespace wolf {
+WOLF_REGISTER_SENSOR(SensorForceTorque)
+} // namespace wolf
diff --git a/src/sensor/sensor_inertial_kinematics.cpp b/src/sensor/sensor_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..33369c5f00e3d7a65383e28cb0d65d102bcc4de7
--- /dev/null
+++ b/src/sensor/sensor_inertial_kinematics.cpp
@@ -0,0 +1,56 @@
+//--------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--------
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+
+#include "core/state_block/state_block.h"
+#include "core/state_block/state_quaternion.h"
+
+namespace wolf {
+
+SensorInertialKinematics::SensorInertialKinematics(const Eigen::VectorXd& _extrinsics, ParamsSensorInertialKinematicsPtr _params) :
+        SensorBase("SensorInertialKinematics",
+                   nullptr, // no position
+                   nullptr, // no orientation
+                   std::make_shared<StateBlock>(Eigen::Vector3d(0,0,0), false), // bias; false = unfixed
+                   (Eigen::Vector6d() << _params->std_pbc,_params->std_pbc,_params->std_pbc,
+                                         _params->std_vbc,_params->std_vbc,_params->std_vbc).finished(),
+                   false, false, true),
+                   intr_ikin_(_params)
+{
+    assert(_extrinsics.size() == 0 && "Wrong extrinsics vector size! Should be 0.");
+    assert(getIntrinsic()->getState().size() == 3 && "Wrong extrinsics size! Should be 3.");
+    setStateBlockDynamic('I', true);
+}
+
+SensorInertialKinematics::~SensorInertialKinematics()
+{
+    //
+}
+
+
+} // namespace wolf
+
+// Register in the FactorySensor
+#include "core/sensor/factory_sensor.h"
+namespace wolf {
+WOLF_REGISTER_SENSOR(SensorInertialKinematics)
+} // namespace wolf
diff --git a/src/sensor/sensor_point_feet_nomove.cpp b/src/sensor/sensor_point_feet_nomove.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6e5a48b7c058aa152ad4179404a061e7fe74dedd
--- /dev/null
+++ b/src/sensor/sensor_point_feet_nomove.cpp
@@ -0,0 +1,54 @@
+//--------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--------
+#include "bodydynamics/sensor/sensor_point_feet_nomove.h"
+
+namespace wolf {
+
+SensorPointFeetNomove::SensorPointFeetNomove(const Eigen::VectorXd& _extrinsics_pq, const ParamsSensorPointFeetNomovePtr& _params) :
+                        SensorBase("SensorPointFeetNomove", 
+                                   nullptr, 
+                                   nullptr,
+                                   nullptr,
+                                   (Eigen::Vector3d() << _params->std_foot_nomove_,_params->std_foot_nomove_,_params->std_foot_nomove_).finished(),
+                                   false,
+                                   false,
+                                   false)
+{
+    assert(_extrinsics_pq.size() == 0 && "Bad extrinsics vector size! Should be 0 since not used.");
+
+    cov_foot_nomove_ = pow(_params->std_foot_nomove_, 2)*Matrix3d::Identity();
+}
+
+
+SensorPointFeetNomove::~SensorPointFeetNomove()
+{
+    //
+}
+
+} // namespace wolf
+
+// Register in the FactorySensor
+#include "core/sensor/factory_sensor.h"
+namespace wolf {
+WOLF_REGISTER_SENSOR(SensorPointFeetNomove);
+WOLF_REGISTER_SENSOR_AUTO(SensorPointFeetNomove);
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 268d8fa2e37f7c85dd1b4b63bf5f88615673fefc..60f23c1fe3f823d3c1c848365735928550d24095 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -22,17 +22,45 @@ add_subdirectory(gtest)
 
 
 # Include gtest directory.
-include_directories(${GTEST_INCLUDE_DIRS} ${wolfIMU_INCLUDE_DIRS})
+include_directories(${GTEST_INCLUDE_DIRS})
 
 ################# ADD YOUR TESTS BELOW ####################
 #                                                         #
 #           ==== IN ALPHABETICAL ORDER! ====              #
 #                                                         #
+wolf_add_gtest(gtest_capture_inertial_kinematics gtest_capture_inertial_kinematics.cpp)
+target_link_libraries(gtest_capture_inertial_kinematics ${PLUGIN_NAME})
+
+wolf_add_gtest(gtest_capture_leg_odom gtest_capture_leg_odom.cpp)
+target_link_libraries(gtest_capture_leg_odom ${PLUGIN_NAME})
+
+wolf_add_gtest(gtest_feature_inertial_kinematics gtest_feature_inertial_kinematics.cpp)
+target_link_libraries(gtest_feature_inertial_kinematics ${PLUGIN_NAME})
+
 wolf_add_gtest(gtest_factor_inertial_kinematics gtest_factor_inertial_kinematics.cpp)
-target_link_libraries(gtest_factor_inertial_kinematics ${PLUGIN_NAME} ${wolfIMU_LIBRARIES})
+target_link_libraries(gtest_factor_inertial_kinematics ${PLUGIN_NAME})
 
 wolf_add_gtest(gtest_factor_force_torque gtest_factor_force_torque.cpp)
-target_link_libraries(gtest_factor_force_torque ${PLUGIN_NAME} ${wolfIMU_LIBRARIES})
+target_link_libraries(gtest_factor_force_torque ${PLUGIN_NAME})
 
 wolf_add_gtest(gtest_force_torque_delta_tools gtest_force_torque_delta_tools.cpp)
-target_link_libraries(gtest_force_torque_delta_tools ${PLUGIN_NAME})
\ No newline at end of file
+target_link_libraries(gtest_force_torque_delta_tools ${PLUGIN_NAME})
+
+# wolf_add_gtest(gtest_feature_force_torque_preint gtest_feature_force_torque_preint.cpp)
+# target_link_libraries(gtest_feature_force_torque_preint ${PLUGIN_NAME})
+
+# wolf_add_gtest(gtest_processor_inertial_kinematics gtest_processor_inertial_kinematics.cpp)
+# target_link_libraries(gtest_processor_inertial_kinematics ${PLUGIN_NAME})
+
+# wolf_add_gtest(gtest_processor_force_torque_preint gtest_processor_force_torque_preint.cpp)
+# target_link_libraries(gtest_processor_force_torque_preint ${PLUGIN_NAME})
+
+wolf_add_gtest(gtest_processor_point_feet_nomove gtest_processor_point_feet_nomove.cpp)
+target_link_libraries(gtest_processor_point_feet_nomove ${PLUGIN_NAME})
+
+wolf_add_gtest(gtest_sensor_force_torque gtest_sensor_force_torque.cpp)
+target_link_libraries(gtest_sensor_force_torque ${PLUGIN_NAME})
+
+wolf_add_gtest(gtest_sensor_inertial_kinematics gtest_sensor_inertial_kinematics.cpp)
+target_link_libraries(gtest_sensor_inertial_kinematics ${PLUGIN_NAME})
+
diff --git a/test/gtest_capture_inertial_kinematics.cpp b/test/gtest_capture_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4402ddec609e504f7f5fc63e345eb2eda6778980
--- /dev/null
+++ b/test/gtest_capture_inertial_kinematics.cpp
@@ -0,0 +1,57 @@
+//--------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--------
+/**
+ * \file gtest_capture_inertial_kinematics.cpp
+ *
+ *  Created on: Feb 28, 2020
+ *      \author: jsola
+ */
+
+
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+
+#include "core/utils/utils_gtest.h"
+#include <core/utils/logging.h>
+
+#include <Eigen/Dense>
+
+using namespace wolf;
+
+TEST(CaptureInertialKinematics, constructor_and_getters)
+{
+    SensorInertialKinematicsPtr sen;
+    Vector9d data = Vector9d::Random();
+    Matrix3d Iq = Matrix3d::Random();
+    Vector3d Lcm = Vector3d::Random();
+    Vector3d bias = Vector3d::Random();
+    CaptureInertialKinematicsPtr C = std::make_shared<CaptureInertialKinematics>(0, sen, data, Iq, Lcm, bias);
+
+    ASSERT_MATRIX_APPROX(C->getData(), data, 1e-20);
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/test/gtest_capture_leg_odom.cpp b/test/gtest_capture_leg_odom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b19d8db8b85ff86254e84c373cb23a4616a7833
--- /dev/null
+++ b/test/gtest_capture_leg_odom.cpp
@@ -0,0 +1,120 @@
+//--------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--------
+/**
+ * \file gtest_capture_leg_odom.cpp
+ *
+ *  Created on: May 12, 2020
+ *      \author: mfourmy
+ */
+
+
+#include "bodydynamics/capture/capture_leg_odom.h"
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+#include <Eigen/Dense>
+
+using namespace wolf;
+using namespace Eigen;
+
+
+TEST(CaptureLegOdom, point_feet_relative_kin)
+{   
+  TimeStamp ts(0.0);
+  double dt = 1;
+  // 2 point feet + wb
+  MatrixXd data_kin(3,5);
+  Vector3d bm_p_bml1; bm_p_bml1 << 0,0,0;
+  Vector3d b_p_bl1; b_p_bl1 << -1,0,0;
+  Vector3d bm_p_bml2; bm_p_bml2 << 0,0,0;
+  Vector3d b_p_bl2; b_p_bl2 << -1,0,0;
+  Vector3d i_omg_oi; i_omg_oi << 0,0,0;
+  data_kin.col(0) = bm_p_bml1;
+  data_kin.col(1) = b_p_bl1;
+  data_kin.col(2) = bm_p_bml2;
+  data_kin.col(3) = b_p_bl2;
+  data_kin.col(4) = i_omg_oi;
+
+  Matrix6d data_cov = Matrix6d::Identity();
+  CaptureLegOdomPtr CLO = std::make_shared<CaptureLegOdom>(ts, nullptr, data_kin, data_cov, dt, CaptureLegOdomType::POINT_FEET_RELATIVE_KIN);
+
+  Vector3d bm_p_bmb_exp; bm_p_bmb_exp << 1, 0, 0;
+  ASSERT_MATRIX_APPROX(CLO->getData().head<3>(), bm_p_bmb_exp, 1e-6)
+}
+
+TEST(CaptureLegOdom, point_feet_differential_kin)
+{   
+  TimeStamp ts(0.0);
+  double dt = 1;
+  // 2 point feet + wb
+  MatrixXd data_kin(10,3);
+  Vector3d i_omg_oi; i_omg_oi << 0,1,0;
+
+  // 1rst foot: only relative foot/base motion from J*dqa, no lever so no influence from angular vel 
+  Vector3d b_p_bl1; b_p_bl1 << 0,0,0; 
+  Vector4d b_qvec_l1; b_qvec_l1 << 0,0,0,1; 
+  Vector3d l_v_bl1; l_v_bl1 << -1,0,0;
+
+  // 2nd feet: rigid rotation pivoting around the foot point
+  Vector3d b_p_bl2; b_p_bl2 << 0,0,-1; 
+  Vector4d b_qvec_l2; b_qvec_l2 << 0,0,0,1; 
+  Vector3d l_v_bl2; l_v_bl2 << 0,0,0; 
+  data_kin.col(0) << b_p_bl1, b_qvec_l1, l_v_bl1;
+  data_kin.col(1) << b_p_bl2, b_qvec_l2, l_v_bl2;
+  data_kin.col(2) << i_omg_oi, 0,0,0,0,0,0,0;
+
+  Matrix6d data_cov = Matrix6d::Identity();
+  CaptureLegOdomPtr CLO = std::make_shared<CaptureLegOdom>(ts, nullptr, data_kin, data_cov, dt, CaptureLegOdomType::POINT_FEET_DIFFERENTIAL_KIN);
+
+  Vector3d bm_p_bmb_exp; bm_p_bmb_exp << 1, 0, 0;
+  ASSERT_MATRIX_APPROX(CLO->getData().head<3>(), bm_p_bmb_exp, 1e-6)
+}
+
+TEST(CaptureLegOdom, humanoid_feet)
+{   
+  TimeStamp ts(0.0);
+  double dt = 1;
+  // 1 humanoid feet + wb
+  MatrixXd data_kin(7,2); 
+  // only one foot taken into account
+  Vector3d bm_p_bml1; bm_p_bml1 << 0,0,0;
+  Vector4d bm_q_bml1; bm_q_bml1 << 0,0,0,1;
+  Vector3d b_p_bl1; b_p_bl1 << -1,0,0;
+  Vector4d b_q_bl1; b_q_bl1 << 0,0,0,1;
+  data_kin.col(0) << bm_p_bml1, bm_q_bml1;
+  data_kin.col(1) << b_p_bl1, b_q_bl1;
+
+  Matrix6d data_cov = Matrix6d::Identity();
+  CaptureLegOdomPtr CLO = std::make_shared<CaptureLegOdom>(ts, nullptr, data_kin, data_cov, dt, CaptureLegOdomType::HUMANOID_FEET_RELATIVE_KIN);
+
+  Vector3d bm_p_bmb_exp; bm_p_bmb_exp << 1, 0, 0;
+  ASSERT_MATRIX_APPROX(CLO->getData().head<3>(), bm_p_bmb_exp, 1e-6)
+
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/test/gtest_factor_force_torque.cpp b/test/gtest_factor_force_torque.cpp
index 9eff4752348505f42b7f26484de945533e33802f..16bd8d88a91abebcf2c00cdabb7bd163f44e2046 100644
--- a/test/gtest_factor_force_torque.cpp
+++ b/test/gtest_factor_force_torque.cpp
@@ -1,3 +1,24 @@
+//--------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--------
 
 /**
  * \file gtest_factor_inertial_kinematics.cpp
@@ -9,10 +30,10 @@
 /*
 
 
-Organisation: For each test, the problem, sensors, factors (if possible) are instanciated in a base class inheriting fromtesting::Test only. 
+Organisation: For each test, the problem, sensors, factors (if possible) are instanciated in a base class inheriting fromtesting::Test only.
 Then, each test case is derived in a child class which defines the data processed by the different sensors and expected estimated test values.
-Finally, each of these child classes is used in one or several Test in which basically the expected values are compared against estimation and 
-solve is done with a perturbated system. 
+Finally, each of these child classes is used in one or several Test in which basically the expected values are compared against estimation and
+solve is done with a perturbated system.
 
 Tests list:
 
@@ -32,25 +53,32 @@ FactorInertialKinematics_2KF_1v_bfix,ZeroMvt:
 
 // WOLF
 #include <core/problem/problem.h>
-#include <core/ceres_wrapper/ceres_manager.h>
+#include <core/ceres_wrapper/solver_ceres.h>
 #include <core/math/rotations.h>
 #include <core/capture/capture_base.h>
 #include <core/feature/feature_base.h>
 #include <core/factor/factor_block_absolute.h>
 
-// IMU
-// #include "IMU/internal/config.h"
-// #include "IMU/capture/capture_IMU.h"
-// #include "IMU/processor/processor_IMU.h"
-// #include "IMU/sensor/sensor_IMU.h"
+// Imu
+// #include "imu/internal/config.h"
+// #include "imu/capture/capture_imu.h"
+// #include "imu/processor/processor_imu.h"
+// #include "imu/sensor/sensor_imu.h"
 
 // BODYDYNAMICS
 #include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
 #include "bodydynamics/feature/feature_inertial_kinematics.h"
 #include "bodydynamics/factor/factor_inertial_kinematics.h"
 #include "bodydynamics/feature/feature_force_torque.h"
 #include "bodydynamics/factor/factor_force_torque.h"
 
+// ODOM3d
+#include "core/capture/capture_pose.h"
+#include "core/feature/feature_pose.h"
+#include "core/factor/factor_relative_pose_3d.h"
+
 #include <Eigen/Eigenvalues>
 
 using namespace wolf;
@@ -59,8 +87,8 @@ using namespace std;
 
 
 // SOME CONSTANTS
-const double Acc1 = 1.0;
-const double Acc2 = 2.5;
+const double Acc1 = 1;
+const double Acc2 = 3;
 const Vector3d zero3 = Vector3d::Zero();
 const Vector6d zero6 = Vector6d::Zero();
 
@@ -89,7 +117,7 @@ Matrix9d computeKinJac(const Vector3d& w_unb,
 }
 
 Matrix9d computeKinCov(const Matrix3d& Qp,
-                              const Matrix3d& Qv, 
+                              const Matrix3d& Qv,
                               const Matrix3d& Qw,
                               const Vector3d& w_unb,
                               const Vector3d& p_unb,
@@ -114,21 +142,24 @@ Matrix9d computeKinCov(const Matrix3d& Qp,
 }
 
 
-void perturbateIfUnFixed(const FrameBasePtr& KF, std::string sb_name){
-    if(!KF->getStateBlock(sb_name)->isFixed()) 
+void perturbateIfUnFixed(const FrameBasePtr& KF, char sb_name){
+    if(!KF->getStateBlock(sb_name)->isFixed())
     {
-        if (sb_name == "O")
+        if (sb_name == 'O')
         {
-            KF->getStateBlock("O")->setState(KF->getStateBlock("O")->getState().reverse());
+            double max_rad_pert = M_PI / 3;
+            Vector3d do_pert = max_rad_pert*Vector3d::Random();
+            Quaterniond curr_state_q(KF->getO()->getState().data());
+            KF->getStateBlock('O')->setState((curr_state_q * exp_q(do_pert)).coeffs());
         }
-        else 
-        {   
+        else
+        {
             VectorXd pert;
             pert.resize(KF->getStateBlock(sb_name)->getSize());
-            pert.setRandom();
+            pert.setRandom(); pert *= 0.5;
             KF->getStateBlock(sb_name)->setState(KF->getStateBlock(sb_name)->getState() + pert);
         }
-    } 
+    }
 }
 
 void perturbateAllIfUnFixed(const FrameBasePtr& KF)
@@ -138,15 +169,14 @@ void perturbateAllIfUnFixed(const FrameBasePtr& KF)
     }
 }
 
-FrameBasePtr createKFWithCDLBI(const ProblemPtr& problem, const TimeStamp& t, const Vector10d& x_origin,
-                              const Vector3d& c, const Vector3d& cd, const Vector3d& Lc, const Vector3d& bias_p, const Vector6d& bias_imu)
-{   
-    FrameBasePtr KF = FrameBase::emplace<FrameBase>(problem->getTrajectory(), "POV", 3, wolf::KEY, t, x_origin);
-    StateBlockPtr sbc = make_shared<StateBlock>(c);  KF->addStateBlock("C", sbc); problem->notifyStateBlock(sbc,ADD);
-    StateBlockPtr sbd = make_shared<StateBlock>(cd); KF->addStateBlock("D", sbd); problem->notifyStateBlock(sbd,ADD);
-    StateBlockPtr sbL = make_shared<StateBlock>(Lc); KF->addStateBlock("L", sbL); problem->notifyStateBlock(sbL,ADD);
-    StateBlockPtr sbb = make_shared<StateBlock>(bias_p); KF->addStateBlock("B", sbb); problem->notifyStateBlock(sbb,ADD);
-    StateBlockPtr sbbimu = make_shared<StateBlock>(bias_imu); KF->addStateBlock("I", sbbimu); problem->notifyStateBlock(sbbimu,ADD);
+FrameBasePtr createKFWithCDLI(const ProblemPtr& problem, const TimeStamp& t, VectorComposite x_origin,
+                              Vector3d c, Vector3d cd, Vector3d Lc, Vector6d bias_imu)
+{
+    FrameBasePtr KF = FrameBase::emplace<FrameBase>(problem->getTrajectory(), t, "POV", x_origin);
+    StateBlockPtr sbc = make_shared<StateBlock>(c);  KF->addStateBlock('C', sbc, problem);
+    StateBlockPtr sbd = make_shared<StateBlock>(cd); KF->addStateBlock('D', sbd, problem);
+    StateBlockPtr sbL = make_shared<StateBlock>(Lc); KF->addStateBlock('L', sbL, problem);
+    StateBlockPtr sbbimu = make_shared<StateBlock>(bias_imu); KF->addStateBlock('I', sbbimu, problem);  // Simulating IMU
     return KF;
 }
 
@@ -158,22 +188,24 @@ class FactorInertialKinematics_2KF : public testing::Test
         wolf::TimeStamp tA_;
         wolf::TimeStamp tB_;
         ProblemPtr problem_;
-        CeresManager* ceres_manager_;
-        VectorXd x_origin_;
-        MatrixXd P_origin_;
+        SolverCeresPtr solver_;
+        VectorComposite x_origin_; // initial state
+        VectorComposite s_origin_; // initial sigmas for prior
+        SensorInertialKinematicsPtr SIK_;
+        CaptureInertialKinematicsPtr CIKA_, CIKB_;
         FrameBasePtr KFA_;
         FrameBasePtr KFB_;
         Matrix3d Qp_, Qv_, Qw_;
         Vector3d bias_p_;
         Vector6d bias_imu_;
-        FeatureInertialKinematicsPtr feat_ikinA_;
-        FeatureInertialKinematicsPtr feat_ikinB_;
-        FeatureForceTorquePtr feat_ftAB_;
-        // SensorIMUPtr sen_imu_;
-        // ProcessorIMUPtr processor_imu_;
-    
+        FeatureInertialKinematicsPtr FIKA_;
+        FeatureInertialKinematicsPtr FIKB_;
+        FeatureForceTorquePtr FFTAB_;
+        // SensorImuPtr sen_imu_;
+        // ProcessorImuPtr processor_imu_;
+
     protected:
-    virtual void SetUp()
+    void SetUp() override
     {
 
         std::string bodydynamics_root = _WOLF_BODYDYNAMICS_ROOT_DIR;
@@ -183,104 +215,98 @@ class FactorInertialKinematics_2KF : public testing::Test
         // WOLF PROBLEM
         problem_ = Problem::create("POV", 3);
 
+        VectorXd extr_ik(0);
+        ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+        intr_ik->std_pbc = 0.1;
+        intr_ik->std_vbc = 0.1;
+        auto senik = problem_->installSensor("SensorInertialKinematics", "senIK", extr_ik, intr_ik);
+        SIK_ = std::static_pointer_cast<SensorInertialKinematics>(senik);
+
         // CERES WRAPPER
-        ceres::Solver::Options ceres_options;
-        // ceres_options.minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
-        // ceres_options.max_line_search_step_contraction = 1e-3;
-        ceres_options.max_num_iterations = 1e3;
-        ceres_manager_ = new CeresManager(problem_, ceres_options);
+        solver_ = std::make_shared<SolverCeres>(problem_);
+        solver_->getSolverOptions().max_num_iterations = 1e3;
+        // solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+        // solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
 
-        // INSTALL IMU SENSOR
+        // INSTALL Imu SENSOR
         // Vector7d extrinsics; extrinsics << 0,0,0,0,0,0,1;
-        // SensorBasePtr sen_imu = problem_->installSensor("SensorIMU", "Main IMU Sensor", extrinsics, bodydynamics_root + "/demos/sensor_imu.yaml");
-        // sen_imu_ = std::static_pointer_cast<SensorIMU>(sen_imu);
-        // ProcessorBasePtr prc_imu = problem_->installProcessor("ProcessorIMU", "IMU PROC", "Main IMU Sensor", bodydynamics_root + "/demos/processor_imu.yaml");
-        // Vector6d data = Vector6d::Zero();
-        // wolf::CaptureIMUPtr imu_ptr = std::make_shared<CaptureIMU>(0, sen_imu_, data, sen_imu_->getNoiseCov(), Vector6d::Zero());
+        // SensorBasePtr sen_imu = problem_->installSensor("SensorImu", "Main Imu Sensor", extrinsics, bodydynamics_root + "/demos/sensor_imu.yaml");
+        // sen_imu_ = std::static_pointer_cast<SensorImu>(sen_imu);
+        // ProcessorBasePtr prc_imu = problem_->installProcessor("ProcessorImu", "Imu PROC", "Main Imu Sensor", bodydynamics_root + "/demos/processor_imu.yaml");
+        // Vector6d data = zero6;
+        // wolf::CaptureImuPtr imu_ptr = std::make_shared<CaptureImu>(0, sen_imu_, data, sen_imu_->getNoiseCov(), zero6);
         // // sen_imu_->process(imu_ptr);
 
         // ======================= INITIALIZATION KFA WITH PRIOR + INERTIAL KINEMATIC FACTOR
         tA_.set(0);
-        x_origin_.resize(10);
-        x_origin_ << 0,0,0, 0,0,0,1, 0,0,0;
-        P_origin_ = pow(1e-3, 2) * Matrix9d::Identity();
-
-        // Set origin of the problem
-        KFA_ = problem_->setPrior(x_origin_, P_origin_.topLeftCorner<6,6>(), tA_, 0.005);
-        // Specific factor for origin velocity (not covered by setPrior)
-        CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KFA_, "Vel0", tA_);
-        FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin_.tail(3), P_origin_.bottomRightCorner<3,3>());
-        FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, KFA_->getV());
+        x_origin_ = VectorComposite("POV", {Vector3d(0,0,0), Quaterniond::Identity().coeffs(), Vector3d(0,0,0)});
+        s_origin_ = VectorComposite("POV", {1e-3*Vector3d(1,1,1), 1e-3*Vector3d(1,1,1), 1e-3*Vector3d(1,1,1)});
+        KFA_ = problem_->setPriorFactor(x_origin_, s_origin_, tA_);
 
 
         // ADD THE STATEBLOCKS AND SET THEIR DEFAULT VALUES
         bias_p_ = zero3;
         bias_imu_ = zero6;
-        StateBlockPtr sbcA = make_shared<StateBlock>(zero3); KFA_->addStateBlock("C", sbcA); problem_->notifyStateBlock(sbcA,ADD);
-        StateBlockPtr sbdA = make_shared<StateBlock>(zero3); KFA_->addStateBlock("D", sbdA); problem_->notifyStateBlock(sbdA,ADD);
-        StateBlockPtr sbLA = make_shared<StateBlock>(zero3); KFA_->addStateBlock("L", sbLA); problem_->notifyStateBlock(sbLA,ADD);
-        StateBlockPtr sbbA = make_shared<StateBlock>(bias_p_); KFA_->addStateBlock("B", sbbA); problem_->notifyStateBlock(sbbA,ADD);
-        StateBlockPtr sbbimuA = make_shared<StateBlock>(bias_imu_); KFA_->addStateBlock("I", sbbimuA); problem_->notifyStateBlock(sbbimuA,ADD);
-        
+        StateBlockPtr sbcA = make_shared<StateBlock>(zero3); KFA_->addStateBlock('C', sbcA, problem_);
+        StateBlockPtr sbdA = make_shared<StateBlock>(zero3); KFA_->addStateBlock('D', sbdA, problem_);
+        StateBlockPtr sbLA = make_shared<StateBlock>(zero3); KFA_->addStateBlock('L', sbLA, problem_);
+        StateBlockPtr sbbimuA = make_shared<StateBlock>(bias_imu_); KFA_->addStateBlock('I', sbbimuA, problem_);
+
         // Fix the one we cannot estimate
         // KFA_->getP()->fix();
         // KFA_->getO()->fix();
         // KFA_->getV()->fix();
-        KFA_->getStateBlock("I")->fix();
-        KFA_->getStateBlock("B")->fix();
+        KFA_->getStateBlock('I')->fix(); // Imu
 
         // INERTIAL KINEMATICS FACTOR
         // Measurements
-        Vector3d pBC_measA = zero3; 
+        Vector3d pBC_measA = zero3;
         Vector3d vBC_measA = zero3;
-        Vector3d w_measA = zero3; 
+        Vector3d w_measA = zero3;
         Vector9d meas_ikinA; meas_ikinA << pBC_measA, vBC_measA, w_measA;
-
         // momentum parameters
-        Matrix3d Iq; Iq.setIdentity(); 
-        Vector3d Lq = zero3; 
+        Matrix3d Iq; Iq.setIdentity();
+        Vector3d Lq = zero3;
 
         Qp_ = pow(1e-2, 2)*Matrix3d::Identity();
         Qv_ = pow(1e-2, 2)*Matrix3d::Identity();
         Qw_ = pow(1e-2, 2)*Matrix3d::Identity();
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_measA-bias_p_, w_measA-bias_imu_.tail(3), Iq);
+        Eigen::Matrix9d Q_ikin_err = computeKinCov(Qp_, Qv_, Qw_, meas_ikinA.head<3>()-bias_p_, meas_ikinA.tail<3>()-bias_imu_.tail<3>(), Iq);
 
-        // FACTOR INERTIAL KINEMATICS ON KFA
-        CaptureBasePtr cap_ikinA = CaptureBase::emplace<CaptureBase>(KFA_, "CaptureInertialKinematics", tA_, nullptr);
-        auto feat_ikinA = FeatureBase::emplace<FeatureInertialKinematics>(cap_ikinA, meas_ikinA, Qkin, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
-        feat_ikinA_ = static_pointer_cast<FeatureInertialKinematics>(feat_ikinA); // !! auto feat_ikinA_ creates no compilation error but a segfault
-        auto fac_inkinA = FactorBase::emplace<FactorInertialKinematics>(feat_ikinA_, feat_ikinA_, sbbimuA, nullptr, false);
+        CIKA_ = CaptureBase::emplace<CaptureInertialKinematics>(KFA_, tA_, SIK_, meas_ikinA, Iq, Lq, bias_p_);
+        CIKA_->getStateBlock('I')->fix(); // IK bias
+        FIKA_ = FeatureBase::emplace<FeatureInertialKinematics>(CIKA_, meas_ikinA, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        FactorBase::emplace<FactorInertialKinematics>(FIKA_, FIKA_, sbbimuA, nullptr, false);
 
 
         // =============== NEW KFB WITH CORRESPONDING STATEBLOCKS
         tB_.set(1);
 
-        KFB_ = createKFWithCDLBI(problem_, tB_, x_origin_,
-                                zero3, zero3, zero3, bias_p_, bias_imu_);
+        KFB_ = createKFWithCDLI(problem_, tB_, x_origin_,
+                                zero3, zero3, zero3, bias_imu_);
         // Fix the one we cannot estimate
         // KFB_->getP()->fix();
         KFB_->getO()->fix();  // Not in the FT factor, so should be fixed (or absolute factor)
         // KFB_->getV()->fix();
-        KFB_->getStateBlock("I")->fix();
-        KFB_->getStateBlock("B")->fix();
+        KFB_->getStateBlock('I')->fix();  // Imu
 
 
-        // // ================ PROCESS IMU DATA
+        // // ================ PROCESS Imu DATA
         // Vector6d data_imu;
         // data_imu << -wolf::gravity(), 0,0,0;
-        // CaptureIMUPtr cap_imu = std::make_shared<CaptureIMU>(tB_, sen_imu_, data_imu, sen_imu_->getNoiseCov(), bias_imu_); //set data on IMU (measure only gravity here)
+        // CaptureImuPtr cap_imu = std::make_shared<CaptureImu>(tB_, sen_imu_, data_imu, sen_imu_->getNoiseCov(), bias_imu_); //set data on Imu (measure only gravity here)
         // // process data in capture
         // // sen_imu_->process(cap_imu);
 
         // ================ FACTOR INERTIAL KINEMATICS ON KFB
-        Vector3d pBC_measB = zero3; 
+        Vector3d pBC_measB = zero3;
         Vector3d vBC_measB = zero3;
-        Vector3d w_measB = zero3; 
+        Vector3d w_measB = zero3;
         Vector9d meas_ikinB; meas_ikinB << pBC_measB, vBC_measB, w_measB;
-        CaptureBasePtr cap_ikinB = CaptureBase::emplace<CaptureBase>(KFB_, "CaptureInertialKinematics", tB_, nullptr);
-        auto feat_ikinB = FeatureBase::emplace<FeatureInertialKinematics>(cap_ikinB, meas_ikinB, Qkin, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
-        feat_ikinB_ = static_pointer_cast<FeatureInertialKinematics>(feat_ikinB); // !! auto feat_ikinB_ creates no compilation error but a segfault
-        auto fac_inkinB = FactorBase::emplace<FactorInertialKinematics>(feat_ikinB_, feat_ikinB_, KFB_->getStateBlock("I"), nullptr, false);
+        CIKB_ = CaptureBase::emplace<CaptureInertialKinematics>(KFB_, tB_, SIK_, meas_ikinB, Iq, Lq, bias_p_);
+        CIKB_->getSensorIntrinsic()->fix(); // IK bias
+        FIKB_ = FeatureBase::emplace<FeatureInertialKinematics>(CIKB_, meas_ikinB, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        auto fac_inkinB = FactorBase::emplace<FactorInertialKinematics>(FIKB_, FIKB_, KFB_->getStateBlock('I'), nullptr, false);
 
 
         // FORCE TORQUE FACTOR BETWEEEN KFA AND KFB
@@ -298,100 +324,175 @@ class FactorInertialKinematics_2KF : public testing::Test
         Vector6d tau_meas; tau_meas << tau1, tau2;
         Matrix<double,14,1> kin_meas; kin_meas << pbl1, pbl2, bql1, bql2;
 
-        Matrix6d cov_f =   1e-4 * Matrix6d::Identity(); 
-        Matrix6d cov_tau = 1e-4 * Matrix6d::Identity(); 
-        Matrix3d cov_pbc = 1e-4 * Matrix3d::Identity(); 
+        Matrix6d cov_f =   1e-4 * Matrix6d::Identity();
+        Matrix6d cov_tau = 1e-4 * Matrix6d::Identity();
+        Matrix3d cov_pbc = 1e-4 * Matrix3d::Identity();
 
         CaptureBasePtr cap_ftB = CaptureBase::emplace<CaptureBase>(KFB_, "CaptureForceTorque", tB_, nullptr);
-        feat_ftAB_ = FeatureBase::emplace<FeatureForceTorque>(cap_ftB,
-                        tB_ - tA_, mass_,  
+        FFTAB_ = FeatureBase::emplace<FeatureForceTorque>(cap_ftB,
+                        tB_ - tA_, mass_,
                         f_meas, tau_meas, pbc, kin_meas,
                         cov_f, cov_tau, cov_pbc);
-        FactorForceTorquePtr fac_ftB = FactorBase::emplace<FactorForceTorque>(feat_ftAB_, feat_ftAB_, KFA_, nullptr, false);
+
+        FactorForceTorquePtr fac_ftAB = FactorBase::emplace<FactorForceTorque>(FFTAB_, FFTAB_, KFA_, CIKA_->getSensorIntrinsic(), nullptr, false);
+
+    }
+};
+
+
+class FactorInertialKinematics_2KF_foot1turned : public FactorInertialKinematics_2KF
+{
+    protected:
+    void SetUp() override
+    {
+        cout << "\n\n\nFactorInertialKinematics_2KF_foot1turned" << endl;
+        FactorInertialKinematics_2KF::SetUp();
+        problem_->print(3,false,true,true);
+        Vector4d WqL; WqL.setRandom(); WqL.normalize();  // random unitary quaternion
+        Quaterniond quat_WqL(WqL.data());
+        Matrix<double, 14, 1> kin_meas; kin_meas << zero3, zero3, WqL, 0,0,0,1;
+        Vector3d f1 = -mass_*wolf::gravity()/2;
+        f1 = quat_WqL.conjugate() * f1;  // Rotate force meas accordingly
+        Vector3d f2 = -mass_*wolf::gravity()/2;
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setKinMeas(kin_meas);
+        FFTAB_->setForcesMeas(fmeas);
+        // problem_->print(3,false,true,true);
+    }
+};
+
+class FactorInertialKinematics_2KF_foot2turned : public FactorInertialKinematics_2KF
+{
+    protected:
+    void SetUp() override
+    {
+        FactorInertialKinematics_2KF::SetUp();
+        Vector4d WqL; WqL.setRandom(); WqL.normalize();  // random unitary quaternion
+        Quaterniond quat_WqL(WqL.data());
+        Matrix<double, 14, 1> kin_meas; kin_meas << zero3, zero3, 0,0,0,1, WqL;
+        Vector3d f1 = -mass_*wolf::gravity()/2;
+        Vector3d f2 = -mass_*wolf::gravity()/2;
+        f2 = quat_WqL.conjugate() * f2;  // Rotate force meas accordingly
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setKinMeas(kin_meas);
+        FFTAB_->setForcesMeas(fmeas);
     }
+};
 
-    virtual void TearDown(){}
+class FactorInertialKinematics_2KF_singleContactFoot2turned : public FactorInertialKinematics_2KF
+{
+    protected:
+    void SetUp() override
+    {
+        FactorInertialKinematics_2KF::SetUp();
+        Vector4d WqL; WqL.setRandom(); WqL.normalize();  // random unitary quaternion
+        Quaterniond quat_WqL(WqL.data());
+        Matrix<double, 14, 1> kin_meas; kin_meas << zero3, zero3, WqL, 0,0,0,1;
+        Vector3d f1 = -mass_*wolf::gravity();
+        f1 = quat_WqL.conjugate() * f1;  // Rotate force meas accordingly
+        Vector3d f2 = zero3;
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setKinMeas(kin_meas);
+        FFTAB_->setForcesMeas(fmeas);
+    }
+};
+
+class FactorInertialKinematics_2KF_singleContactFoot1turned : public FactorInertialKinematics_2KF
+{
+    protected:
+    void SetUp() override
+    {
+        FactorInertialKinematics_2KF::SetUp();
+        Vector4d WqL; WqL.setRandom(); WqL.normalize();  // random unitary quaternion
+        Quaterniond quat_WqL(WqL.data());
+        Matrix<double, 14, 1> kin_meas; kin_meas << zero3, zero3, 0,0,0,1, WqL;
+        Vector3d f1 = zero3;
+        Vector3d f2 = -mass_*wolf::gravity();
+        f2 = quat_WqL.conjugate() * f2;  // Rotate force meas accordingly
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setKinMeas(kin_meas);
+        FFTAB_->setForcesMeas(fmeas);
+    }
 };
 
 
+
+
 class FactorInertialKinematics_2KF_ForceX : public FactorInertialKinematics_2KF
 {
     protected:
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_2KF::SetUp();
-        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<mass_*Acc1/2,0,0).finished()); 
-        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<mass_*Acc1/2,0,0).finished()); 
-        Vector6d fmeas; fmeas << f1, f2; 
-        feat_ftAB_->setForcesMeas(fmeas);
+        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<mass_*Acc1/2,0,0).finished());
+        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<mass_*Acc1/2,0,0).finished());
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setForcesMeas(fmeas);
     }
 };
 
 class FactorInertialKinematics_2KF_ForceY : public FactorInertialKinematics_2KF
 {
     protected:
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_2KF::SetUp();
-        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,mass_*Acc2/2,0).finished()); 
-        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,mass_*Acc2/2,0).finished()); 
-        Vector6d fmeas; fmeas << f1, f2; 
-        feat_ftAB_->setForcesMeas(fmeas);
+        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,mass_*Acc2/2,0).finished());
+        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,mass_*Acc2/2,0).finished());
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setForcesMeas(fmeas);
     }
 };
 
 class FactorInertialKinematics_2KF_ForceZ : public FactorInertialKinematics_2KF
 {
     protected:
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_2KF::SetUp();
-        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,0,mass_*Acc1/2).finished()); 
-        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,0,mass_*Acc1/2).finished()); 
-        Vector6d fmeas; fmeas << f1, f2; 
-        feat_ftAB_->setForcesMeas(fmeas);
+        Vector3d f1 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,0,mass_*Acc1/2).finished());
+        Vector3d f2 = -mass_*wolf::gravity()/2 + ((Vector3d()<<0,0,mass_*Acc1/2).finished());
+        Vector6d fmeas; fmeas << f1, f2;
+        FFTAB_->setForcesMeas(fmeas);
     }
 };
 
 class FactorInertialKinematics_3KF_ForceX : public FactorInertialKinematics_2KF_ForceX
 {
-    public: 
+    public:
         FrameBasePtr KFC_;
+        CaptureInertialKinematicsPtr CIKC_;
+        TimeStamp tC_;
 
     protected:
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_2KF_ForceX::SetUp();
-        TimeStamp tC;
-        tC.set(2);
-
-        std::cout << "\n\n\n" << feat_ftAB_->getForcesMeas().transpose() << endl;
-        // WHY NOT SAME VALUE AS
+        tC_.set(2);
 
         // KFB_->getStateBlock("O")->unfix();
-        
-        KFC_ = createKFWithCDLBI(problem_, tC, x_origin_,
-                                 zero3, zero3, zero3, bias_p_, bias_imu_);
+
+        KFC_ = createKFWithCDLI(problem_, tC_, x_origin_,
+                                 zero3, zero3, zero3, bias_imu_);
         // Fix the one we cannot estimate
         // KFB_->getP()->fix();
-        KFC_->getO()->fix();  // Not in the FT factor, so should be fixed (or absolute factor)
+        // KFC_->getO()->fix();  // Not in the FT factor, so should be fixed (or absolute factor)
         // KFB_->getV()->fix();
-        KFC_->getStateBlock("I")->fix();
-        KFC_->getStateBlock("B")->fix();
+        KFC_->getStateBlock('I')->fix();
 
         // ================ FACTOR INERTIAL KINEMATICS ON KFB
-        Vector3d pBC_measC = zero3; 
+        Vector3d pBC_measC = zero3;
         Vector3d vBC_measC = zero3;
-        Vector3d w_measC = zero3; 
+        Vector3d w_measC = zero3;
         Vector9d meas_ikinC; meas_ikinC << pBC_measC, vBC_measC, w_measC;
-        Matrix3d Iq; Iq.setIdentity(); 
-        Vector3d Lq = zero3; 
+        Matrix3d Iq; Iq.setIdentity();
+        Vector3d Lq = zero3;
+        Eigen::Matrix9d Q_ikin_errC = computeKinCov(Qp_, Qv_, Qw_, meas_ikinC.head<3>()-bias_p_, meas_ikinC.tail<3>()-bias_imu_.tail<3>(), Iq);
 
-        Matrix9d QkinC = computeKinCov(Qp_, Qv_, Qw_, pBC_measC-bias_p_, w_measC-bias_imu_.tail(3), Iq);
-        CaptureBasePtr cap_ikinB = CaptureBase::emplace<CaptureBase>(KFC_, "CaptureInertialKinematics", tC, nullptr);
-        auto feat_BC = FeatureBase::emplace<FeatureInertialKinematics>(cap_ikinB, meas_ikinC, QkinC, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
-        auto feat_ikinBC = static_pointer_cast<FeatureInertialKinematics>(feat_BC); // !! auto feat_ikinB_ creates no compilation error but a segfault
-        auto fac_inkinBC = FactorBase::emplace<FactorInertialKinematics>(feat_ikinBC, feat_ikinBC, KFC_->getStateBlock("I"), nullptr, false);
+        CIKC_ = CaptureBase::emplace<CaptureInertialKinematics>(KFC_, tC_, SIK_, meas_ikinC, Iq, Lq, bias_p_);
+        CIKC_->getStateBlock('I')->fix(); // IK bias
+        auto feat_ikin_C = FeatureBase::emplace<FeatureInertialKinematics>(CIKC_, meas_ikinC, Q_ikin_errC, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        FactorBase::emplace<FactorInertialKinematics>(feat_ikin_C, feat_ikin_C, CIKC_->getStateBlock('I'), nullptr, false);
 
 
         // FORCE TORQUE FACTOR BETWEEEN KFA AND KFB
@@ -409,160 +510,341 @@ class FactorInertialKinematics_3KF_ForceX : public FactorInertialKinematics_2KF_
         Vector6d tau_meas; tau_meas << tau1, tau2;
         Matrix<double,14,1> kin_meas; kin_meas << pbl1, pbl2, bql1, bql2;
 
-        Matrix6d cov_f =   1e-4 * Matrix6d::Identity(); 
-        Matrix6d cov_tau = 1e-4 * Matrix6d::Identity(); 
-        Matrix3d cov_pbc = 1e-4 * Matrix3d::Identity(); 
+        Matrix6d cov_f =   1e-4 * Matrix6d::Identity();
+        Matrix6d cov_tau = 1e-4 * Matrix6d::Identity();
+        Matrix3d cov_pbc = 1e-4 * Matrix3d::Identity();
 
-        CaptureBasePtr cap_ftC = CaptureBase::emplace<CaptureBase>(KFC_, "CaptureForceTorque", tC, nullptr);
+        CaptureBasePtr cap_ftC = CaptureBase::emplace<CaptureBase>(KFC_, "CaptureForceTorque", tC_, nullptr);
         auto feat_ftBC = FeatureBase::emplace<FeatureForceTorque>(cap_ftC,
-                        tC - tB_, mass_,  
+                        tC_ - tB_, mass_,
                         f_meas, tau_meas, pbc, kin_meas,
                         cov_f, cov_tau, cov_pbc);
-        FactorForceTorquePtr fac_ftB = FactorBase::emplace<FactorForceTorque>(feat_ftBC, feat_ftBC, KFB_, nullptr, false);
+        FactorForceTorquePtr fac_ftBC = FactorBase::emplace<FactorForceTorque>(feat_ftBC, feat_ftBC, KFB_, CIKB_->getSensorIntrinsic(), nullptr, false);
     }
 };
 
+class FactorInertialKinematics_2KF_ForceX_Odom3d : public FactorInertialKinematics_2KF_ForceX
+{
+    protected:
+    void SetUp() override
+    {
+        FactorInertialKinematics_2KF_ForceX::SetUp();
+
+        Vector7d pose_A_B; pose_A_B << (tB_-tA_)*Acc1/2,0,0, 0,0,0,1;
+        Matrix6d rel_pose_cov = Matrix6d::Identity();
+        rel_pose_cov.topLeftCorner(3, 3) *= pow(1e-3, 2);
+        rel_pose_cov.bottomRightCorner(3, 3) *= pow(M_TORAD*1e-3, 2);
+
+        CaptureBasePtr cap_pose_base = CaptureBase::emplace<CapturePose>(KFB_, tB_, nullptr, pose_A_B, rel_pose_cov);
+        FeatureBasePtr ftr_pose_base = FeatureBase::emplace<FeaturePose>(cap_pose_base, pose_A_B, rel_pose_cov);
+        FactorBase::emplace<FactorRelativePose3d>(ftr_pose_base, ftr_pose_base, KFA_, nullptr, false, TOP_MOTION);
+
+        // With Odom3d the bias on relative base COM position is observable, we unfix the KFB state block
+        CIKB_->getStateBlock('I')->unfix();
+        // However this test is not sufficient to observe the bias at KFA
+        // CIKA_->getStateBlock('I')->unfix();  // this is not observable
+    }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////
 // =============== TEST FONCTIONS ======================
 ////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////
 
 
 TEST_F(FactorInertialKinematics_2KF,ZeroMvt)
-{   
-    Vector3d c_exp  = Vector3d::Zero(); 
-    Vector3d cd_exp = Vector3d::Zero(); 
-    Vector3d Lc_exp = Vector3d::Zero(); 
-    Vector3d bp_exp = Vector3d::Zero(); 
+{
+    Vector3d c_exp  = zero3;
+    Vector3d cd_exp = zero3;
+    Vector3d Lc_exp = zero3;
+    Vector3d bp_exp = zero3;
+
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    perturbateAllIfUnFixed(KFA_);
+    perturbateAllIfUnFixed(KFB_);
 
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);  // due to opposite
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+}
+
+TEST_F(FactorInertialKinematics_2KF_foot1turned,ZeroMvt)
+{
+    Vector3d c_exp  = zero3;
+    Vector3d cd_exp = zero3;
+    Vector3d Lc_exp = zero3;
+    Vector3d bp_exp = zero3;
+
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
 
     perturbateAllIfUnFixed(KFA_);
     perturbateAllIfUnFixed(KFB_);
 
-    problem_->print(4,true,true,true);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(4,true,true,true);
-
-    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.head<3>(), 1e-5);
-    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.segment<4>(3), 1e-5);  // due to opposite
-    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.tail<3>(), 1e-5);    
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("B")->getState(), bp_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("B")->getState(), bp_exp, 1e-5);
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);  // due to opposite
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), bp_exp, 1e-5);
 }
 
+TEST_F(FactorInertialKinematics_2KF_foot2turned,ZeroMvt)
+{
+    Vector3d c_exp  = zero3;
+    Vector3d cd_exp = zero3;
+    Vector3d Lc_exp = zero3;
+    Vector3d bp_exp = zero3;
+
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    perturbateAllIfUnFixed(KFA_);
+    perturbateAllIfUnFixed(KFB_);
+
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);  // due to opposite
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+}
+
+
+TEST_F(FactorInertialKinematics_2KF_singleContactFoot1turned,ZeroMvt)
+{
+    Vector3d c_exp  = zero3;
+    Vector3d cd_exp = zero3;
+    Vector3d Lc_exp = zero3;
+    Vector3d bp_exp = zero3;
+
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    perturbateAllIfUnFixed(KFA_);
+    perturbateAllIfUnFixed(KFB_);
+
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);  // due to opposite
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+}
+
+
+TEST_F(FactorInertialKinematics_2KF_singleContactFoot2turned,ZeroMvt)
+{
+    Vector3d c_exp  = zero3;
+    Vector3d cd_exp = zero3;
+    Vector3d Lc_exp = zero3;
+    Vector3d bp_exp = zero3;
+
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    perturbateAllIfUnFixed(KFA_);
+    perturbateAllIfUnFixed(KFB_);
+
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), bp_exp, 1e-5);
+}
+
+
 
 TEST_F(FactorInertialKinematics_2KF_ForceX,ZeroMvt)
-{   
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+{
+    // problem_->print(4,true,true,true);
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
 
     perturbateAllIfUnFixed(KFA_);
     perturbateAllIfUnFixed(KFB_);
 
-    problem_->print(4,true,true,true);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(4,true,true,true);
-
-    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.head<3>(), 1e-5);
-    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.segment<4>(3), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.tail<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("C")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("D")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("C")->getState(), (Vector3d()<<Acc1/2, 0, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("D")->getState(), (Vector3d()<<Acc1, 0, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), (Vector3d()<<(tB_-tA_)*Acc1/2, 0, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), (Vector3d()<<(tB_-tA_)*Acc1, 0, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), zero3, 1e-5);
 }
 
 
 TEST_F(FactorInertialKinematics_2KF_ForceY,ZeroMvt)
-{   
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+{
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
 
     perturbateAllIfUnFixed(KFA_);
     perturbateAllIfUnFixed(KFB_);
 
-    problem_->print(4,true,true,true);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(4,true,true,true);
-
-    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.head<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.segment<4>(3), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.tail<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("C")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("D")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("C")->getState(), (Vector3d()<<0, Acc2/2, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("D")->getState(), (Vector3d()<<0, Acc2, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), (Vector3d()<<0, (tB_ - tA_)*Acc2/2, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), (Vector3d()<<0, (tB_ - tA_)*Acc2, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), zero3, 1e-5);
 }
 
 
 TEST_F(FactorInertialKinematics_2KF_ForceZ,ZeroMvt)
-{   
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+{
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
 
     perturbateAllIfUnFixed(KFA_);
     perturbateAllIfUnFixed(KFB_);
 
-    problem_->print(4,true,true,true);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(4,true,true,true);
-
-    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.head<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.segment<4>(3), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.tail<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("C")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("D")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("C")->getState(), (Vector3d()<<0, 0, Acc1/2).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("D")->getState(), (Vector3d()<<0, 0, Acc1).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), (Vector3d()<<0, 0, (tB_-tA_)*Acc1/2).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), (Vector3d()<<0, 0, (tB_-tA_)*Acc1).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), zero3, 1e-5);
 }
 
-TEST_F(FactorInertialKinematics_3KF_ForceX,ZeroMvt)
-{   
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+// TEST_F(FactorInertialKinematics_3KF_ForceX,ZeroMvt)
+// {
+//     string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+//     perturbateAllIfUnFixed(KFA_);
+//     perturbateAllIfUnFixed(KFB_);
+//     perturbateAllIfUnFixed(KFC_);
+
+//     // problem_->print(4,true,true,true);
+//     report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+//     // std::cout << report << std::endl;
+//     // problem_->print(4,true,true,true);
+
+//     ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+//     // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+//     ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+//     ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), (Vector3d()<<(tB_-tA_)*Acc1/2, 0, 0).finished(), 1e-5);
+//     ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), (Vector3d()<<(tB_-tA_)*Acc1, 0, 0).finished(), 1e-5);
+//     ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(KFC_->getStateBlock('C')->getState(), (Vector3d()<<(tC_-tB_)*Acc1/2 + (tB_-tA_)*Acc1, 0, 0).finished(), 1e-5); // + -> due to initial vel of KFB
+//     ASSERT_MATRIX_APPROX(KFC_->getStateBlock('D')->getState(), (Vector3d()<<(tC_-tB_)*Acc1, 0, 0).finished(), 1e-5);        // No acc btw B and C -> same vel
+//     ASSERT_MATRIX_APPROX(KFC_->getStateBlock('L')->getState(), zero3, 1e-5);
+//     ASSERT_MATRIX_APPROX(CIKC_->getStateBlock('I')->getState(), zero3, 1e-5);
+// }
+
+
+TEST_F(FactorInertialKinematics_2KF_ForceX_Odom3d,ZeroMvt)
+{
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
 
     perturbateAllIfUnFixed(KFA_);
     perturbateAllIfUnFixed(KFB_);
-    perturbateAllIfUnFixed(KFC_);
-
-    problem_->print(4,true,true,true);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(4,true,true,true);
-
-    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.head<3>(), 1e-5);
-    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.segment<4>(3), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.tail<3>(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("C")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("D")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFA_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("C")->getState(), (Vector3d()<<Acc1/2, 0, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("D")->getState(), (Vector3d()<<Acc1, 0, 0).finished(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFB_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFC_->getStateBlock("C")->getState(), (Vector3d()<<Acc1/2 + Acc1, 0, 0).finished(), 1e-5); // + -> due to initial vel of KFB
-    ASSERT_MATRIX_APPROX(KFC_->getStateBlock("D")->getState(), (Vector3d()<<Acc1, 0, 0).finished(), 1e-5);        // No acc btw B and C -> same vel
-    ASSERT_MATRIX_APPROX(KFC_->getStateBlock("L")->getState(), Vector3d::Zero(), 1e-5);
-    ASSERT_MATRIX_APPROX(KFC_->getStateBlock("B")->getState(), Vector3d::Zero(), 1e-5);
+
+    // problem_->print(4,true,true,true);
+    report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+    // problem_->print(4,true,true,true);
+
+    ASSERT_MATRIX_APPROX(KFA_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    // ASSERT_MATRIX_APPROX(KFA_->getO()->getState(), x_origin_.at('O'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getV()->getState(), x_origin_.at('V'), 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('C')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('D')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFA_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKA_->getStateBlock('I')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('C')->getState(), (Vector3d()<<(tB_-tA_)*Acc1/2, 0, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('D')->getState(), (Vector3d()<<(tB_-tA_)*Acc1, 0, 0).finished(), 1e-5);
+    ASSERT_MATRIX_APPROX(KFB_->getStateBlock('L')->getState(), zero3, 1e-5);
+    ASSERT_MATRIX_APPROX(CIKB_->getStateBlock('I')->getState(), zero3, 1e-5);
 }
 
 
diff --git a/test/gtest_factor_inertial_kinematics.cpp b/test/gtest_factor_inertial_kinematics.cpp
index 760db647769e9ad175f2c508284d94443187cbd4..45034d63e1e90ab4c86172a8ba81a88317cd6bb4 100644
--- a/test/gtest_factor_inertial_kinematics.cpp
+++ b/test/gtest_factor_inertial_kinematics.cpp
@@ -1,3 +1,24 @@
+//--------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--------
 
 /**
  * \file gtest_factor_inertial_kinematics.cpp
@@ -9,10 +30,10 @@
 /*
 
 
-Organisation: For each test, the problem, sensors, factors (if possible) are instanciated in a base class inheriting fromtesting::Test only. 
+Organisation: For each test, the problem, sensors, factors (if possible) are instanciated in a base class inheriting fromtesting::Test only.
 Then, each test case is derived in a child class which defines the data processed by the different sensors and expected estimated test values.
-Finally, each of these child classes is used in one or several Test in which basically the expected values are compared against estimation and 
-solve is done with a perturbated system. 
+Finally, each of these child classes is used in one or several Test in which basically the expected values are compared against estimation and
+solve is done with a perturbated system.
 
 Tests list:
 
@@ -32,20 +53,23 @@ FactorInertialKinematics_1KF_1v_bfix,ZeroMvt:
 
 // WOLF
 #include <core/problem/problem.h>
-#include <core/ceres_wrapper/ceres_manager.h>
+#include <core/ceres_wrapper/solver_ceres.h>
 #include <core/math/rotations.h>
 #include <core/capture/capture_base.h>
 #include <core/feature/feature_base.h>
 #include <core/factor/factor_block_absolute.h>
 
-// IMU
-#include "IMU/internal/config.h"
-#include "IMU/capture/capture_IMU.h"
-#include "IMU/processor/processor_IMU.h"
-#include "IMU/sensor/sensor_IMU.h"
+// Imu
+//#include "imu/internal/config.h"
+//#include "imu/capture/capture_imu.h"
+//#include "imu/processor/processor_imu.h"
+//#include "imu/sensor/sensor_imu.h"
 
 // BODYDYNAMICS
 #include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
 #include "bodydynamics/feature/feature_inertial_kinematics.h"
 #include "bodydynamics/factor/factor_inertial_kinematics.h"
 
@@ -55,382 +79,303 @@ using namespace wolf;
 using namespace Eigen;
 using namespace std;
 
-
-Matrix9d computeKinJac(const Vector3d& w_unb,
-                       const Vector3d& p_unb,
-                       const Matrix3d& Iq)
-{
-    Matrix9d J; J.setZero();
-
-    Matrix3d wx = skew(w_unb);
-    Matrix3d px = skew(p_unb);
-    // Starting from top left, to the right and then next row
-    J.topLeftCorner<3,3>() = Matrix3d::Identity();
-    // J.block<3,3>(0,3) = Matrix3d::Zero();
-    // J.topRightCorner<3,3>() = Matrix3d::Zero();
-    J.block<3,3>(3,0) = -wx;
-    J.block<3,3>(3,3) = -Matrix3d::Identity();
-    J.block<3,3>(3,6) = px;
-    // J.bottomLeftCorner<3,3>() = Matrix3d::Zero();
-    // J.block<3,3>(6,3) = Matrix3d::Zero();
-    J.bottomRightCorner<3,3>() = -Iq;
-
-    return J;
-}
-
-Matrix9d computeKinCov(const Matrix3d& Qp,
-                              const Matrix3d& Qv, 
-                              const Matrix3d& Qw,
-                              const Vector3d& w_unb,
-                              const Vector3d& p_unb,
-                              const Matrix3d& Iq)
-{
-    Matrix9d cov; cov.setZero();
-
-    Matrix3d wx = skew(w_unb);
-    Matrix3d px = skew(p_unb);
-    // Starting from top left, to the right and then next row
-    cov.topLeftCorner<3,3>() = Qp;
-    cov.block<3,3>(0,3) = Qp * wx;
-    // cov.topRightCorner<3,3>() = Matrix3d::Zero();
-    cov.block<3,3>(3,0) = cov.block<3,3>(0,3).transpose();
-    cov.block<3,3>(3,3) = -wx*Qp*wx + Qv - px*Qw*px;
-    cov.block<3,3>(3,6) = -px*Qw*Iq;
-    // cov.bottomLeftCorner<3,3>() = Matrix3d::Zero();
-    cov.block<3,3>(6,3) = Iq*Qw*px;
-    cov.bottomRightCorner<3,3>() = Iq*Qw*Iq;
-
-    return cov;
-}
-
-
+const Vector3d ZERO3 = Vector3d::Zero();
+const Vector6d ZERO6 = Vector6d::Zero();
 
 class FactorInertialKinematics_1KF : public testing::Test
 {
     public:
         wolf::TimeStamp t_;
         ProblemPtr problem_;
-        // SensorIMUPtr sen_imu;
-        CeresManager* ceres_manager_;
+        // SensorImuPtr sen_imu;
+        SolverCeresPtr solver_;
         // ProcessorBasePtr processor;
-        // ProcessorIMUPtr processor_imu;
-        VectorXd x_origin_;
-        MatrixXd P_origin_;
+        // ProcessorImuPtr processor_imu;
+        VectorComposite x_origin_; // initial state
+        VectorComposite s_origin_; // initial sigmas for prior
         FrameBasePtr KF0_;
+        SensorInertialKinematicsPtr SIK_;
+        CaptureInertialKinematicsPtr CIK0_;
         Eigen::Matrix3d Qp_, Qv_, Qw_;
-        Eigen::Vector3d bias_p_, bias_imu_;
         FeatureInertialKinematicsPtr feat_in_;
+        StateBlockPtr sbbimu_;
+        Vector3d bias_p_;
+        Vector6d bias_imu_;
 
-    virtual void SetUp()
+    void SetUp() override
     {
         //===================================================== SETTING PROBLEM
         // WOLF PROBLEM
         problem_ = Problem::create("POV", 3);
 
         // CERES WRAPPER
-        ceres::Solver::Options ceres_options;
-        // ceres_options.minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
-        // ceres_options.max_line_search_step_contraction = 1e-3;
-        // ceres_options.max_num_iterations = 1e4;
-        ceres_manager_ = new CeresManager(problem_, ceres_options);
+        solver_ = std::make_shared<SolverCeres>(problem_);
+        // solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+        // solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
+        // solver_->getSolverOptions().max_num_iterations = 1e4;
 
         //===================================================== INITIALIZATION
-        x_origin_.resize(10);
-        x_origin_ << 0,0,0, 0,0,0,1, 0,0,0;
-        P_origin_ = pow(1e-3, 2) * Eigen::Matrix9d::Identity();
-        t_.set(0);
-
-
-        // Set origin of the problem
-        KF0_ = problem_->setPrior(x_origin_, P_origin_.topLeftCorner<6,6>(), t_, 0.005);
-        // Specific factor for origin velocity (not covered by setPrior)
-        CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF0_, "Vel0", t_);
-        FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin_.tail(3), P_origin_.bottomRightCorner<3,3>());
-        FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, KF0_->getV());
+        x_origin_ = VectorComposite("POV", {Vector3d(0,0,0), Quaterniond::Identity().coeffs(), Vector3d(0,0,0)});
+        s_origin_ = VectorComposite("POV", {1e-3*Vector3d(1,1,1), 1e-3*Vector3d(1,1,1), 1e-3*Vector3d(1,1,1)});
+        t_.set(0.0);
+        KF0_ = problem_->setPriorFactor(x_origin_, s_origin_, t_);
 
+        ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+        SIK_ = std::make_shared<SensorInertialKinematics>(Eigen::VectorXd(0), intr_ik);
 
         // ADD THE STATEBLOCKS AND SET THEIR DEFAULT VALUES
-        Vector3d zero3; zero3 << 0,0,0;
-        Vector6d zero6; zero6 << zero3, zero3;
-        Vector3d bias_p_ = zero3;
-        Vector6d bias_imu_ = zero6;
-        StateBlockPtr sbc = make_shared<StateBlock>(zero3);
-        StateBlockPtr sbd = make_shared<StateBlock>(zero3);
-        StateBlockPtr sbL = make_shared<StateBlock>(zero3);
+        bias_p_ = ZERO3;
+        bias_imu_ = ZERO6;
+        StateBlockPtr sbc = make_shared<StateBlock>(ZERO3);
+        StateBlockPtr sbd = make_shared<StateBlock>(ZERO3);
+        StateBlockPtr sbL = make_shared<StateBlock>(ZERO3);
         StateBlockPtr sbb = make_shared<StateBlock>(bias_p_);
-        StateBlockPtr sbbimu = make_shared<StateBlock>(bias_imu_);
+        sbbimu_ = make_shared<StateBlock>(bias_imu_);
 
-        KF0_->addStateBlock("C", sbc); problem_->notifyStateBlock(sbc,ADD);
-        KF0_->addStateBlock("D", sbd); problem_->notifyStateBlock(sbd,ADD);
-        KF0_->addStateBlock("L", sbL); problem_->notifyStateBlock(sbL,ADD);
-        KF0_->addStateBlock("B", sbb); problem_->notifyStateBlock(sbb,ADD);
-        KF0_->addStateBlock("I", sbbimu); problem_->notifyStateBlock(sbbimu,ADD);
+        KF0_->addStateBlock('C', sbc, problem_);
+        KF0_->addStateBlock('D', sbd, problem_);
+        KF0_->addStateBlock('L', sbL, problem_);
+        KF0_->addStateBlock('I', sbbimu_, problem_);  // IMU bias
 
         // Fix the one we cannot estimate
-        // KF0_->getP()->fix();
-        // KF0_->getO()->fix();
-        // KF0_->getV()->fix();
-        KF0_->getStateBlock("I")->fix();
+        KF0_->getStateBlock('I')->fix();  // Imu
 
         // =============== SET DEFAULT" KIN MEASUREMENT AND COVARIANCE
         // Measurements
-        Vector3d pBC_meas = zero3; 
-        Vector3d vBC_meas = zero3;
-        Vector3d w_meas = zero3; 
+        Vector3d pBC_meas = ZERO3;
+        Vector3d vBC_meas = ZERO3;
+        Vector3d w_meas = ZERO3;
 
         // momentum parameters
-        Matrix3d Iq; Iq.setIdentity(); 
-        Vector3d Lq = zero3; 
+        Matrix3d Iq; Iq.setIdentity();
+        Vector3d Lq = ZERO3;
 
         Qp_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
         Qv_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
         Qw_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
-        // Qn.setZero();
-        // Qn.topLeftCorner<3,3>() = Qp_;
-        // Qn.block<3,3>(3,3) = Qv_;
-        // Qn.bottomRightCorner<3,3>() = Qw_;
-
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-
-        // // Compute with the traditionnal method for comparison (unitest to extract later)
-        // Matrix9d J = computeKinJac(pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-        // Matrix9d QkinFromJ = J * Qn * J.transpose();
-
-        // =============== CREATE CAPURE/FEATURE/FACTOR
-        Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
-        CaptureBasePtr cap = CaptureBase::emplace<CaptureBase>(KF0_, "CaptureInertialKinematics", t_, nullptr);
-        auto feat = FeatureBase::emplace<FeatureInertialKinematics>(cap, meas, Qkin, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
-        feat_in_ = static_pointer_cast<FeatureInertialKinematics>(feat); // !! auto feat_in_ creates no compilation error but a segfault
-        auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu, nullptr, false);
+
+        // =============== CREATE CAPURE
+        Vector9d meas_ikin0; meas_ikin0 << pBC_meas, vBC_meas, w_meas;
+        CIK0_ = CaptureBase::emplace<CaptureInertialKinematics>(KF0_, t_, SIK_, meas_ikin0, Iq, Lq, bias_p_);
     }
 
-    virtual void TearDown(){}
+    void TearDown() override{}
 };
 
 
 class FactorInertialKinematics_1KF_Meas0_bpfix : public FactorInertialKinematics_1KF
 {
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_1KF::SetUp();
         // Fix the bp bias
-        KF0_->getStateBlock("B")->fix();
-        KF0_->getStateBlock("C")->unfix();
-        Eigen::Vector3d zero3 = Eigen::Vector3d::Zero(); 
+        CIK0_->getStateBlock('I')->fix();
+        KF0_->getStateBlock('C')->unfix();
+        Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
 
-        Eigen::Vector3d pBC_meas = zero3; 
-        Eigen::Vector3d vBC_meas = zero3;
-        Eigen::Vector3d w_meas = zero3; 
+        Eigen::Vector3d pBC_meas = ZERO3;
+        Eigen::Vector3d vBC_meas = ZERO3;
+        Eigen::Vector3d w_meas = ZERO3;
         Eigen::Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
         // momentum parameters
-        Eigen::Matrix3d Iq; Iq.setIdentity(); 
-        Eigen::Vector3d Lq = zero3; 
-
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-
+        Eigen::Matrix3d Iq; Iq.setIdentity();
+        Eigen::Vector3d Lq = ZERO3;
 
-        feat_in_->setMeasurement(meas);
-        feat_in_->setMeasurementCovariance(Qkin);
-        feat_in_->setBIq(Iq);
-        feat_in_->setBLcm(Lq);
+        // =============== CREATE FEATURE/FACTOR
+        Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp_, Qv_, Qw_, meas.head<3>()-bias_p_, meas.tail<3>()-bias_imu_.tail<3>(), Iq);
+        feat_in_ = FeatureBase::emplace<FeatureInertialKinematics>(CIK0_, meas, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu_, nullptr, false);
     }
 };
 
 class FactorInertialKinematics_1KF_1p_bpfix : public FactorInertialKinematics_1KF
 {
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_1KF::SetUp();
         // Fix the bp bias
-        KF0_->getStateBlock("B")->fix();
-        KF0_->getStateBlock("C")->unfix();
-        Eigen::Vector3d zero3 = Eigen::Vector3d::Zero(); 
+        CIK0_->getStateBlock('I')->fix();
+        KF0_->getStateBlock('C')->unfix();
+        Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
 
-        Eigen::Vector3d pBC_meas; pBC_meas << 1,0,0; 
-        Eigen::Vector3d vBC_meas = zero3;
-        Eigen::Vector3d w_meas = zero3; 
+        Eigen::Vector3d pBC_meas; pBC_meas << 1,0,0;
+        Eigen::Vector3d vBC_meas = ZERO3;
+        Eigen::Vector3d w_meas = ZERO3;
         Eigen::Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
         // momentum parameters
-        Eigen::Matrix3d Iq; Iq.setIdentity(); 
-        Eigen::Vector3d Lq = zero3; 
+        Eigen::Matrix3d Iq; Iq.setIdentity();
+        Eigen::Vector3d Lq = ZERO3;
 
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-
-        feat_in_->setMeasurement(meas);
-        feat_in_->setMeasurementCovariance(Qkin);
-        feat_in_->setBIq(Iq);
-        feat_in_->setBLcm(Lq);
+        // =============== CREATE FEATURE/FACTOR
+        Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp_, Qv_, Qw_, meas.head<3>()-bias_p_, meas.tail<3>()-bias_imu_.tail<3>(), Iq);
+        feat_in_ = FeatureBase::emplace<FeatureInertialKinematics>(CIK0_, meas, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu_, nullptr, false);
     }
 };
 
 class FactorInertialKinematics_1KF_m1p_pfix : public FactorInertialKinematics_1KF
 {
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_1KF::SetUp();
         // Fix the COM position
-        KF0_->getStateBlock("B")->unfix();
-        KF0_->getStateBlock("C")->fix();
-        Eigen::Vector3d zero3 = Eigen::Vector3d::Zero(); 
-
-        Eigen::Vector3d pBC_meas; pBC_meas << 1,0,0; 
-        Eigen::Vector3d vBC_meas = zero3;
-        Eigen::Vector3d w_meas = zero3; 
-        bias_p_ << 1,0,0; 
+        CIK0_->getStateBlock('I')->unfix();
+        KF0_->getStateBlock('C')->fix();
+        Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
+
+        Eigen::Vector3d pBC_meas; pBC_meas << 1,0,0;
+        Eigen::Vector3d vBC_meas = ZERO3;
+        Eigen::Vector3d w_meas = ZERO3;
+        bias_p_ << 1,0,0;
         Eigen::Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
         // momentum parameters
-        Eigen::Matrix3d Iq; Iq.setIdentity(); 
-        Eigen::Vector3d Lq = zero3; 
-
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
+        Eigen::Matrix3d Iq; Iq.setIdentity();
+        Eigen::Vector3d Lq = ZERO3;
 
-        feat_in_->setMeasurement(meas);
-        feat_in_->setMeasurementCovariance(Qkin);
-        feat_in_->setBIq(Iq);
-        feat_in_->setBLcm(Lq);
+        // =============== CREATE FEATURE/FACTOR
+        Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp_, Qv_, Qw_, meas.head<3>()-bias_p_, meas.tail<3>()-bias_imu_.tail<3>(), Iq);
+        feat_in_ = FeatureBase::emplace<FeatureInertialKinematics>(CIK0_, meas, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu_, nullptr, false);
     }
 };
 
 class FactorInertialKinematics_1KF_1v_bfix : public FactorInertialKinematics_1KF
 {
-    virtual void SetUp() override
+    void SetUp() override
     {
         FactorInertialKinematics_1KF::SetUp();
         // Fix the bp bias
-        Eigen::Vector3d zero3 = Eigen::Vector3d::Zero(); 
-        KF0_->getStateBlock("B")->fix();
-        KF0_->getStateBlock("C")->unfix();
-        bias_p_ = zero3; 
-        KF0_->getStateBlock("B")->setState(bias_p_);
+        Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
+        CIK0_->getStateBlock('I')->fix();
+        KF0_->getStateBlock('C')->unfix();
+        bias_p_ = ZERO3;
+        CIK0_->getStateBlock('I')->setState(bias_p_);
 
-        Eigen::Vector3d pBC_meas = zero3; 
+        Eigen::Vector3d pBC_meas = ZERO3;
         Eigen::Vector3d vBC_meas; vBC_meas << 1,0,0;
-        Eigen::Vector3d w_meas = zero3; 
+        Eigen::Vector3d w_meas = ZERO3;
         Eigen::Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
         // momentum parameters
-        Eigen::Matrix3d Iq; Iq.setIdentity(); 
-        Eigen::Vector3d Lq = zero3; 
+        Eigen::Matrix3d Iq; Iq.setIdentity();
+        Eigen::Vector3d Lq = ZERO3;
 
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-
-        feat_in_->setMeasurement(meas);
-        feat_in_->setMeasurementCovariance(Qkin);
-        feat_in_->setBIq(Iq);
-        feat_in_->setBLcm(Lq);
+        // =============== CREATE FEATURE/FACTOR
+        Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp_, Qv_, Qw_, meas.head<3>()-bias_p_, meas.tail<3>()-bias_imu_.tail<3>(), Iq);
+        feat_in_ = FeatureBase::emplace<FeatureInertialKinematics>(CIK0_, meas, Q_ikin_err, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+        auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu_, nullptr, false);
     }
 };
 
 
-class FactorInertialKinematics_IMU : public testing::Test
-{
-    public:
-        wolf::TimeStamp t_;
-        ProblemPtr problem_;
-        CeresManager* ceres_manager_;
-        VectorXd x_origin_;
-        MatrixXd P_origin_;
-        FrameBasePtr KF0_;
-        Eigen::Matrix3d Qp_, Qv_, Qw_;
-        Eigen::Vector3d bias_p_, bias_imu_;
-        FeatureInertialKinematicsPtr feat_in_;
-        SensorIMUPtr sen_imu_;
-        ProcessorIMUPtr processor_imu_;
-
-    virtual void SetUp()
-    {
-        // using shared_ptr;
-        // using make_shared;
-        // using static_pointer_cast;
-
-        std::string bodydynamics_root = _WOLF_BODYDYNAMICS_ROOT_DIR;
-
-        //===================================================== SETTING PROBLEM
-        // WOLF PROBLEM
-        problem_ = Problem::create("POV", 3);
-
-        // CERES WRAPPER
-        ceres::Solver::Options ceres_options;
-        // ceres_options.minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
-        // ceres_options.max_line_search_step_contraction = 1e-3;
-        // ceres_options.max_num_iterations = 1e4;
-        ceres_manager_ = new CeresManager(problem_, ceres_options);
-
-        //===================================================== INITIALIZATION
-        x_origin_.resize(10);
-        x_origin_ << 0,0,0, 0,0,0,1, 0,0,0;
-        P_origin_ = pow(1e-3, 2) * Eigen::Matrix9d::Identity();
-        t_.set(0);
-
-
-        // Set origin of the problem
-        KF0_ = problem_->setPrior(x_origin_, P_origin_.topLeftCorner<6,6>(), t_, 0.005);
-        // Specific factor for origin velocity (not covered by setPrior)
-        CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF0_, "Vel0", t_);
-        FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin_.tail(3), P_origin_.bottomRightCorner<3,3>());
-        FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, KF0_->getV());
-
-
-        // SENSOR + PROCESSOR IMU
-        SensorBasePtr sen0_ptr = problem_->installSensor("SensorIMU", "Main IMU", (Vector7d()<<0,0,0,0,0,0,1).finished(), bodydynamics_root + "/demos/sensor_imu.yaml");
-        sen_imu_ = std::static_pointer_cast<SensorIMU>(sen0_ptr);
-        ProcessorBasePtr proc = problem_->installProcessor("ProcessorImu", "IMU pre-integrator", "Main IMU", bodydynamics_root + "/demos/processor_imu.yaml");
-        processor_imu_ = std::static_pointer_cast<ProcessorIMU>(proc);
-
-
-        // ADD THE STATEBLOCKS AND SET THEIR DEFAULT VALUES
-        Vector3d zero3; zero3 << 0,0,0;
-        Vector6d zero6; zero6 << zero3, zero3;
-        Vector3d bias_p_ = zero3;
-        Vector6d bias_imu_ = zero6;
-        StateBlockPtr sbc = make_shared<StateBlock>(zero3);
-        StateBlockPtr sbd = make_shared<StateBlock>(zero3);
-        StateBlockPtr sbL = make_shared<StateBlock>(zero3);
-        StateBlockPtr sbb = make_shared<StateBlock>(bias_p_);
-
-        KF0_->addStateBlock("C", sbc); problem_->notifyStateBlock(sbc,ADD);
-        KF0_->addStateBlock("D", sbd); problem_->notifyStateBlock(sbd,ADD);
-        KF0_->addStateBlock("L", sbL); problem_->notifyStateBlock(sbL,ADD);
-        KF0_->addStateBlock("B", sbb); problem_->notifyStateBlock(sbb,ADD);
-
-        // Fix the one we cannot estimate
-        // KF0_->getP()->fix();
-        // KF0_->getO()->fix();
-        // KF0_->getV()->fix();
-
-        // =============== SET DEFAULT" KIN MEASUREMENT AND COVARIANCE
-        // Measurements
-        Vector3d pBC_meas = zero3; 
-        Vector3d vBC_meas = zero3;
-        Vector3d w_meas = zero3; 
-
-        // momentum parameters
-        Matrix3d Iq; Iq.setIdentity(); 
-        Vector3d Lq = zero3; 
-
-        Qp_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
-        Qv_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
-        Qw_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
-        // Qn.setZero();
-        // Qn.topLeftCorner<3,3>() = Qp_;
-        // Qn.block<3,3>(3,3) = Qv_;
-        // Qn.bottomRightCorner<3,3>() = Qw_;
-
-        Matrix9d Qkin = computeKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-
-        // // Compute with the traditionnal method for comparison (unitest to extract later)
-        // Matrix9d J = computeKinJac(pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
-        // Matrix9d QkinFromJ = J * Qn * J.transpose();
-
-        // =============== CREATE CAPURE/FEATURE/FACTOR
-        Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
-        CaptureBasePtr cap = CaptureBase::emplace<CaptureBase>(KF0_, "CaptureInertialKinematics", t_, nullptr);
-        auto feat = FeatureBase::emplace<FeatureInertialKinematics>(cap, meas, Qkin, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
-        feat_in_ = static_pointer_cast<FeatureInertialKinematics>(feat); // !! auto feat_in_ creates no compilation error but a segfault
-        // auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu, nullptr, false);
-    }
-
-    virtual void TearDown(){}
-};
+//class FactorInertialKinematic_imu : public testing::Test
+//{
+//    public:
+//        wolf::TimeStamp t_;
+//        ProblemPtr problem_;
+//        SolverCeres* solver_;
+//        VectorXd x_origin_;
+//        MatrixXd P_origin_;
+//        FrameBasePtr KF0_;
+//        Eigen::Matrix3d Qp_, Qv_, Qw_;
+//        Eigen::Vector3d bias_p_, bias_imu_;
+//        FeatureInertialKinematicsPtr feat_in_;
+//        SensorImuPtr sen_imu_;
+//        ProcessorImuPtr processor_imu_;
+//
+//    virtual void SetUp()
+//    {
+//        // using shared_ptr;
+//        // using make_shared;
+//        // using static_pointer_cast;
+//
+//        std::string bodydynamics_root = _WOLF_BODYDYNAMICS_ROOT_DIR;
+//
+//        //===================================================== SETTING PROBLEM
+//        // WOLF PROBLEM
+//        problem_ = Problem::create("POV", 3);
+//
+//        // CERES WRAPPER
+//        ceres::Solver::Options ceres_options;
+//        // ceres_options.minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+//        // ceres_options.max_line_search_step_contraction = 1e-3;
+//        // ceres_options.max_num_iterations = 1e4;
+//        solver_ = new SolverCeres(problem_, ceres_options);
+//
+//        //===================================================== INITIALIZATION
+//        x_origin_.resize(10);
+//        x_origin_ << 0,0,0, 0,0,0,1, 0,0,0;
+//        P_origin_ = pow(1e-3, 2) * Eigen::Matrix9d::Identity();
+//        t_.set(0);
+//
+//
+//        // Set origin of the problem
+//        KF0_ = problem_->setPrior(x_origin_, P_origin_.topLeftCorner<6,6>(), t_, 0.005);
+//        // Specific factor for origin velocity (not covered by setPrior)
+//        CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF0_, "Vel0", t_);
+//        FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin_.tail(3), P_origin_.bottomRightCorner<3,3>());
+//        FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, KF0_->getV());
+//
+//
+//        // SENSOR + PROCESSOR Imu
+//        SensorBasePtr sen0_ptr = problem_->installSensor("SensorImu", "Main Imu", (Vector7d()<<0,0,0,0,0,0,1).finished(), bodydynamics_root + "/demos/sensor_imu.yaml");
+//        sen_imu_ = std::static_pointer_cast<SensorImu>(sen0_ptr);
+//        ProcessorBasePtr proc = problem_->installProcessor("ProcessorImu", "Imu pre-integrator", "Main Imu", bodydynamics_root + "/demos/processor_imu.yaml");
+//        processor_imu_ = std::static_pointer_cast<ProcessorImu>(proc);
+//
+//
+//        // ADD THE STATEBLOCKS AND SET THEIR DEFAULT VALUES
+//        Vector3d ZERO3; ZERO3 << 0,0,0;
+//        Vector6d ZERO6; ZERO6 << ZERO3, ZERO3;
+//        Vector3d bias_p_ = ZERO3;
+//        Vector6d bias_imu_ = ZERO6;
+//        StateBlockPtr sbc = make_shared<StateBlock>(ZERO3);
+//        StateBlockPtr sbd = make_shared<StateBlock>(ZERO3);
+//        StateBlockPtr sbL = make_shared<StateBlock>(ZERO3);
+//        StateBlockPtr sbb = make_shared<StateBlock>(bias_p_);
+//
+//        KF0_->addStateBlock("C", sbcproblem_);
+//        KF0_->addStateBlock("D", sbdproblem_);
+//        KF0_->addStateBlock("L", sbLproblem_);
+//
+//        // Fix the one we cannot estimate
+//        // KF0_->getP()->fix();
+//        // KF0_->getO()->fix();
+//        // KF0_->getV()->fix();
+//
+//        // =============== SET DEFAULT" KIN MEASUREMENT AND COVARIANCE
+//        // Measurements
+//        Vector3d pBC_meas = ZERO3;
+//        Vector3d vBC_meas = ZERO3;
+//        Vector3d w_meas = ZERO3;
+//
+//        // momentum parameters
+//        Matrix3d Iq; Iq.setIdentity();
+//        Vector3d Lq = ZERO3;
+//
+//        Qp_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
+//        Qv_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
+//        Qw_ = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
+//        // Qn.setZero();
+//        // Qn.topLeftCorner<3,3>() = Qp_;
+//        // Qn.block<3,3>(3,3) = Qv_;
+//        // Qn.bottomRightCorner<3,3>() = Qw_;
+//
+//        Matrix9d Qkin = computeIKinCov(Qp_, Qv_, Qw_, pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
+//
+//        // // Compute with the traditionnal method for comparison (unitest to extract later)
+//        // Matrix9d J = computeIKinJac(pBC_meas-bias_p_, w_meas-bias_imu_.tail(3), Iq);
+//        // Matrix9d QkinFromJ = J * Qn * J.transpose();
+//
+//        // =============== CREATE CAPURE/FEATURE/FACTOR
+//        Vector9d meas; meas << pBC_meas, vBC_meas, w_meas;
+//        CaptureBasePtr cap = CaptureBase::emplace<CaptureBase>(KF0_, "CaptureInertialKinematics", t_, nullptr);
+//        auto feat = FeatureBase::emplace<FeatureInertialKinematics>(cap, meas, Qkin, Iq, Lq, FeatureBase::UncertaintyType::UNCERTAINTY_IS_COVARIANCE);
+//        feat_in_ = static_pointer_cast<FeatureInertialKinematics>(feat); // !! auto feat_in_ creates no compilation error but a segfault
+//        // auto fac = FactorBase::emplace<FactorInertialKinematics>(feat_in_, feat_in_, sbbimu_, nullptr, false);
+//    }
+//
+//    virtual void TearDown(){}
+//};
 
 
 
@@ -440,120 +385,66 @@ class FactorInertialKinematics_IMU : public testing::Test
 
 
 TEST_F(FactorInertialKinematics_1KF_Meas0_bpfix,ZeroMvt)
-{   
-    Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero(); 
-    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero(); 
-
-    Vector3d errP = Vector3d::Random();
-    Vector3d errV = Vector3d::Random();
-    Vector3d errC = Vector3d::Random();
-    Vector3d errD = Vector3d::Random();
-    Vector3d errL = Vector3d::Random();
-    // Vector3d errBp = Vector3d::Random();
-
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-
-    // throw std::invalid_argument( "received negative value" );
-    KF0_->getP()->setState(KF0_->getP()->getState() + errP); 
-    KF0_->getO()->setState(KF0_->getO()->getState().reverse()); 
-    KF0_->getV()->setState(KF0_->getV()->getState() + errV); 
-    KF0_->getStateBlock("C")->setState(KF0_->getStateBlock("C")->getState() + errC); 
-    KF0_->getStateBlock("D")->setState(KF0_->getStateBlock("D")->getState() + errD); 
-    KF0_->getStateBlock("L")->setState(KF0_->getStateBlock("L")->getState() + errL); 
-    // KF0_->getStateBlock("B")->setState(KF0_->getStateBlock("B")->getState() + errBp); 
-    
-    problem_->print(3,0,1,1);
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    std::cout << report << std::endl;
-    problem_->print(3,0,1,1);
-
-    ASSERT_MATRIX_APPROX(KF0_->getP()->getState(), x_origin_.head(3), 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    // ASSERT_MATRIX_APPROX(KF0_->getStateBlock("B")->getState(), bp_exp, 1e-5);
+{
+    Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero();
+    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero();
+    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero();
+    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero();
+
+    KF0_->perturb();
+    string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    ASSERT_MATRIX_APPROX(KF0_->getP()->getState(), x_origin_.at('P'), 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
 }
 
 
 TEST_F(FactorInertialKinematics_1KF_1p_bpfix,ZeroMvt)
-{   
-    Eigen::Vector3d c_exp; c_exp << 1,0,0; 
-    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero(); 
-    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero(); 
-
-    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero(); 
-    Vector3d errC = Vector3d::Random();
-    Vector3d errD = Vector3d::Random();
-    Vector3d errL = Vector3d::Random();
-    // Vector3d errBp = Vector3d::Random();
-
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-
-    KF0_->getStateBlock("C")->setState(KF0_->getStateBlock("C")->getState() + errC); 
-    KF0_->getStateBlock("D")->setState(KF0_->getStateBlock("D")->getState() + errD); 
-    KF0_->getStateBlock("L")->setState(KF0_->getStateBlock("L")->getState() + errL); 
-    // KF0_->getStateBlock("B")->setState(KF0_->getStateBlock("B")->getState() + errBp); 
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    // ASSERT_MATRIX_APPROX(KF0_->getStateBlock("B")->getState(), bp_exp, 1e-5);
+{
+    Eigen::Vector3d c_exp; c_exp << 1,0,0;
+    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero();
+    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero();
+    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero();
+
+    KF0_->perturb();
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
 }
 
 TEST_F(FactorInertialKinematics_1KF_m1p_pfix,ZeroMvt)
-{   
-    // Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d bp_exp; bp_exp << 1,0,0; 
-
-    // Vector3d errC = Vector3d::Random();
-    Vector3d errD = Vector3d::Random();
-    Vector3d errL = Vector3d::Random();
-    Vector3d errBp = Vector3d::Random();
-
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-
-    // KF0_->getStateBlock("C")->setState(KF0_->getStateBlock("C")->getState() + errC); 
-    KF0_->getStateBlock("D")->setState(KF0_->getStateBlock("D")->getState() + errD); 
-    KF0_->getStateBlock("L")->setState(KF0_->getStateBlock("L")->getState() + errL); 
-    KF0_->getStateBlock("B")->setState(KF0_->getStateBlock("B")->getState() + errBp); 
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-
-    // ASSERT_MATRIX_APPROX(KF0_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("B")->getState(), bp_exp, 1e-5);
+{
+    // Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero();
+    Eigen::Vector3d cd_exp = Eigen::Vector3d::Zero();
+    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero();
+    Eigen::Vector3d bp_exp; bp_exp << 1,0,0;
+
+    KF0_->perturb();
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    // ASSERT_MATRIX_APPROX(KF0_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(CIK0_->getStateBlock('I')->getState(), bp_exp, 1e-5);
 }
 
 TEST_F(FactorInertialKinematics_1KF_1v_bfix,ZeroMvt)
-{   
-    Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero(); 
-    Eigen::Vector3d cd_exp; cd_exp << 1,0,0; 
-    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero(); 
-    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero(); 
-
-    Vector3d errC = Vector3d::Random();
-    Vector3d errD = Vector3d::Random();
-    Vector3d errL = Vector3d::Random();
-    // Vector3d errBp = Vector3d::Random();
-
-    string report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    KF0_->getStateBlock("C")->setState(KF0_->getStateBlock("C")->getState() + errC); 
-    KF0_->getStateBlock("D")->setState(KF0_->getStateBlock("D")->getState() + errD); 
-    KF0_->getStateBlock("L")->setState(KF0_->getStateBlock("L")->getState() + errL); 
-    // KF0_->getStateBlock("B")->setState(KF0_->getStateBlock("B")->getState() + errBp); 
-    report = ceres_manager_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
-    // problem_->print(3,0,1,1);
-
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("C")->getState(), c_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("D")->getState(), cd_exp, 1e-5);
-    ASSERT_MATRIX_APPROX(KF0_->getStateBlock("L")->getState(), Lc_exp, 1e-5);
-    // ASSERT_MATRIX_APPROX(KF0_->getStateBlock("B")->getState(), bp_exp, 1e-5);
+{
+    Eigen::Vector3d c_exp  = Eigen::Vector3d::Zero();
+    Eigen::Vector3d cd_exp; cd_exp << 1,0,0;
+    Eigen::Vector3d Lc_exp = Eigen::Vector3d::Zero();
+    // Eigen::Vector3d bp_exp = Eigen::Vector3d::Zero();
+
+    KF0_->perturb();
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('C')->getState(), c_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('D')->getState(), cd_exp, 1e-5);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('L')->getState(), Lc_exp, 1e-5);
 }
 
 
diff --git a/test/gtest_feature_inertial_kinematics.cpp b/test/gtest_feature_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41d39701af5ac0ed184a8324ac88ba2faf51c6c1
--- /dev/null
+++ b/test/gtest_feature_inertial_kinematics.cpp
@@ -0,0 +1,88 @@
+//--------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--------
+/**
+ * \file gtest_feature_inertial_kinematics.cpp
+ *
+ *  Created on: March 9, 2020
+ *      \author: mfourmy
+ */
+
+
+#include "bodydynamics/feature/feature_inertial_kinematics.h"
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+#include <Eigen/Dense>
+
+using namespace wolf;
+using namespace Eigen;
+
+
+TEST(FeatureInertialKinematics, jacobian_covariance_and_constructor)
+{   
+    Vector3d pBC_meas = Vector3d::Random(); 
+    Vector3d vBC_meas = Vector3d::Random();
+    Vector3d w_meas =   Vector3d::Random(); 
+    
+    Eigen::Matrix3d Qp = pow(1e-2, 2)*Eigen::Matrix3d::Identity();
+    Eigen::Matrix3d Qv = pow(2e-2, 2)*Eigen::Matrix3d::Identity();
+    Eigen::Matrix3d Qw = pow(3e-2, 2)*Eigen::Matrix3d::Identity();
+    
+    // momentum parameters
+    Matrix3d Iq = Matrix3d::Identity(); 
+    Vector3d Lq = Vector3d::Random(); 
+    Vector9d meas_ikin; meas_ikin << pBC_meas, vBC_meas, w_meas;
+    
+    // direct computation of error covariance (analytical formula of first order propagation)
+    Eigen::Matrix9d Q_ikin_err = computeIKinCov(Qp, Qv, Qw, pBC_meas, w_meas, Iq);
+
+    // covariance from the first order propagation (using jacobian analytical formula)
+    Matrix9d J_err_noise = computeIKinJac(pBC_meas, w_meas, Iq);
+    Eigen::Matrix9d Q_meas = Eigen::Matrix9d::Zero(); 
+    Q_meas.block<3,3>(0,0) = Qp;
+    Q_meas.block<3,3>(3,3) = Qv;
+    Q_meas.block<3,3>(6,6) = Qw;
+    Eigen::Matrix9d Q_ikin_err_from_Jc = J_err_noise * Q_meas * J_err_noise.transpose();
+    FeatureInertialKinematicsPtr feat_ptr = std::make_shared<FeatureInertialKinematics>(meas_ikin, Q_ikin_err, Iq, Lq);
+
+    // Test if analytic covariance matches with the explicit jacobian propagation formula
+    ASSERT_MATRIX_APPROX(Q_ikin_err, Q_ikin_err_from_Jc, 1e-3);
+
+    // Simple constructor tests
+    ASSERT_MATRIX_APPROX(Iq, feat_ptr->getBIq(), 1e-3);
+    ASSERT_MATRIX_APPROX(Lq, feat_ptr->getBLcm(), 1e-3);
+    Iq = 2*Matrix3d::Identity(); 
+    Lq = Vector3d::Random(); 
+    feat_ptr->setBIq(Iq);
+    feat_ptr->setBLcm(Lq);
+    ASSERT_MATRIX_APPROX(Iq, feat_ptr->getBIq(), 1e-3);
+    ASSERT_MATRIX_APPROX(Lq, feat_ptr->getBLcm(), 1e-3);
+
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/test/gtest_force_torque_delta_tools.cpp b/test/gtest_force_torque_delta_tools.cpp
index bb7397182a2d9779a46feee1ae53f5f5633d777b..e27553d54600d3538c6b87671ecfa276ace51182 100644
--- a/test/gtest_force_torque_delta_tools.cpp
+++ b/test/gtest_force_torque_delta_tools.cpp
@@ -1,3 +1,24 @@
+//--------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--------
 /*
  * gtest_force_torque_delta_tools.cpp
  *
@@ -12,7 +33,7 @@ using namespace Eigen;
 using namespace wolf;
 using namespace bodydynamics;
 
-TEST(IMU_tools, identity)
+TEST(FT_tools, identity)
 {
     VectorXd id_true;
     id_true.setZero(13);
@@ -22,7 +43,7 @@ TEST(IMU_tools, identity)
     ASSERT_MATRIX_APPROX(id, id_true, 1e-10);
 }
 
-TEST(IMU_tools, identity_split)
+TEST(FT_tools, identity_split)
 {
     VectorXd c(3), cd(3), Lc(3), qv(4);
     Quaterniond q;
@@ -40,7 +61,7 @@ TEST(IMU_tools, identity_split)
     ASSERT_MATRIX_APPROX(qv, (Vector4d()<<0,0,0,1).finished(), 1e-10);
 }
 
-TEST(IMU_tools, inverse)
+TEST(FT_tools, inverse)
 {
     VectorXd d(13), id(13), iid(13), iiid(13), I(13);
     Vector4d qv;
@@ -62,240 +83,280 @@ TEST(IMU_tools, inverse)
     ASSERT_MATRIX_APPROX(id, iiid, 1e-10);
 }
 
-// TEST(IMU_tools, compose_between)
-// {
-//     VectorXd dx1(10), dx2(10), dx3(10);
-//     Matrix<double, 10, 1> d1, d2, d3;
-//     Vector4d qv;
-//     double dt = 0.1;
-
-//     qv = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     dx1 << 0, 1, 2, qv, 7, 8, 9;
-//     d1 = dx1;
-//     qv = (Vector4d() << 6, 5, 4, 3).finished().normalized();
-//     dx2 << 9, 8, 7, qv, 2, 1, 0;
-//     d2 = dx2;
-
-//     // combinations of templates for sum()
-//     compose(dx1, dx2, dt, dx3);
-//     compose(d1, d2, dt, d3);
-//     ASSERT_MATRIX_APPROX(d3, dx3, 1e-10);
-
-//     compose(d1, dx2, dt, dx3);
-//     d3 = compose(dx1, d2, dt);
-//     ASSERT_MATRIX_APPROX(d3, dx3, 1e-10);
-
-//     // minus(d1, d3) should recover delta_2
-//     VectorXd diffx(10);
-//     Matrix<double,10,1> diff;
-//     between(d1, d3, dt, diff);
-//     ASSERT_MATRIX_APPROX(diff, d2, 1e-10);
-
-//     // minus(d3, d1, -dt) should recover inverse(d2, dt)
-//     diff = between(d3, d1, -dt);
-//     ASSERT_MATRIX_APPROX(diff, inverse(d2, dt), 1e-10);
-// }
-
-// TEST(IMU_tools, compose_between_with_state)
-// {
-//     VectorXd x(10), d(10), x2(10), x3(10), d2(10), d3(10);
-//     Vector4d qv;
-//     double dt = 0.1;
-
-//     qv = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     x << 0, 1, 2, qv, 7, 8, 9;
-//     qv = (Vector4d() << 6, 5, 4, 3).finished().normalized();
-//     d << 9, 8, 7, qv, 2, 1, 0;
-
-//     composeOverState(x, d, dt, x2);
-//     x3 = composeOverState(x, d, dt);
-//     ASSERT_MATRIX_APPROX(x3, x2, 1e-10);
-
-//     // betweenStates(x, x2) should recover d
-//     betweenStates(x, x2, dt, d2);
-//     d3 = betweenStates(x, x2, dt);
-//     ASSERT_MATRIX_APPROX(d2, d, 1e-10);
-//     ASSERT_MATRIX_APPROX(d3, d, 1e-10);
-//     ASSERT_MATRIX_APPROX(betweenStates(x, x2, dt), d, 1e-10);
-
-//     // x + (x2 - x) = x2
-//     ASSERT_MATRIX_APPROX(composeOverState(x, betweenStates(x, x2, dt), dt), x2, 1e-10);
-
-//     // (x + d) - x = d
-//     ASSERT_MATRIX_APPROX(betweenStates(x, composeOverState(x, d, dt), dt), d, 1e-10);
-// }
-
-// TEST(IMU_tools, lift_retract)
-// {
-//     VectorXd d_min(9); d_min << 0, 1, 2, .3, .4, .5, 6, 7, 8; // use angles in the ball theta < pi
-
-//     // transform to delta
-//     VectorXd delta = exp_IMU(d_min);
-
-//     // expected delta
-//     Vector3d dp = d_min.head(3);
-//     Quaterniond dq = v2q(d_min.segment(3,3));
-//     Vector3d dv = d_min.tail(3);
-//     VectorXd delta_true(10); delta_true << dp, dq.coeffs(), dv;
-//     ASSERT_MATRIX_APPROX(delta, delta_true, 1e-10);
-
-//     // transform to a new tangent -- should be the original tangent
-//     VectorXd d_from_delta = log_IMU(delta);
-//     ASSERT_MATRIX_APPROX(d_from_delta, d_min, 1e-10);
-
-//     // transform to a new delta -- should be the previous delta
-//     VectorXd delta_from_d = exp_IMU(d_from_delta);
-//     ASSERT_MATRIX_APPROX(delta_from_d, delta, 1e-10);
-// }
+TEST(FT_tools, compose_between)
+{
+    VectorXd dx1(13), dx2(13), dx3(13);
+    Matrix<double, 13, 1> d1, d2, d3;
+    double dt = 0.1;
 
-// TEST(IMU_tools, plus)
-// {
-//     VectorXd d1(10), d2(10), d3(10);
-//     VectorXd err(9);
-//     Vector4d qv = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     d1 << 0, 1, 2, qv, 7, 8, 9;
-//     err << 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09;
-
-//     d3.head(3) = d1.head(3) + err.head(3);
-//     d3.segment(3,4) = (Quaterniond(qv.data()) * exp_q(err.segment(3,3))).coeffs();
-//     d3.tail(3) = d1.tail(3) + err.tail(3);
-
-//     plus(d1, err, d2);
-//     ASSERT_MATRIX_APPROX(diff(d3, d2), VectorXd::Zero(9), 1e-10);
-// }
+    dx1 << Vector9d::Random(), Vector4d::Random().normalized();
+    d1 = dx1;
+    dx2 << Vector9d::Random(), Vector4d::Random().normalized();
+    d2 = dx2;
+
+    // combinations of templates for sum()
+    compose(dx1, dx2, dt, dx3);
+    compose(d1, d2, dt, d3);
+    ASSERT_MATRIX_APPROX(d3, dx3, 1e-10);
+
+    compose(d1, dx2, dt, dx3);
+    d3 = compose(dx1, d2, dt);
+    ASSERT_MATRIX_APPROX(d3, dx3, 1e-10);
+
+    // // minus(d1, d3) should recover delta_2
+    VectorXd diffx(13);
+    Matrix<double,13,1> diff;
+    between(d1, d3, dt, diff);
+    ASSERT_MATRIX_APPROX(diff, d2, 1e-10);
+
+    // minus(d3, d1, -dt) should recover inverse(d2, dt)
+    diff = between(d3, d1, -dt);
+    ASSERT_MATRIX_APPROX(diff, inverse(d2, dt), 1e-10);
+}
 
-// TEST(IMU_tools, diff)
-// {
-//     VectorXd d1(10), d2(10);
-//     Vector4d qv = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     d1 << 0, 1, 2, qv, 7, 8, 9;
-//     d2 = d1;
+TEST(FT_tools, compose_between_with_state)
+{
+    VectorXd x(13), d(13), x2(13), x3(13), d2(13), d3(13);
+    double dt = 0.1;
 
-//     VectorXd err(9);
-//     diff(d1, d2, err);
-//     ASSERT_MATRIX_APPROX(err, VectorXd::Zero(9), 1e-10);
-//     ASSERT_MATRIX_APPROX(diff(d1, d2), VectorXd::Zero(9), 1e-10);
+    x << Vector9d::Random(), Vector4d::Random().normalized();
+    d << Vector9d::Random(), Vector4d::Random().normalized();
 
-//     VectorXd d3(10);
-//     d3.setRandom(); d3.segment(3,4).normalize();
-//     err.head(3) = d3.head(3) - d1.head(3);
-//     err.segment(3,3) = log_q(Quaterniond(d1.data()+3).conjugate()*Quaterniond(d3.data()+3));
-//     err.tail(3) = d3.tail(3) - d1.tail(3);
+    composeOverState(x, d, dt, x2);
+    x3 = composeOverState(x, d, dt);
+    ASSERT_MATRIX_APPROX(x3, x2, 1e-10);
 
-//     ASSERT_MATRIX_APPROX(err, diff(d1, d3), 1e-10);
+    // betweenStates(x, x2) should recover d
+    betweenStates(x, x2, dt, d2);
+    d3 = betweenStates(x, x2, dt);
+    ASSERT_MATRIX_APPROX(d2, d, 1e-10);
+    ASSERT_MATRIX_APPROX(d3, d, 1e-10);
+    ASSERT_MATRIX_APPROX(betweenStates(x, x2, dt), d, 1e-10);
 
-// }
+    // x + (x2 - x) = x2
+    ASSERT_MATRIX_APPROX(composeOverState(x, betweenStates(x, x2, dt), dt), x2, 1e-10);
 
-// TEST(IMU_tools, compose_jacobians)
-// {
-//     VectorXd d1(10), d2(10), d3(10), d1_pert(10), d2_pert(10), d3_pert(10); // deltas
-//     VectorXd t1(9), t2(9); // tangents
-//     Matrix<double, 9, 9> J1_a, J2_a, J1_n, J2_n;
-//     Vector4d qv1, qv2;
-//     double dt = 0.1;
-//     double dx = 1e-6;
-//     qv1 = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     d1 << 0, 1, 2, qv1, 7, 8, 9;
-//     qv2 = (Vector4d() << 1, 2, 3, 4).finished().normalized();
-//     d2 << 9, 8, 7, qv2, 2, 1, 0;
-
-//     // analytical jacobians
-//     compose(d1, d2, dt, d3, J1_a, J2_a);
+    // (x + d) - x = d
+    ASSERT_MATRIX_APPROX(betweenStates(x, composeOverState(x, d, dt), dt), d, 1e-10);
+}
 
-//     // numerical jacobians
-//     for (unsigned int i = 0; i<9; i++)
-//     {
-//         t1      . setZero();
-//         t1(i)   = dx;
-
-//         // Jac wrt first delta
-//         d1_pert = plus(d1, t1);                 //     (d1 + t1)
-//         d3_pert = compose(d1_pert, d2, dt);     //     (d1 + t1) + d2
-//         t2      = diff(d3, d3_pert);            //   { (d2 + t1) + d2 } - { d1 + d2 }
-//         J1_n.col(i) = t2/dx;                    // [ { (d2 + t1) + d2 } - { d1 + d2 } ] / dx
-
-//         // Jac wrt second delta
-//         d2_pert = plus(d2, t1);                 //          (d2 + t1)
-//         d3_pert = compose(d1, d2_pert, dt);     //     d1 + (d2 + t1)
-//         t2      = diff(d3, d3_pert);            //   { d1 + (d2 + t1) } - { d1 + d2 }
-//         J2_n.col(i) = t2/dx;                    // [ { d1 + (d2 + t1) } - { d1 + d2 } ] / dx
-//     }
+TEST(FT_tools, lift_retract)
+{
+    VectorXd d_min(12); d_min << Vector9d::Random(), .1*Vector3d::Random(); // use angles in the ball theta < pi
+
+    // transform to delta
+    VectorXd delta = exp_FT(d_min);
+
+    // expected delta
+    Vector3d dc = d_min.head(3);
+    Vector3d dcd = d_min.segment<3>(3);
+    Vector3d dLc = d_min.segment<3>(6);
+    Quaterniond dq = v2q(d_min.segment<3>(9));
+    VectorXd delta_true(13); delta_true << dc, dcd, dLc, dq.coeffs();
+    ASSERT_MATRIX_APPROX(delta, delta_true, 1e-10);
+
+    // transform to a new tangent -- should be the original tangent
+    VectorXd d_from_delta = log_FT(delta);
+    ASSERT_MATRIX_APPROX(d_from_delta, d_min, 1e-10);
+
+    // transform to a new delta -- should be the previous delta
+    VectorXd delta_from_d = exp_FT(d_from_delta);
+    ASSERT_MATRIX_APPROX(delta_from_d, delta, 1e-10);
+}
 
-//     // check that numerical and analytical match
-//     ASSERT_MATRIX_APPROX(J1_a, J1_n, 1e-4);
-//     ASSERT_MATRIX_APPROX(J2_a, J2_n, 1e-4);
-// }
+TEST(FT_tools, plus)
+{
+    VectorXd d1(13), d2(13), d3(13);
+    VectorXd err(12);
+    Vector4d qv = Vector4d::Random().normalized();
+    d1 <<  Vector9d::Random(), qv;
+    err << Matrix<double, 12, 1>::Random();
+
+    d3.head<3>() = d1.head<3>() + err.head<3>();
+    d3.segment<3>(3) = d1.segment<3>(3) + err.segment<3>(3);
+    d3.segment<3>(6) = d1.segment<3>(6) + err.segment<3>(6);
+    d3.tail<4>() = (Quaterniond(qv.data()) * exp_q(err.tail<3>())).coeffs();
+
+    plus(d1, err, d2);
+    ASSERT_MATRIX_APPROX(diff(d3, d2), VectorXd::Zero(12), 1e-10);
+}
 
-// TEST(IMU_tools, diff_jacobians)
-// {
-//     VectorXd d1(10), d2(10), d3(9), d1_pert(10), d2_pert(10), d3_pert(9); // deltas
-//     VectorXd t1(9), t2(9); // tangents
-//     Matrix<double, 9, 9> J1_a, J2_a, J1_n, J2_n;
-//     Vector4d qv1, qv2;
-//     double dx = 1e-6;
-//     qv1 = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     d1 << 0, 1, 2, qv1, 7, 8, 9;
-//     qv2 = (Vector4d() << 1, 2, 3, 4).finished().normalized();
-//     d2 << 9, 8, 7, qv2, 2, 1, 0;
-
-//     // analytical jacobians
-//     diff(d1, d2, d3, J1_a, J2_a);
+TEST(FT_tools, diff)
+{
+    VectorXd d1(13), d2(13);
+    VectorXd err(12);
+    Vector4d qv = Vector4d::Random().normalized();
+    d1 <<  Vector9d::Random(), qv;
+    d2 = d1;
+
+    diff(d1, d2, err);
+    ASSERT_MATRIX_APPROX(err, VectorXd::Zero(12), 1e-10);
+    ASSERT_MATRIX_APPROX(diff(d1, d2), VectorXd::Zero(12), 1e-10);
+
+    VectorXd d3(13);
+    d3.setRandom(); d3.tail<4>().normalize();
+    err.head<3>() = d3.head<3>() - d1.head<3>();
+    err.segment<3>(3) = d3.segment<3>(3) - d1.segment<3>(3);
+    err.segment<3>(6) = d3.segment<3>(6) - d1.segment<3>(6);    
+    err.tail<3>() = log_q(Quaterniond(d1.data()+9).conjugate()*Quaterniond(d3.data()+9));
+
+    ASSERT_MATRIX_APPROX(err, diff(d1, d3), 1e-10);
+}
 
-//     // numerical jacobians
-//     for (unsigned int i = 0; i<9; i++)
-//     {
-//         t1      . setZero();
-//         t1(i)   = dx;
-
-//         // Jac wrt first delta
-//         d1_pert = plus(d1, t1);                 //          (d1 + t1)
-//         d3_pert = diff(d1_pert, d2);            //     d2 - (d1 + t1)
-//         t2      = d3_pert - d3;                 //   { d2 - (d1 + t1) } - { d2 - d1 }
-//         J1_n.col(i) = t2/dx;                    // [ { d2 - (d1 + t1) } - { d2 - d1 } ] / dx
-
-//         // Jac wrt second delta
-//         d2_pert = plus(d2, t1);                 //     (d2 + t1)
-//         d3_pert = diff(d1, d2_pert);            //     (d2 + t1) - d1
-//         t2      = d3_pert - d3;                 //   { (d2 + t1) - d1 } - { d2 - d1 }
-//         J2_n.col(i) = t2/dx;                    // [ { (d2 + t1) - d1 } - { d2 - d1 } ] / dx
-//     }
+TEST(FT_tools, compose_jacobians)
+{
+    VectorXd d1(13), d2(13), d3(13), d1_pert(13), d2_pert(13), d3_pert(13); // deltas
+    VectorXd t1(12), t2(12); // tangent elements
+    Matrix<double, 12, 12> J1_a, J2_a, J1_n, J2_n;
+    Vector4d qv1, qv2;
+    double dt = 0.1;
+    double dx = 1e-6;
+    qv1 = Vector4d::Random().normalized();
+    d1 << Vector9d::Random(), qv1;
+    qv2 = Vector4d::Random().normalized();
+    d2 << Vector9d::Random(), qv2;
+
+    // analytical jacobians
+    compose(d1, d2, dt, d3, J1_a, J2_a);
+
+    // numerical jacobians
+    for (unsigned int i = 0; i<12; i++)
+    {
+        t1      . setZero();
+        t1(i)   = dx;
+
+        // Jac wrt first delta
+        d1_pert = plus(d1, t1);                 //     (d1 + t1)
+        d3_pert = compose(d1_pert, d2, dt);     //     (d1 + t1) + d2
+        t2      = diff(d3, d3_pert);            //   { (d2 + t1) + d2 } - { d1 + d2 }
+        J1_n.col(i) = t2/dx;                    // [ { (d2 + t1) + d2 } - { d1 + d2 } ] / dx
+
+        // Jac wrt second delta
+        d2_pert = plus(d2, t1);                 //          (d2 + t1)
+        d3_pert = compose(d1, d2_pert, dt);     //     d1 + (d2 + t1)
+        t2      = diff(d3, d3_pert);            //   { d1 + (d2 + t1) } - { d1 + d2 }
+        J2_n.col(i) = t2/dx;                    // [ { d1 + (d2 + t1) } - { d1 + d2 } ] / dx
+    }
+
+    // check that numerical and analytical match
+    ASSERT_MATRIX_APPROX(J1_a, J1_n, 1e-4);
+    ASSERT_MATRIX_APPROX(J2_a, J2_n, 1e-4);
+}
 
-//     // check that numerical and analytical match
-//     ASSERT_MATRIX_APPROX(J1_a, J1_n, 1e-4);
-//     ASSERT_MATRIX_APPROX(J2_a, J2_n, 1e-4);
-// }
+TEST(FT_tools, diff_jacobians)
+{
+    VectorXd d1(13), d2(13), diff3(12), d1_pert(13), d2_pert(13), diff3_pert(12); // deltas
+    VectorXd t1(12), t2(12); // tangents
+    Matrix<double, 12, 12> J1_a, J2_a, J1_n, J2_n;
+    Vector4d qv1, qv2;
+    double dx = 1e-6;
+    qv1 = Vector4d::Random().normalized();
+    d1 << Vector9d::Random(), qv1;
+    qv2 = Vector4d::Random().normalized();
+    d2 << Vector9d::Random(), qv2;
+
+    // analytical jacobians
+    diff(d1, d2, diff3, J1_a, J2_a);
+
+    // numerical jacobians
+    for (unsigned int i = 0; i<12; i++)
+    {
+        t1      . setZero();
+        t1(i)   = dx;
+
+        // Jac wrt first delta
+        d1_pert = plus(d1, t1);                 //          (d1 + t1)
+        diff3_pert = diff(d1_pert, d2);            //     d2 - (d1 + t1)
+        t2      = diff3_pert - diff3;                 //   { d2 - (d1 + t1) } - { d2 - d1 }
+        J1_n.col(i) = t2/dx;                    // [ { d2 - (d1 + t1) } - { d2 - d1 } ] / dx
+
+        // Jac wrt second delta
+        d2_pert = plus(d2, t1);                 //     (d2 + t1)
+        diff3_pert = diff(d1, d2_pert);            //     (d2 + t1) - d1
+        t2      = diff3_pert - diff3;                 //   { (d2 + t1) - d1 } - { d2 - d1 }
+        J2_n.col(i) = t2/dx;                    // [ { (d2 + t1) - d1 } - { d2 - d1 } ] / dx
+    }
+
+    // check that numerical and analytical match
+    ASSERT_MATRIX_APPROX(J1_a, J1_n, 1e-4);
+    ASSERT_MATRIX_APPROX(J2_a, J2_n, 1e-4);
+}
 
-// TEST(IMU_tools, body2delta_jacobians)
-// {
-//     VectorXd delta(10), delta_pert(10); // deltas
-//     VectorXd body(6), pert(6);
-//     VectorXd tang(9); // tangents
-//     Matrix<double, 9, 6> J_a, J_n; // analytic and numerical jacs
-//     Vector4d qv;;
-//     double dt = 0.1;
-//     double dx = 1e-6;
-//     qv = (Vector4d() << 3, 4, 5, 6).finished().normalized();
-//     delta << 0, 1, 2,   qv,   7, 8, 9;
-//     body << 1, 2, 3,   3, 2, 1;
+TEST(FT_tools, body2delta_jacobians)
+{
+    VectorXd delta(13), delta_pert(13); // deltas
+    VectorXd body(32), pert(32), body_pert(32);
+    VectorXd tang(12); // tangents
+    Matrix<double, 12, 30> J_a, J_n; // analytic and numerical jacs
+    double dt = 0.1;
+    double mass = 1;
+    double dx = 1e-6;
+    Vector4d qv = Vector4d::Random().normalized();
+    delta << Vector9d::Random(), qv;
+    Vector4d bql1 = Vector4d::Random().normalized();
+    Vector4d bql2 = Vector4d::Random().normalized();
+    body << Matrix<double, 24, 1>::Random(), bql1, bql2;  //
+
+    // analytical jacobians
+    body2delta(body, dt, mass, 2, 6, delta, J_a);
+
+    // numerical jacobians
+    J_n.setZero();
+    for (unsigned int i = 0; i<18; i++)
+    {
+        pert      . setZero();
+        pert(i)   = dx;
+
+        // Jac wrt first delta
+        body_pert  = body + pert;
+        delta_pert = body2delta(body_pert, dt, mass, 2, 6);   //   delta(body + pert)
+        tang       = diff(delta, delta_pert);           //   delta(body + pert) -- delta(body)
+        J_n.col(i) = tang/dx;                          //  ( delta(body + pert) -- delta(body) ) / dx
+    }
+
+    // check that numerical and analytical match
+    // ASSERT_MATRIX_APPROX(J_a.block(0,0,12,18), J_n.block(0,0,12,18), 1e-4);  // only compare the implemented parts -> kinematics uncertainty not considered here
+    ASSERT_MATRIX_APPROX(J_a, J_n, 1e-4);
+}
 
-//     // analytical jacobians
-//     body2delta(body, dt, delta, J_a);
 
-//     // numerical jacobians
-//     for (unsigned int i = 0; i<6; i++)
-//     {
-//         pert      . setZero();
-//         pert(i)   = dx;
+TEST(FT_tools, debiasData_jacobians)
+{
+    VectorXd data(32), bias(6), bias_pert(6), pert(6), body(32), body_pert(32), tang(32);
+    Matrix<double, 30, 6> J_a, J_n; // analytic and numerical jacs
+    double dx = 1e-6;
+    Vector4d bql1 = Vector4d::Random().normalized();
+    Vector4d bql2 = Vector4d::Random().normalized();
+    data << Matrix<double, 24, 1>::Random(), bql1, bql2;  //
+    bias << Vector6d::Random();
+
+    // analytical jacobians
+    debiasData(data, bias, 2, 6, body, J_a);
+
+    // numerical jacobian
+    J_n.setZero();
+    for (unsigned int i = 0; i<6; i++)
+    {
+        pert      . setZero();
+        pert(i)   = dx;
+
+        // Jac wrt first delta
+        bias_pert  = bias + pert;
+        debiasData(data, bias_pert, 2, 6, body_pert);
+        // The next line is actually a cheating: 
+        // we know that the quaternions at the end of the body vector are not influenced by the 
+        // bias perturbation so we just take the euclidean difference of the 2 vectors and remove the last 2 elements
+        // to get the tangent space vector
+        tang       = (body_pert - body).segment<30>(0);
+
+        J_n.col(i) = tang/dx;
+    }
+
+    // check that numerical and analytical match
+    ASSERT_MATRIX_APPROX(J_a, J_n, 1e-4);
+}
 
-//         // Jac wrt first delta
-//         delta_pert = body2delta(body + pert, dt);   //     delta(body + pert)
-//         tang       = diff(delta, delta_pert);       //   delta(body + pert) -- delta(body)
-//         J_n.col(i) = tang/dx;                       // ( delta(body + pert) -- delta(body) ) / dx
-//     }
 
-//     // check that numerical and analytical match
-//     ASSERT_MATRIX_APPROX(J_a, J_n, 1e-4);
-// }
 
 // TEST(motion2data, zero)
 // {
@@ -395,7 +456,7 @@ TEST(IMU_tools, inverse)
 //  *   N: number of steps
 //  *   q0: initial orientaiton
 //  *   motion: [ax, ay, az, wx, wy, wz] as the true magnitudes in body brame
-//  *   bias_real: the real bias of the IMU
+//  *   bias_real: the real bias of the Imu
 //  *   bias_preint: the bias used for Delta pre-integration
 //  * Output:
 //  *   return: the preintegrated delta
@@ -404,8 +465,8 @@ TEST(IMU_tools, inverse)
 // {
 //     VectorXd data(6);
 //     VectorXd body(6);
-//     VectorXd delta(10);
-//     VectorXd Delta(10), Delta_plus(10);
+//     VectorXd delta(13);
+//     VectorXd Delta(13), Delta_plus(13);
 //     Delta << 0,0,0, 0,0,0,1, 0,0,0;
 //     Quaterniond q(q0);
 //     for (int n = 0; n<N; n++)
@@ -425,7 +486,7 @@ TEST(IMU_tools, inverse)
 //  *   N: number of steps
 //  *   q0: initial orientaiton
 //  *   motion: [ax, ay, az, wx, wy, wz] as the true magnitudes in body brame
-//  *   bias_real: the real bias of the IMU
+//  *   bias_real: the real bias of the Imu
 //  *   bias_preint: the bias used for Delta pre-integration
 //  * Output:
 //  *   J_D_b: the Jacobian of the preintegrated delta wrt the bias
@@ -435,10 +496,10 @@ TEST(IMU_tools, inverse)
 // {
 //     VectorXd data(6);
 //     VectorXd body(6);
-//     VectorXd delta(10);
+//     VectorXd delta(13);
 //     Matrix<double, 9, 6> J_d_d, J_d_b;
 //     Matrix<double, 9, 9> J_D_D, J_D_d;
-//     VectorXd Delta(10), Delta_plus(10);
+//     VectorXd Delta(13), Delta_plus(13);
 //     Quaterniond q;
 
 //     Delta << 0,0,0, 0,0,0,1, 0,0,0;
@@ -450,12 +511,12 @@ TEST(IMU_tools, inverse)
 //         data = motion2data(motion, q, bias_real);
 //         q    = q*exp_q(motion.tail(3)*dt);
 //         // Motion::integrateOneStep()
-//         {   // IMU::computeCurrentDelta
+//         {   // Imu::computeCurrentDelta
 //             body  = data - bias_preint;
 //             body2delta(body, dt, delta, J_d_d);
 //             J_d_b = - J_d_d;
 //         }
-//         {   // IMU::deltaPlusDelta
+//         {   // Imu::deltaPlusDelta
 //             compose(Delta, delta, dt, Delta_plus, J_D_D, J_D_d);
 //         }
 //         // Motion:: jac calib
@@ -466,9 +527,9 @@ TEST(IMU_tools, inverse)
 //     return Delta;
 // }
 
-// TEST(IMU_tools, integral_jacobian_bias)
+// TEST(FT_tools, integral_jacobian_bias)
 // {
-//     VectorXd Delta(10), Delta_pert(10); // deltas
+//     VectorXd Delta(13), Delta_pert(13); // deltas
 //     VectorXd bias_real(6), pert(6), bias_pert(6), bias_preint(6);
 //     VectorXd body(6), data(6), motion(6);
 //     VectorXd tang(9); // tangents
@@ -501,9 +562,9 @@ TEST(IMU_tools, inverse)
 //     ASSERT_MATRIX_APPROX(J_a, J_n, 1e-4);
 // }
 
-// TEST(IMU_tools, delta_correction)
+// TEST(FT_tools, delta_correction)
 // {
-//     VectorXd Delta(10), Delta_preint(10), Delta_corr; // deltas
+//     VectorXd Delta(13), Delta_preint(13), Delta_corr; // deltas
 //     VectorXd bias(6), pert(6), bias_preint(6);
 //     VectorXd body(6), motion(6);
 //     VectorXd tang(9); // tangents
@@ -538,11 +599,11 @@ TEST(IMU_tools, inverse)
 //     ASSERT_MATRIX_APPROX(diff(Delta_preint, Delta_corr), step, 1e-5);
 // }
 
-// TEST(IMU_tools, full_delta_residual)
+// TEST(FT_tools, full_delta_residual)
 // {
-//     VectorXd x1(10), x2(10);
-//     VectorXd Delta(10), Delta_preint(10), Delta_corr(10);
-//     VectorXd Delta_real(9), Delta_err(9), Delta_diff(10), Delta_exp(10);
+//     VectorXd x1(13), x2(13);
+//     VectorXd Delta(13), Delta_preint(13), Delta_corr(13);
+//     VectorXd Delta_real(9), Delta_err(9), Delta_diff(13), Delta_exp(13);
 //     VectorXd bias(6), pert(6), bias_preint(6), bias_null(6);
 //     VectorXd body(6), motion(6);
 //     VectorXd tang(9); // tangents
@@ -625,7 +686,7 @@ TEST(IMU_tools, inverse)
 int main(int argc, char **argv)
 {
   testing::InitGoogleTest(&argc, argv);
-//  ::testing::GTEST_FLAG(filter) = "IMU_tools.delta_correction";
+//  ::testing::GTEST_FLAG(filter) = "FT_tools.delta_correction";
   return RUN_ALL_TESTS();
 }
 
diff --git a/test/gtest_processor_force_torque_preint.cpp b/test/gtest_processor_force_torque_preint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3465ea6187358cffe667af6a2dd326d279e31e47
--- /dev/null
+++ b/test/gtest_processor_force_torque_preint.cpp
@@ -0,0 +1,803 @@
+//--------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--------
+
+/**
+ * \file gtest_factor_force_torque_preint.cpp
+ *
+ *  Created on: March 20, 2020
+ *      \author: Mederic Fourmy
+ */
+
+// debug
+#include <iostream>
+#include <fstream>
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+
+// #include <core/ceres_wrapper/qr_manager.h>
+
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_block_absolute.h>
+#include <core/factor/factor_relative_pose_3d.h>
+
+// IMU
+#include "imu/sensor/sensor_imu.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/sensor/sensor_force_torque.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_force_torque_preint.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_force_torque_preint.h"
+
+#include <Eigen/Eigenvalues>
+
+using namespace wolf;
+using namespace Eigen;
+using namespace std;
+
+const Vector3d ZERO3 = Vector3d::Zero();
+const Vector6d ZERO6 = Vector6d::Zero();
+const Vector3d ONES3 = Vector3d::Ones();
+
+const Vector3d BIAS_PBC_SMAL = {0.01, 0.02, 0.03};
+
+const double ACC = 1;
+const double MASS = 1;
+const double DT = 1.0;
+const Vector3d PBCX = {-0.1, 0, 0};  // X axis offset 
+const Vector3d PBCY = {0, -0.1, 0};  // Y axis offset 
+const Vector3d PBCZ = {0, 0, -0.1};  // Z axis offset
+
+
+class ProcessorForceTorquePreintImuOdom3dIkin2KF : public testing::Test
+{
+public:
+    wolf::TimeStamp t0_;
+    ProblemPtr problem_;
+    SensorImuPtr sen_imu_;
+    SolverCeresPtr solver_;
+    SensorInertialKinematicsPtr sen_ikin_;
+    SensorForceTorquePtr sen_ft_;
+    ProcessorImuPtr proc_imu_;
+    ProcessorInertialKinematicsPtr proc_inkin_;
+    ProcessorForceTorquePreintPtr proc_ftpreint_;
+    FrameBasePtr KF1_;
+
+    void SetUp() override
+    {
+        std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+        problem_ = Problem::create("POVCDL", 3);
+
+        // CERES WRAPPER
+        solver_ = std::make_shared<SolverCeres>(problem_);
+        // solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+        // solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
+        // solver_->getSolverOptions().max_num_iterations = 1e4;
+
+        //===================================================== INITIALIZATION
+        // SENSOR + PROCESSOR INERTIAL KINEMATICS
+        ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+        intr_ik->std_pbc = 0.1;
+        intr_ik->std_vbc = 0.1;
+        VectorXd extr; // default size for dynamic vector is 0-0 -> what we need
+        SensorBasePtr sen_ikin_base = problem_->installSensor("SensorInertialKinematics", "SenIK", extr, intr_ik);
+        // SensorBasePtr sen_ikin_base = problem_->installSensor("SensorInertialKinematics", "SenIK", extr,  bodydynamics_root_dir + "/demos/sensor_inertial_kinematics.yaml");  // TODO: does not work!
+        sen_ikin_ = std::static_pointer_cast<SensorInertialKinematics>(sen_ikin_base);
+
+        ParamsProcessorInertialKinematicsPtr params_ik = std::make_shared<ParamsProcessorInertialKinematics>();
+        params_ik->sensor_angvel_name = "SenImu";
+        params_ik->std_bp_drift = 1e-2;
+        ProcessorBasePtr proc_ikin_base = problem_->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", sen_ikin_, params_ik);
+        // ProcessorBasePtr proc_ikin_base = problem_->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", "SenIK", bodydynamics_root_dir + "/test/processor_inertial_kinematics.yaml");
+        proc_inkin_ = std::static_pointer_cast<ProcessorInertialKinematics>(proc_ikin_base);
+
+        // SENSOR + PROCESSOR IMU
+        SensorBasePtr sen_imu_base = problem_->installSensor("SensorImu", "SenImu", (Vector7d() << 0, 0, 0, 0, 0, 0, 1).finished(), bodydynamics_root_dir + "/test/sensor_imu.yaml");
+        sen_imu_ = std::static_pointer_cast<SensorImu>(sen_imu_base);
+        ProcessorBasePtr proc_ftp_base = problem_->installProcessor("ProcessorImu", "imu pre-integrator", "SenImu", bodydynamics_root_dir + "/test/processor_imu.yaml");
+        proc_imu_ = std::static_pointer_cast<ProcessorImu>(proc_ftp_base);
+
+        // SENSOR + PROCESSOR FT PREINT
+        ParamsSensorForceTorquePtr intr_ft = std::make_shared<ParamsSensorForceTorque>();
+        intr_ft->std_f = 0.001;
+        intr_ft->std_tau = 0.001;
+        intr_ft->mass = MASS;
+        SensorBasePtr sen_ft_base = problem_->installSensor("SensorForceTorque", "SenFT", VectorXd(0), intr_ft);
+        // SensorBasePtr sen_ft_base = problem_->installSensor("SensorForceTorque", "SenFT", VectorXd(0), bodydynamics_root_dir + "/demos/sensor_ft.yaml");
+        sen_ft_ = std::static_pointer_cast<SensorForceTorque>(sen_ft_base);
+        ParamsProcessorForceTorquePreintPtr params_sen_ft = std::make_shared<ParamsProcessorForceTorquePreint>();
+        params_sen_ft->sensor_ikin_name = "SenIK";
+        params_sen_ft->sensor_angvel_name = "SenImu";
+        params_sen_ft->nbc = 2;
+        params_sen_ft->dimc = 6;
+        params_sen_ft->time_tolerance = 0.0005;
+        params_sen_ft->unmeasured_perturbation_std = 1e-4;
+        params_sen_ft->max_time_span = 1000;
+        params_sen_ft->max_buff_length = 500;
+        params_sen_ft->dist_traveled = 20000.0;
+        params_sen_ft->angle_turned = 1000;
+        params_sen_ft->voting_active = true;
+        ProcessorBasePtr proc_ft_base = problem_->installProcessor("ProcessorForceTorquePreint", "PFTPreint", sen_ft_, params_sen_ft);
+        // ProcessorBasePtr proc_ft_base = problem_->installProcessor("ProcessorForceTorquePreint", "PFTPreint", "SenFT", bodydynamics_root_dir + "/demos/processor_ft_preint");
+        proc_ftpreint_ = std::static_pointer_cast<ProcessorForceTorquePreint>(proc_ft_base);
+    }
+
+    void TearDown() override {}
+};
+
+
+class Cst2KFZeroMotion : public ProcessorForceTorquePreintImuOdom3dIkin2KF
+{
+public:
+    FrameBasePtr KF2_;
+
+    // Unitary data
+    Vector3d acc_meas_;
+    Vector3d w_meas_;
+    Vector3d pbc_;
+    Vector3d pbc_meas_;
+    Vector3d vBC_meas_;
+    Vector3d f1_;
+    Vector3d tau1_;
+    Vector3d pbl1_;
+    Vector4d bql1_;
+    Vector3d f2_;
+    Vector3d tau2_;
+    Vector3d pbl2_;
+    Vector4d bql2_;
+
+    Matrix6d cov_f_;
+    Matrix6d cov_tau_;
+    Matrix3d cov_pbc_;
+    Matrix3d cov_wb_;
+    
+    // Aggregated data to construct Captures 
+    Vector6d acc_gyr_meas_;
+    Matrix6d acc_gyr_cov_;
+    Vector9d meas_ikin_;
+    MatrixXd cap_ftp_data_;
+    MatrixXd Qftp_;
+    Matrix3d Iq_;
+    Vector3d Lq_;
+    Vector3d bias_pbc_;
+    Vector7d prev_pose_curr_;
+
+    VectorXd x_origin_;
+
+
+
+    Cst2KFZeroMotion()
+    {
+        bias_pbc_ = ZERO3;
+    }
+
+    virtual void setOriginState()
+    {
+        x_origin_.resize(19);
+        x_origin_ << ZERO3, 0, 0, 0, 1, ZERO3, ZERO3, ZERO3, ZERO3;
+    }
+
+    void SetUp() override
+    {
+        ProcessorForceTorquePreintImuOdom3dIkin2KF::SetUp();
+        t0_.set(0.0);
+        TimeStamp t1; t1.set(1*DT);
+        TimeStamp t2; t2.set(2*DT);
+        TimeStamp t3; t3.set(3*DT);
+        TimeStamp t4; t4.set(3*DT);
+
+        // SETPRIOR RETRO-ENGINEERING
+        // We are not using setPrior because of processors initial captures problems so we have to
+        // - Manually set problem prior
+        // - call setOrigin on processors MotionProvider
+        setOriginState();
+        MatrixXd P_origin_ = pow(1e-3, 2) * MatrixXd::Identity(18,18);
+        KF1_ = problem_->emplaceFrame( t0_, x_origin_);
+        // Prior pose factor
+        CapturePosePtr pose_prior_capture = CaptureBase::emplace<CapturePose>(KF1_, t0_, nullptr, x_origin_.head(7), P_origin_.topLeftCorner(6, 6));
+        pose_prior_capture->emplaceFeatureAndFactor();
+        ///////////////////////////////////////////////////
+        // Prior velocity factor
+        CaptureBasePtr capV0 = CaptureBase::emplace<CaptureBase>(KF1_, "Vel0", t0_);
+        FeatureBasePtr featV0 = FeatureBase::emplace<FeatureBase>(capV0, "Vel0", x_origin_.segment<3>(7), P_origin_.block<3, 3>(6, 6));
+        FactorBasePtr facV0 = FactorBase::emplace<FactorBlockAbsolute>(featV0, featV0, KF1_->getV(), nullptr, false);
+
+        initializeData1();
+
+        // Special trick to make things work in the IMU + IKin + FTPreint case
+        // 0. Call keyFrameCallback of processorIKin so that its kf_buffer contains the first KF0
+        // 1. Call setOrigin processorIMU  to generate a fake captureIMU -> generates b_imu
+        // 2. process one IKin capture corresponding to the same KF, the factor needing b_imu -> generates a b_pbc
+        // 3. set processorForceTorquePreint origin which requires both b_imu and b_pbc
+        proc_inkin_->keyFrameCallback(KF1_, 0.5);
+        proc_imu_->setOrigin(KF1_);
+        auto CIKin0 = std::make_shared<CaptureInertialKinematics>(t0_, sen_ikin_, meas_ikin_, Iq_, Lq_);
+        CIKin0->process();
+        proc_ftpreint_->setOrigin(KF1_);
+
+        // T1
+        CaptureImuPtr CImu1 = std::make_shared<CaptureImu>(t1, sen_imu_, acc_gyr_meas_, acc_gyr_cov_);
+        CImu1->process();
+        auto CIKin1 = std::make_shared<CaptureInertialKinematics>(t1, sen_ikin_, meas_ikin_, Iq_, Lq_);
+        CIKin1->process();
+        auto CFTpreint1 = std::make_shared<CaptureForceTorquePreint>(t1, sen_ft_, CIKin1, CImu1, cap_ftp_data_, Qftp_);
+        CFTpreint1->process();
+
+
+        changeForData2();
+
+
+        // T2
+        CaptureImuPtr CImu2 = std::make_shared<CaptureImu>(t2, sen_imu_, acc_gyr_meas_, acc_gyr_cov_);
+        CImu2->process();
+        auto CIKin2 = std::make_shared<CaptureInertialKinematics>(t2, sen_ikin_, meas_ikin_, Iq_, Lq_);
+        CIKin2->process();
+        auto CFTpreint2 = std::make_shared<CaptureForceTorquePreint>(t2, sen_ft_, CIKin2, CImu2, cap_ftp_data_, Qftp_);
+        CFTpreint2->process();
+
+        changeForData3();
+
+        // T3
+        CaptureImuPtr CImu3 = std::make_shared<CaptureImu>(t3, sen_imu_, acc_gyr_meas_, acc_gyr_cov_);
+        CImu3->process();
+        CaptureInertialKinematicsPtr CIKin3 = std::make_shared<CaptureInertialKinematics>(t3, sen_ikin_, meas_ikin_, Iq_, Lq_);
+        CIKin3->process();
+        auto CFTpreint3 = std::make_shared<CaptureForceTorquePreint>(t3, sen_ft_, CIKin3, CImu3, cap_ftp_data_, Qftp_);
+        CFTpreint3->process();
+
+        // T4, just here to make sure the KF at t3 is created
+        CaptureImuPtr CImu4 = std::make_shared<CaptureImu>(t4, sen_imu_, acc_gyr_meas_, acc_gyr_cov_);
+        CImu4->process();
+        CaptureInertialKinematicsPtr CIKin4 = std::make_shared<CaptureInertialKinematics>(t4, sen_ikin_, meas_ikin_, Iq_, Lq_);
+        CIKin4->process();
+        auto CFTpreint4 = std::make_shared<CaptureForceTorquePreint>(t4, sen_ft_, CIKin3, CImu3, cap_ftp_data_, Qftp_);
+        CFTpreint4->process();
+
+        /////////////////////////////////////////////
+        // Add an Odom3D to make the biases observable
+        setOdomData();
+        Matrix6d rel_pose_cov = 1e-6 * Matrix6d::Identity();
+
+        KF2_ = problem_->getTrajectory()->getLastFrame();
+        CaptureBasePtr cap_pose_base = CaptureBase::emplace<CapturePose>(KF2_, KF2_->getTimeStamp(), nullptr, prev_pose_curr_, rel_pose_cov);
+        FeatureBasePtr ftr_odom3d_base = FeatureBase::emplace<FeaturePose>(cap_pose_base, prev_pose_curr_, rel_pose_cov);
+        FactorBase::emplace<FactorRelativePose3d>(ftr_odom3d_base, ftr_odom3d_base, KF1_, nullptr, false, TOP_MOTION);
+
+        ////////////////////////////////////////////
+        // Add absolute factor on Imu biases to simulate a fix()
+        Matrix6d Q_bi = 1e-8 * Matrix6d::Identity();
+        CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF1_, "bi0", t0_);
+        FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", ZERO6, Q_bi);
+        FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF1_->getCaptureOf(sen_imu_)->getSensorIntrinsic(),nullptr,false);
+
+        // Add loose absolute factor on initial bp bias since dynamical trajectories 
+        // able to excite the system enough to make COM and bias_p observable hard to generate in a simple gtest
+        Matrix3d Q_bp = 1e-1 * Matrix3d::Identity();
+        CaptureBasePtr cap_bp0 = CaptureBase::emplace<CaptureBase>(KF1_, "bp0", t0_);
+        FeatureBasePtr feat_bp0 = FeatureBase::emplace<FeatureBase>(cap_bp0, "bp0", bias_pbc_, Q_bp);
+        // FeatureBasePtr feat_bp0 = FeatureBase::emplace<FeatureBase>(cap_bp0, "bp0", bias_pbc_ + (Vector3d()<<0.1,0.1,0.1).finished(), Q_bp);
+        FactorBasePtr fac_bp0 = FactorBase::emplace<FactorBlockAbsolute>(feat_bp0, feat_bp0, KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic(),nullptr,false);
+ 
+    }
+
+    void buildDataVectors(){
+        pbc_meas_ = pbc_ + bias_pbc_;
+
+        acc_gyr_meas_ << acc_meas_, w_meas_;
+        acc_gyr_cov_ = 1e-3 * Matrix6d::Identity();
+        meas_ikin_ << pbc_meas_, vBC_meas_, w_meas_;
+
+        Vector6d f_meas; f_meas << f1_, f2_;
+        Vector6d tau_meas; tau_meas << tau1_, tau2_;
+        Matrix<double, 14, 1> kin_meas;
+        kin_meas << pbl1_, pbl2_, bql1_, bql2_;
+        cap_ftp_data_.resize(32,1);
+        cap_ftp_data_ << f_meas, tau_meas, pbc_meas_, acc_gyr_meas_.tail<3>(), kin_meas;
+
+        Qftp_ = Matrix<double, 18, 18>::Identity();
+        Qftp_.block<6, 6>(0, 0) = cov_f_;
+        Qftp_.block<6, 6>(6, 6) = cov_tau_;
+        Qftp_.block<3, 3>(12, 12) = cov_pbc_;
+        Qftp_.block<3, 3>(15, 15) = cov_wb_;
+    }
+
+    void setZeroMotionData()
+    {
+        // IMU CAPTURE DATA ZERO MOTION
+        acc_meas_ = -gravity();
+        w_meas_ = ZERO3;
+
+        // INERTIAL KINEMATICS CAPTURE DATA ZERO MOTION
+        pbc_ = ZERO3;
+        vBC_meas_ = ZERO3;
+        // momentum parameters
+        Iq_.setIdentity();
+        Lq_.setZero();
+
+        // FORCE TORQUE CAPTURE DATA ZERO MOTION
+        f1_ << -MASS * wolf::gravity() / 2; // Only gravity -> static robot on both feet
+        tau1_ << ZERO3;
+        pbl1_ << ZERO3;
+        bql1_ << ZERO3, 1;
+        f2_ << -MASS * wolf::gravity() / 2; // Only gravity -> static robot on both feet
+        tau2_ << ZERO3;
+        pbl2_ << ZERO3;
+        bql2_ << ZERO3, 1;
+
+        cov_f_ = 1e-4 * Matrix6d::Identity();
+        cov_tau_ = 1e-4 * Matrix6d::Identity();
+        cov_pbc_ = 1e-4 * Matrix3d::Identity();
+        cov_wb_ = 1e-4 * Matrix3d::Identity();
+    }
+
+    virtual void initializeData1()
+    {
+        setZeroMotionData();
+        buildDataVectors();
+    }
+
+    virtual void changeForData2()
+    {
+        // DO NOTHING IN BASE CLASS
+    }
+
+    virtual void changeForData3()
+    {
+        // DO NOTHING IN BASE CLASS
+    }
+
+    virtual void setOdomData()
+    {
+        prev_pose_curr_ << ZERO6, 1;
+    }
+
+};
+
+class Cst2KFZeroMotionBias : public Cst2KFZeroMotion
+{
+public:
+    void SetUp() override
+    {
+        bias_pbc_ = BIAS_PBC_SMAL;
+        Cst2KFZeroMotion::SetUp();
+    }
+};
+
+class Cst2KFXaxisLinearMotion : public Cst2KFZeroMotion
+{
+public:
+    void initializeData1() override
+    {
+        setZeroMotionData();
+        acc_meas_[0] += ACC;
+        f1_[0] += sen_ft_->getMass() * ACC / 2;
+        f2_[0] += sen_ft_->getMass() * ACC / 2;
+        // taus according to static Euler eq are zero since no lever
+
+        buildDataVectors();
+    }
+
+    void setOdomData() override
+    {
+        prev_pose_curr_ << 0.5*ACC*pow(3*DT,2), 0, 0, 0, 0, 0, 1;
+    }
+};
+
+class Cst2KFXaxisLinearMotionPbc : public Cst2KFZeroMotion
+{
+public:
+
+    void SetUp() override
+    {
+        Cst2KFZeroMotion::SetUp();
+    }
+
+    void setOriginState() override
+    {
+        Cst2KFZeroMotion::setOriginState();
+        x_origin_.segment<3>(10) = PBCX + PBCY + PBCZ; // Ok if PO is 0,0,0, 0,0,0,1
+    }
+
+    void initializeData1() override
+    {
+        setZeroMotionData();
+        pbc_ = PBCX + PBCY + PBCZ;
+        acc_meas_[0] += ACC;
+        f1_[0] += sen_ft_->getMass() * ACC / 2;
+        f2_[0] += sen_ft_->getMass() * ACC / 2;
+        // taus according to static Euler eq
+        tau1_ = -(pbl1_- pbc_).cross(f1_);  // torque at C due to f1
+        tau2_ = -(pbl2_- pbc_).cross(f2_);  // torque at C due to f2
+
+        buildDataVectors();
+    }
+
+    void setOdomData() override
+    {
+        prev_pose_curr_ << 0.5*ACC*pow(3*DT,2), 0, 0, 0, 0, 0, 1;
+    }
+};
+
+class Cst2KFXaxisLinearMotionPbcBias : public Cst2KFZeroMotion
+{
+public:
+    void SetUp() override
+    {
+        bias_pbc_ = BIAS_PBC_SMAL;
+        Cst2KFZeroMotion::SetUp();
+    }
+
+    void setOriginState() override
+    {
+        Cst2KFZeroMotion::setOriginState();
+        x_origin_.segment<3>(10) = PBCX + PBCY + PBCZ; // Ok if PO is 0,0,0, 0,0,0,1
+    }
+
+    void initializeData1() override
+    {
+        setZeroMotionData();
+        pbc_ = PBCX + PBCY + PBCZ;
+        acc_meas_[0] += ACC;
+        f1_[0] += sen_ft_->getMass() * ACC / 2;
+        f2_[0] += sen_ft_->getMass() * ACC / 2;
+        // taus according to static Euler eq
+        tau1_ = -(pbl1_- pbc_).cross(f1_);  // torque at C due to f1
+        tau2_ = -(pbl2_- pbc_).cross(f2_);  // torque at C due to f2
+
+        buildDataVectors();
+    }
+
+    void setOdomData() override
+    {
+        prev_pose_curr_ << 0.5*ACC*pow(3*DT,2), 0, 0, 0, 0, 0, 1;
+    }
+};
+class Cst2KFXaxisLinearMotionPbcBiasPQbl : public Cst2KFZeroMotion
+{
+public:
+    void SetUp() override
+    {
+        bias_pbc_ = BIAS_PBC_SMAL;
+        Cst2KFZeroMotion::SetUp();
+    }
+
+    void setOriginState() override
+    {
+        Cst2KFZeroMotion::setOriginState();
+        x_origin_.segment<3>(10) = PBCX + PBCY + PBCZ; // Ok if PO is 0,0,0, 0,0,0,1
+    }
+
+    void initializeData1() override
+    {
+        setZeroMotionData();
+        pbc_ = PBCX + PBCY + PBCZ;
+        acc_meas_[0] += ACC;
+        f1_[0] += sen_ft_->getMass() * ACC / 2;
+        f2_[0] += sen_ft_->getMass() * ACC / 2;
+        pbl1_ << 0.1, 0.1, 0.1;
+        pbl2_ << 0.1, 0.1, 0.1;
+        bql1_ << 1, 0, 0, 0;
+        bql2_ << 1, 0, 0, 0;
+        Quaterniond quat_bl1(bql1_);
+        Quaterniond quat_bl2(bql2_);
+        f1_ = quat_bl1.inverse() * f1_;
+        f2_ = quat_bl2.inverse() * f2_;
+
+        // taus according to static Euler eq
+        tau1_ = -(quat_bl1.inverse()*(pbl1_- pbc_)).cross(f1_);  // torque at C due to f1
+        tau2_ = -(quat_bl2.inverse()*(pbl2_- pbc_)).cross(f2_);  // torque at C due to f2
+
+        buildDataVectors();
+    }
+
+    void setOdomData() override
+    {
+        prev_pose_curr_ << 0.5*ACC*pow(3*DT,2), 0, 0, 0, 0, 0, 1;
+    }
+};
+
+class Cst2KFXaxisRotationPureTorque : public Cst2KFZeroMotion
+{
+public:
+    void SetUp() override
+    {
+        Cst2KFZeroMotion::SetUp();
+    }
+
+    void initializeData1() override
+    {
+        setZeroMotionData();
+
+        // taus according to static Euler eq
+        tau1_ << M_PI/3, 0, 0; // with Identity rotational inertial matrix, rotation of PI/2 rad.s-2
+        w_meas_ << M_PI/3, 0, 0;
+
+        buildDataVectors();
+    }
+
+    void setOdomData() override
+    {
+        prev_pose_curr_ << 0.5*ACC*pow(3*DT,2), 0, 0, 0, 0, 0, 1;
+    }
+};
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+/////////////////////////////   TESTS   //////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+TEST_F(Cst2KFZeroMotion, ZeroMotionZeroBias)
+{
+    // // TEST FIRST PURE INTEGRATION
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // THEN SOLVE
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+}
+
+TEST_F(Cst2KFZeroMotionBias, ZeroMotionBias)
+{
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // problem_->print(4,true,true,true);
+    // std::cout << report << std::endl;
+
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // Bias value on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+}
+
+TEST_F(Cst2KFXaxisLinearMotion, XLinearMotionZeroBias)
+{
+
+    Vector3d posi_diff = ZERO3;
+    posi_diff[0] = 0.5*ACC * pow(3*DT,2);
+    Vector3d vel_diff = ZERO3;
+    vel_diff[0] = ACC * 3*DT;
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState(), posi_diff, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // problem_->print(4, true, true, true);
+    // std::cout << report << std::endl;
+
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), posi_diff.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // Bias value on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+}
+
+TEST_F(Cst2KFXaxisLinearMotionPbc, XLinearMotionBias)
+{
+    Vector3d posi_diff = ZERO3;
+    posi_diff[0] = 0.5*ACC * pow(3*DT,2);
+    Vector3d vel_diff = ZERO3;
+    vel_diff[0] = ACC * 3*DT;
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2), 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // // COM position on Z axis is not observable with this problem
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2) + posi_diff.head(2), 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    // ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // std::cout << report << std::endl;
+
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2) + posi_diff.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // Bias value on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), ZERO3.head(2), 1e-6);
+}
+
+TEST_F(Cst2KFXaxisLinearMotionPbcBias, XLinearMotionBias)
+{
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // problem_->print(4, true, true, true);
+    // std::cout << report << std::endl;
+
+    Vector3d posi_diff = ZERO3;
+    posi_diff[0] = 0.5*ACC * pow(3*DT,2);
+    Vector3d vel_diff = ZERO3;
+    vel_diff[0] = ACC * 3*DT;
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2) + posi_diff.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // Bias value on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+}
+
+TEST_F(Cst2KFXaxisLinearMotionPbcBiasPQbl, XLinearMotionBias)
+{
+    problem_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    // problem_->print(4, true, true, true);
+    // std::cout << report << std::endl;
+
+    Vector3d posi_diff = ZERO3;
+    posi_diff[0] = 0.5*ACC * pow(3*DT,2);
+    Vector3d vel_diff = ZERO3;
+    vel_diff[0] = ACC * 3*DT;
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('P')->getState(), posi_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('V')->getState(), vel_diff, 1e-6);
+    // COM position on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('C')->getState().head(2), (PBCX + PBCY + PBCZ).head(2) + posi_diff.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('D')->getState(), vel_diff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    // Bias value on Z axis is not observable with this problem
+    ASSERT_MATRIX_APPROX(KF1_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+    ASSERT_MATRIX_APPROX(KF2_->getCaptureOf(sen_ikin_)->getSensorIntrinsic()->getState().head(2), BIAS_PBC_SMAL.head(2), 1e-6);
+}
+
+
+// TEST_F(Cst2KFXaxisRotationPureTorque, RotationOnlyNoSolve)
+// {
+
+//     Vector3d Lc_diff; Lc_diff << M_PI, 0, 0;
+//     Vector4d q_diff; q_diff << 1,0,0,0;
+//     ASSERT_MATRIX_APPROX(KF1_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+//     ASSERT_MATRIX_APPROX(KF1_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+//     ASSERT_MATRIX_APPROX(KF1_->getStateBlock('C')->getState(), ZERO3, 1e-6);
+//     ASSERT_MATRIX_APPROX(KF1_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+//     ASSERT_MATRIX_APPROX(KF1_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+//     ASSERT_MATRIX_APPROX(KF2_->getStateBlock('O')->getState(), q_diff, 1e-6);
+//     ASSERT_MATRIX_APPROX(KF2_->getStateBlock('L')->getState(), Lc_diff, 1e-6);
+// }
+
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_processor_inertial_kinematics.cpp b/test/gtest_processor_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bee3fb3c6127bdd35969b3d8bc3eb5d2659bd286
--- /dev/null
+++ b/test/gtest_processor_inertial_kinematics.cpp
@@ -0,0 +1,236 @@
+//--------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--------
+
+/**
+ * \file gtest_factor_inertial_kinematics.cpp
+ *
+ *  Created on: March 9, 2020
+ *      \author: Mederic Fourmy
+ */
+
+// debug
+#include <iostream>
+#include <fstream>
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+#include <core/math/rotations.h>
+#include <core/capture/capture_base.h>
+#include <core/capture/capture_pose.h>
+#include <core/feature/feature_base.h>
+#include <core/factor/factor_block_absolute.h>
+
+// Imu
+#include "imu/internal/config.h"
+#include "imu/capture/capture_imu.h"
+#include "imu/processor/processor_imu.h"
+#include "imu/sensor/sensor_imu.h"
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+#include "bodydynamics/processor/processor_inertial_kinematics.h"
+#include "bodydynamics/capture/capture_inertial_kinematics.h"
+
+#include <Eigen/Eigenvalues>
+
+using namespace wolf;
+using namespace Eigen;
+using namespace std;
+
+const Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
+const Eigen::Vector6d ZERO6 = Eigen::Vector6d::Zero();
+
+
+class FactorInertialKinematics_2KF : public testing::Test
+{
+    public:
+        wolf::TimeStamp t_;
+        ProblemPtr problem_;
+        SensorImuPtr sen_imu_;
+        SolverCeresPtr solver_;
+        SensorInertialKinematicsPtr sen_ikin_;
+        VectorXd x_origin_;
+        MatrixXd P_origin_;
+        FrameBasePtr KF0_;
+
+    void SetUp() override
+    {
+        std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+        problem_ = Problem::create("POVCDL", 3);
+
+        // CERES WRAPPER
+        solver_ = std::make_shared<SolverCeres>(problem_);
+        // solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+        // solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
+        // solver_->getSolverOptions().max_num_iterations = 1e4;
+
+        //===================================================== INITIALIZATION
+        t_.set(0.0);
+
+        // SENSOR + PROCESSOR INERTIAL KINEMATICS
+        ParamsSensorInertialKinematicsPtr intr_ik = std::make_shared<ParamsSensorInertialKinematics>();
+        intr_ik->std_pbc = 0.1;
+        intr_ik->std_vbc = 0.1;
+        VectorXd extr;  // default size for dynamic vector is 0-0 -> what we need
+        SensorBasePtr sen_ikin_base = problem_->installSensor("SensorInertialKinematics", "SenIK", extr, intr_ik);
+        // SensorBasePtr sen_ikin_base = problem_->installSensor("SensorInertialKinematics", "SenIK", extr,  bodydynamics_root_dir + "/test/sensor_inertial_kinematics.yaml");  // TODO: does not work!
+        sen_ikin_ = std::static_pointer_cast<SensorInertialKinematics>(sen_ikin_base);
+
+
+        // SENSOR + PROCESSOR Imu
+        SensorBasePtr sen_imu_base = problem_->installSensor("SensorImu", "SenImu", (Vector7d()<<0,0,0,0,0,0,1).finished(), bodydynamics_root_dir + "/test/sensor_imu.yaml");
+        sen_imu_ = std::static_pointer_cast<SensorImu>(sen_imu_base);
+        ProcessorBasePtr proc = problem_->installProcessor("ProcessorImu", "Imu pre-integrator", "SenImu", bodydynamics_root_dir + "/test/processor_imu.yaml");
+        // auto processor_imu = std::static_pointer_cast<ProcessorImu>(proc);
+
+
+
+        ParamsProcessorInertialKinematicsPtr params_ik = std::make_shared<ParamsProcessorInertialKinematics>();
+        params_ik->sensor_angvel_name = "SenImu";
+        params_ik->std_bp_drift = 0.0000001;
+        ProcessorBasePtr proc_ikin_base = problem_->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", sen_ikin_, params_ik);
+        // ProcessorBasePtr proc_ikin_base = problem_->installProcessor("ProcessorInertialKinematics", "PInertialKinematics", "SenIK", bodydynamics_root_dir + "/test/processor_inertial_kinematics.yaml");
+        // auto proc_inkin = std::static_pointer_cast<ProcessorInertialKinematics>(proc_ikin_base);
+
+        // Set origin of the problem
+        x_origin_.resize(19);
+        x_origin_ << ZERO3, 0,0,0,1, ZERO3, ZERO3, ZERO3, ZERO3;
+        VectorXd std_vec = pow(1e-3, 2)*VectorXd::Ones(18); 
+        VectorComposite prior(x_origin_,  "POVCDL", {3,4,3,3,3,3});
+        VectorComposite prior_std(std_vec, "POVCDL", {3,3,3,3,3,3});
+        problem_->setPriorFactor(prior, prior_std, t_);
+        KF0_ = problem_->getTrajectory()->getLastFrame();
+
+        // Set origin of processor Imu
+        std::static_pointer_cast<ProcessorImu>(proc)->setOrigin(KF0_);
+
+        ////////////////////////////////////////////
+        // Add absolute factor on Imu biases to simulate a fix()
+        Matrix6d Q_bi = 1e-8 * Matrix6d::Identity();
+        CaptureBasePtr capbi0 = CaptureBase::emplace<CaptureBase>(KF0_, "bi0", t_);
+        FeatureBasePtr featbi0 = FeatureBase::emplace<FeatureBase>(capbi0, "bi0", ZERO6, Q_bi);
+        FactorBasePtr facbi0 = FactorBase::emplace<FactorBlockAbsolute>(featbi0, featbi0, KF0_->getCaptureOf(sen_imu_)->getSensorIntrinsic(),nullptr,false);
+
+    }
+
+    void TearDown() override{}
+};
+
+TEST_F(FactorInertialKinematics_2KF, sensor_and_processors_registration)
+{
+    TimeStamp t0; t0.set(0);
+    TimeStamp t1; t1.set(1);
+    TimeStamp t2; t2.set(2);
+    TimeStamp t3; t3.set(3);
+
+    // PROCESS ONE Imu CAPTURE
+    Vector6d acc_gyr_meas;
+    acc_gyr_meas << -gravity(), 0,0,0;
+    acc_gyr_meas[0] = 1;
+    Matrix6d acc_gyr_cov = 1e-3*Matrix6d::Identity();
+
+    // PROCESS ONE INERTIAL KINEMATICS CAPTURE
+    Vector3d pBC_meas = Vector3d::Zero();
+    Vector3d vBC_meas = Vector3d::Zero();
+    Vector3d w_meas =   Vector3d::Zero();
+    // momentum parameters
+    Matrix3d Iq; Iq.setIdentity();
+    Vector3d Lq = Vector3d::Zero();
+    Vector9d meas_ikin0; meas_ikin0 << pBC_meas, vBC_meas, w_meas;
+
+    // sen_imu_->getIntrinsic()->fix();
+
+    // T0
+    CaptureImuPtr CImu0 = std::make_shared<CaptureImu>(t0, sen_imu_, acc_gyr_meas, acc_gyr_cov);
+    sen_imu_->process(CImu0);
+    auto CIKin0 = std::make_shared<CaptureInertialKinematics>(t0, sen_ikin_, meas_ikin0, Iq, Lq);
+    sen_ikin_->process(CIKin0);
+
+    CIKin0->getSensorIntrinsic()->fix();
+
+    // T1
+    CaptureImuPtr CImu1 = std::make_shared<CaptureImu>(t1, sen_imu_, acc_gyr_meas, acc_gyr_cov);
+    sen_imu_->process(CImu1);
+    // CImu1->getSensorIntrinsic()->fix();
+
+    auto CIKin1 = std::make_shared<CaptureInertialKinematics>(t1, sen_ikin_, meas_ikin0, Iq, Lq);
+    sen_ikin_->process(CIKin1);
+    CIKin1->getSensorIntrinsic()->fix();
+
+
+    // T2
+    CaptureImuPtr CImu2 = std::make_shared<CaptureImu>(t2, sen_imu_, acc_gyr_meas, acc_gyr_cov);
+    // CImu2->getSensorIntrinsic()->fix();
+    sen_imu_->process(CImu2);
+
+    auto CIKin2 = std::make_shared<CaptureInertialKinematics>(t2, sen_ikin_, meas_ikin0, Iq, Lq);
+    sen_ikin_->process(CIKin2);
+    CIKin2->getSensorIntrinsic()->fix();
+
+    // T3
+    CaptureImuPtr CImu3 = std::make_shared<CaptureImu>(t3, sen_imu_, acc_gyr_meas, acc_gyr_cov);
+    // CImu3->getSensorIntrinsic()->fix();
+    sen_imu_->process(CImu3);
+
+    CaptureInertialKinematicsPtr CIKin3 = std::make_shared<CaptureInertialKinematics>(t3, sen_ikin_, meas_ikin0, Iq, Lq);
+    sen_ikin_->process(CIKin3);
+    CIKin3->getSensorIntrinsic()->fix();
+
+    KF0_->perturb();
+
+    std::string report = solver_->solve(SolverManager::ReportVerbosity::BRIEF); // 0: nothing, 1: BriefReport, 2: FullReport;
+    std::cout << report << std::endl;
+    problem_->print(4,true,true,true);
+
+    Vector3d pdiff; pdiff << 4.5, 0, 0;
+    Vector3d vdiff; vdiff << 3, 0, 0;
+
+    FrameBasePtr KF1 = problem_->getTrajectory()->getLastFrame();
+
+
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('P')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('V')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('C')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('D')->getState(), ZERO3, 1e-6);
+    ASSERT_MATRIX_APPROX(KF0_->getStateBlock('L')->getState(), ZERO3, 1e-6);
+
+    ASSERT_MATRIX_APPROX(KF1->getStateBlock('P')->getState(), pdiff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1->getStateBlock('V')->getState(), vdiff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1->getStateBlock('C')->getState(), pdiff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1->getStateBlock('D')->getState(), vdiff, 1e-6);
+    ASSERT_MATRIX_APPROX(KF1->getStateBlock('L')->getState(), ZERO3, 1e-6);
+}
+
+
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_processor_point_feet_nomove.cpp b/test/gtest_processor_point_feet_nomove.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2a246f3a1292c1b96a74ffeecb7c1ec65bc66471
--- /dev/null
+++ b/test/gtest_processor_point_feet_nomove.cpp
@@ -0,0 +1,199 @@
+//--------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--------
+
+/**
+ * \file gtest_processor_point_feet_nomove.cpp
+ *
+ *  Created on: October 28, 2020
+ *      \author: Mederic Fourmy
+ */
+
+// debug
+#include <iostream>
+#include <fstream>
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+// WOLF
+#include <core/problem/problem.h>
+#include <core/capture/capture_base.h>
+// #include <core/ceres_wrapper/solver_ceres.h>
+// #include <core/math/rotations.h>
+// #include <core/capture/capture_pose.h>
+// #include <core/feature/feature_base.h>
+// #include <core/factor/factor_block_absolute.h>
+
+// BODYDYNAMICS
+#include "bodydynamics/internal/config.h"
+#include "bodydynamics/capture/capture_point_feet_nomove.h"
+#include "bodydynamics/sensor/sensor_point_feet_nomove.h"
+#include "bodydynamics/processor/processor_point_feet_nomove.h"
+
+#include <Eigen/Eigenvalues>
+
+using namespace wolf;
+using namespace Eigen;
+using namespace std;
+
+const Eigen::Vector3d ZERO3 = Eigen::Vector3d::Zero();
+// const Eigen::Vector6d ZERO6 = Eigen::Vector6d::Zero();
+
+
+class PointFeetCaptures : public testing::Test
+{
+    public:
+        wolf::TimeStamp t_;
+        ProblemPtr problem_;
+        SensorPointFeetNomovePtr sen_pfnm_;
+        ProcessorBasePtr proc_pfnm_base_;
+        VectorXd x_origin_;
+        // MatrixXd P_origin_;
+        FrameBasePtr KF0_;
+
+        CapturePointFeetNomovePtr CPF0_;
+        CapturePointFeetNomovePtr CPF1_;
+        CapturePointFeetNomovePtr CPF2_;
+        CapturePointFeetNomovePtr CPF3_;
+        CapturePointFeetNomovePtr CPF4_;
+        CapturePointFeetNomovePtr CPF5_;
+        CapturePointFeetNomovePtr CPF6_;
+
+    void SetUp() override
+    {
+        std::string bodydynamics_root_dir = _WOLF_BODYDYNAMICS_ROOT_DIR;
+
+        problem_ = Problem::create("PO", 3);
+
+        // CERES WRAPPER
+        // solver_ = std::make_shared<SolverCeres>(problem_);
+        // solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION; //ceres::TRUST_REGION;ceres::LINE_SEARCH
+        // solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
+        // solver_->getSolverOptions().max_num_iterations = 1e4;
+
+        //===================================================== INITIALIZATION
+
+        // SENSOR + PROCESSOR POINT FEET NOMOVE
+        ParamsSensorPointFeetNomovePtr intr_pfn = std::make_shared<ParamsSensorPointFeetNomove>();
+        intr_pfn->std_foot_nomove_ = 0.1;
+        VectorXd extr;  // default size for dynamic vector is 0-0 -> what we need
+        SensorBasePtr sen_pfnm_base = problem_->installSensor("SensorPointFeetNomove", "SenPfnm", extr, intr_pfn);
+        sen_pfnm_ = std::static_pointer_cast<SensorPointFeetNomove>(sen_pfnm_base);
+        ParamsProcessorPointFeetNomovePtr params_pfnm = std::make_shared<ParamsProcessorPointFeetNomove>();
+        proc_pfnm_base_ = problem_->installProcessor("ProcessorPointFeetNomove", "PPointFeetNomove", sen_pfnm_, params_pfnm);
+
+        // Set origin of the problem
+        x_origin_.resize(7);
+        x_origin_ << 0,0,0, 0,0,0,1;
+        VectorXd std_vec = pow(1e-3, 2)*VectorXd::Ones(6); 
+        VectorComposite prior(x_origin_,   "PO", {3,4});
+        VectorComposite prior_std(std_vec, "PO", {3,3});
+        problem_->setPriorFactor(prior, prior_std, 0.0);
+        KF0_ = problem_->getTrajectory()->getLastFrame();
+
+        // Simulate captures with 4 point feet
+        std::unordered_map<int, Vector3d> kin_incontact_A({
+            {1, Vector3d::Zero()},
+            {2, Vector3d::Zero()},
+            {3, Vector3d::Zero()},
+            {4, Vector3d::Zero()}
+        });
+
+        CPF0_ = std::make_shared<CapturePointFeetNomove>(0.0, sen_pfnm_, kin_incontact_A);
+        std::cout << CPF0_->getName() << std::endl;
+        CPF1_ = std::make_shared<CapturePointFeetNomove>(1.0, sen_pfnm_, kin_incontact_A);
+        CPF2_ = std::make_shared<CapturePointFeetNomove>(2.0, sen_pfnm_, kin_incontact_A);
+
+        // contact of the 2nd foot lost
+        std::unordered_map<int, Eigen::Vector3d> kin_incontact_B({
+            {1, Vector3d::Ones()},
+            {3, Vector3d::Ones()},
+            {4, Vector3d::Ones()}
+        });
+        CPF3_ = std::make_shared<CapturePointFeetNomove>(3.0, sen_pfnm_, kin_incontact_B);
+        CPF4_ = std::make_shared<CapturePointFeetNomove>(4.0, sen_pfnm_, kin_incontact_B);
+
+        // contact of the 3rd foot lost
+        std::unordered_map<int, Eigen::Vector3d> kin_incontact_C({
+            {1, 2*Vector3d::Ones()},
+            {4, 2*Vector3d::Ones()}
+        });
+        CPF5_ = std::make_shared<CapturePointFeetNomove>(5.0, sen_pfnm_, kin_incontact_C);
+        CPF6_ = std::make_shared<CapturePointFeetNomove>(6.0, sen_pfnm_, kin_incontact_C);
+
+    }
+
+    void TearDown() override{}
+};
+
+TEST_F(PointFeetCaptures, process_all_capture_first)
+{
+
+    CPF0_->process();
+    CPF1_->process();
+    CPF2_->process();
+    CPF3_->process();
+    CPF4_->process();
+    CPF5_->process();
+    CPF6_->process(); // one last capture to create the factor
+
+    // factor creation due to keyFrameCallback
+    FrameBasePtr KF1 = problem_->emplaceFrame(2.0, x_origin_);
+    proc_pfnm_base_->keyFrameCallback(KF1);
+
+    FrameBasePtr KF2 = problem_->emplaceFrame(3.0, x_origin_);
+    proc_pfnm_base_->keyFrameCallback(KF2);
+
+    problem_->print(4,1,1,1);
+}
+
+
+TEST_F(PointFeetCaptures, process_all_capture_last)
+{
+    // First, get the key frames from the keyFrameCallback
+    FrameBasePtr KF1 = problem_->emplaceFrame(2.0, x_origin_);
+    proc_pfnm_base_->keyFrameCallback(KF1);
+
+    FrameBasePtr KF2 = problem_->emplaceFrame(3.0, x_origin_);
+    proc_pfnm_base_->keyFrameCallback(KF2);
+
+    // Then process the captures
+    CPF0_->process();
+    CPF1_->process();
+    CPF2_->process();
+    CPF3_->process();
+    CPF4_->process();
+    CPF5_->process();
+    CPF6_->process(); // one last capture to create the factor
+
+    problem_->print(4,1,1,1);
+}
+
+
+
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_sensor_force_torque.cpp b/test/gtest_sensor_force_torque.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..470b78a2f10e92f44e604b5c267de4497e882000
--- /dev/null
+++ b/test/gtest_sensor_force_torque.cpp
@@ -0,0 +1,57 @@
+//--------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--------
+/**
+ * \file gtest_sensor_force_torque.cpp
+ *
+ *  Created on: Feb 26, 2020
+ *      \author: jsola
+ */
+
+
+#include "bodydynamics/sensor/sensor_force_torque.h"
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+#include <Eigen/Dense>
+
+using namespace wolf;
+using namespace Eigen;
+
+TEST(SensorForceTorque, constructor_and_getters)
+{
+    ParamsSensorForceTorquePtr intr = std::make_shared<ParamsSensorForceTorque>();
+    intr->std_f = 1;
+    intr->std_tau = 2;
+    VectorXd extr(0);
+    SensorForceTorque S(extr, intr);
+
+    ASSERT_EQ(S.getForceNoiseStd(), 1);
+    ASSERT_EQ(S.getTorqueNoiseStd(), 2);
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/test/gtest_sensor_inertial_kinematics.cpp b/test/gtest_sensor_inertial_kinematics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc981d726049df6bb6145fe8d9ae3c6708a4499a
--- /dev/null
+++ b/test/gtest_sensor_inertial_kinematics.cpp
@@ -0,0 +1,57 @@
+//--------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--------
+/**
+ * \file gtest_sensor_inertial_kinematics.cpp
+ *
+ *  Created on: Feb 28, 2020
+ *      \author: jsola
+ */
+
+
+#include "bodydynamics/sensor/sensor_inertial_kinematics.h"
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+
+#include <Eigen/Dense>
+
+using namespace wolf;
+using namespace Eigen;
+
+TEST(SensorInertialKinematics, constructor_and_getters)
+{
+    ParamsSensorInertialKinematicsPtr intr = std::make_shared<ParamsSensorInertialKinematics>();
+    intr->std_pbc = 1;
+    intr->std_vbc = 2;
+    VectorXd extr(0);
+    SensorInertialKinematics S(extr, intr);
+
+    ASSERT_EQ(S.getPbcNoiseStd(), 1);
+    ASSERT_EQ(S.getVbcNoiseStd(), 2);
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/demos/processor_imu.yaml b/test/processor_imu.yaml
similarity index 51%
rename from demos/processor_imu.yaml
rename to test/processor_imu.yaml
index 3a7f2adf26c62e430549401406c49dc8a52f55fa..468ed456e8fff451dc495eeb2e48b1eabaf104be 100644
--- a/demos/processor_imu.yaml
+++ b/test/processor_imu.yaml
@@ -1,11 +1,10 @@
-type: "ProcessorIMU"         # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+type: "ProcessorImu"         # This must match the KEY used in the SensorFactory. Otherwise it is an error.
 name: "Main imu"    # This is ignored. The name provided to the SensorFactory prevails
 time_tolerance: 0.0025         # Time tolerance for joining KFs
 unmeasured_perturbation_std: 0.00000001
 keyframe_vote:
-    max_time_span:      1.0   # seconds
+    max_time_span:      2.1   # seconds
     max_buff_length:    500   # motion deltas
-    dist_traveled:      2.0   # meters
-    angle_turned:       0.1   # radians (1 rad approx 57 deg, approx 60 deg)
-    voting_active:      false
-    voting_aux_active:  false
\ No newline at end of file
+    dist_traveled:      20000.0   # meters
+    angle_turned:       1000   # radians (1 rad approx 57 deg, approx 60 deg)
+    voting_active:      true
\ No newline at end of file
diff --git a/test/sensor_imu.yaml b/test/sensor_imu.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..124d55651a1b410e825aaa87b565719561f8e252
--- /dev/null
+++ b/test/sensor_imu.yaml
@@ -0,0 +1,9 @@
+type: "SensorImu"             # This must match the KEY used in the SensorFactory. Otherwise it is an error.
+name: "Main Imu Sensor"        # This is ignored. The name provided to the SensorFactory prevails
+motion_variances: 
+   a_noise:                0.03     # standard deviation of Acceleration noise (same for all the axis) in m/s2
+   w_noise:                0.002    # standard deviation of Gyroscope noise (same for all the axis) in rad/sec
+   ab_initial_stdev:       0.00     # m/s2    - initial bias 
+   wb_initial_stdev:       0.0     # rad/sec - initial bias 
+   ab_rate_stdev:          0.0001       # m/s2/sqrt(s)           
+   wb_rate_stdev:          0.0001    # rad/s/sqrt(s)
\ No newline at end of file
diff --git a/test/utils_gtest.h b/test/utils_gtest.h
deleted file mode 100644
index 9cb2166e8429cd236f77c502891c14880e57ec30..0000000000000000000000000000000000000000
--- a/test/utils_gtest.h
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * \file utils_gtest.h
- * \brief Some utils for gtest
- * \author Jeremie Deray
- *  Created on: 26/09/2016
- *  Eigen macros extension by: Joan Sola on 26/04/2017
- */
-
-#ifndef WOLF_UTILS_GTEST_H
-#define WOLF_UTILS_GTEST_H
-
-#include <gtest/gtest.h>
-
-// Macros for testing equalities and inequalities.
-//
-//    * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual
-//    * {ASSERT|EXPECT}_NE(v1, v2):           Tests that v1 != v2
-//    * {ASSERT|EXPECT}_LT(v1, v2):           Tests that v1 < v2
-//    * {ASSERT|EXPECT}_LE(v1, v2):           Tests that v1 <= v2
-//    * {ASSERT|EXPECT}_GT(v1, v2):           Tests that v1 > v2
-//    * {ASSERT|EXPECT}_GE(v1, v2):           Tests that v1 >= v2
-//
-// C String Comparisons.  All tests treat NULL and any non-NULL string
-// as different.  Two NULLs are equal.
-//
-//    * {ASSERT|EXPECT}_STREQ(s1, s2):     Tests that s1 == s2
-//    * {ASSERT|EXPECT}_STRNE(s1, s2):     Tests that s1 != s2
-//    * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case
-//    * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case
-//
-// Macros for comparing floating-point numbers.
-//
-//    * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual):
-//         Tests that two float values are almost equal.
-//    * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual):
-//         Tests that two double values are almost equal.
-//    * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error):
-//         Tests that v1 and v2 are within the given distance to each other.
-//
-// These predicate format functions work on floating-point values, and
-// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g.
-//
-//   EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0);
-//
-// Macros that execute statement and check that it doesn't generate new fatal
-// failures in the current thread.
-//
-//    * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement);
-
-// http://stackoverflow.com/a/29155677
-
-namespace testing
-{
-namespace internal
-{
-enum GTestColor
-{
-  COLOR_DEFAULT,
-  COLOR_RED,
-  COLOR_GREEN,
-  COLOR_YELLOW
-};
-
-extern void ColoredPrintf(GTestColor color, const char* fmt, ...);
-
-#define PRINTF(...) \
-  do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN,\
-  "[          ] "); \
-  testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } \
-  while(0)
-
-#define PRINT_TEST_FINISHED \
-{ \
-  const ::testing::TestInfo* const test_info = \
-    ::testing::UnitTest::GetInstance()->current_test_info(); \
-  PRINTF(std::string("Finished test case ").append(test_info->test_case_name()).\
-          append(" of test ").append(test_info->name()).append(".\n").c_str()); \
-}
-
-// C++ stream interface
-class TestCout : public std::stringstream
-{
-public:
-  ~TestCout()
-  {
-    PRINTF("%s\n", str().c_str());
-  }
-};
-
-/* Usage :
-
-TEST(Test, Foo)
-{
-  // the following works but prints default stream
-  EXPECT_TRUE(false) << "Testing Stream.";
-
-  // or you can play with AINSI color code
-  EXPECT_TRUE(false) << "\033[1;31m" << "Testing Stream.";
-
-  // or use the above defined macros
-
-  PRINTF("Hello world");
-
-  // or
-
-  TEST_COUT << "Hello world";
-}
-
-*/
-#define TEST_COUT testing::internal::TestCout()
-
-} // namespace internal
-
-/* Macros related to testing Eigen classes:
- */
-#define EXPECT_MATRIX_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
-                  return (lhs - rhs).isMuchSmallerThan(1, precision); \
-               }, \
-               C_expect, C_actual);
-
-#define ASSERT_MATRIX_APPROX(C_expect, C_actual, precision) ASSERT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
-                  return (lhs - rhs).isMuchSmallerThan(1, precision); \
-               }, \
-               C_expect, C_actual);
-
-#define EXPECT_QUATERNION_APPROX(C_expect, C_actual, precision) EXPECT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
-
-#define ASSERT_QUATERNION_APPROX(C_expect, C_actual, precision) ASSERT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
-
-#define EXPECT_POSE2D_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
-                   MatrixXs er = lhs - rhs; \
-                   er(2) = pi2pi((Scalar)er(2)); \
-                   return er.isMuchSmallerThan(1, precision); \
-               }, \
-               C_expect, C_actual);
-
-#define ASSERT_POSE2D_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXs lhs, const Eigen::MatrixXs rhs) { \
-                   MatrixXs er = lhs - rhs; \
-                   er(2) = pi2pi((Scalar)er(2)); \
-                   return er.isMuchSmallerThan(1, precision); \
-               }, \
-               C_expect, C_actual);
-
-} // namespace testing
-
-#endif /* WOLF_UTILS_GTEST_H */