diff --git a/SimoxUtility/CMakeLists.txt b/SimoxUtility/CMakeLists.txt
index 0b14327c3f7dd1cb08879188f5355e0c898668eb..dd3b2d65174ce122bb0c2e9d761b9ec94c597351 100644
--- a/SimoxUtility/CMakeLists.txt
+++ b/SimoxUtility/CMakeLists.txt
@@ -81,6 +81,7 @@ SET(SOURCES
     math/pose/check_rotation_matrix.cpp
     math/pose/invert.cpp
     math/pose/orthogonalize.cpp
+    math/pose/interpolate.cpp
 
     math/statistics/Histogram1D.cpp
 
@@ -99,12 +100,13 @@ SET(INCLUDES
     preprocessor/variadic_for_each.h
     preprocessor/drop_front.h
 
-    algorithm/for_each_if.h
+    algorithm/advanced.h
     algorithm/apply.hpp
+    algorithm/contains.h
+    algorithm/for_each_if.h
+    algorithm/fuzzy_find.h
     algorithm/get_map_keys_values.h
     algorithm/minmax.h
-    algorithm/advanced.h
-    algorithm/fuzzy_find.h
     algorithm/string/string_tools.h
     algorithm/string/string_conversion.h
     algorithm/string/string_conversion_eigen.h
@@ -220,6 +222,7 @@ SET(INCLUDES
     math/pose/orthogonalize.h
     math/pose/pose.h
     math/pose/transform.h
+    math/pose/interpolate.h
 
     math/similarity/cosine_similarity.h
     math/similarity/angular_similarity.h
diff --git a/SimoxUtility/algorithm.h b/SimoxUtility/algorithm.h
index a4752d98709411b0351ec0376e155c5d794d88a6..bc7252a2cee84dc106b548b5a3e3025bdae0c14e 100644
--- a/SimoxUtility/algorithm.h
+++ b/SimoxUtility/algorithm.h
@@ -3,6 +3,7 @@
 // This file is generated!
 
 #include "algorithm/advanced.h"
+#include "algorithm/contains.h"
 #include "algorithm/for_each_if.h"
 #include "algorithm/fuzzy_find.h"
 #include "algorithm/get_map_keys_values.h"
diff --git a/SimoxUtility/algorithm/contains.h b/SimoxUtility/algorithm/contains.h
new file mode 100644
index 0000000000000000000000000000000000000000..45516257dff2a166ed516779558e4498ef93954c
--- /dev/null
+++ b/SimoxUtility/algorithm/contains.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <map>
+
+
+namespace simox::alg
+{
+
+    // For overloads of `simox::alg::contains` taking `std::string`:
+    // #include <SimoxUtility/algorithm/string/string_tools.h>
+
+    // GENERAL CONTAINERS
+
+    /**
+     * Return true if `value` is an element of `container`, false otherwise.
+     */
+    template <class ContainerT, class ValueT>
+    bool contains(const ContainerT& container, const ValueT& value)
+    {
+        return std::find_if(container.begin(), container.end(), [&value](const auto& v)
+        {
+            return v == value;
+        }) != container.end();
+    }
+
+    /**
+     * Return true if `value` is an element of `container` (as indicated by `predicate`),
+     * false otherwise.
+     */
+    template <class ContainerT, class ValueT, class PredicateT>
+    bool contains(const ContainerT& container, const ValueT& value, const PredicateT& predicate)
+    {
+        return std::find_if(container.begin(), container.end(), [&value, &predicate](const auto& v)
+        {
+            return predicate(v, value);
+        }) != container.end();
+    }
+
+
+    // MAPS
+
+    /**
+     * Return true if `key` is a key in `map`, false otherwise.
+     */
+    template <class K, class V, template<class...> class MapT = std::map, class...Ts>
+    bool contains_key(const MapT<K, V, Ts...>& map, const K& key)
+    {
+        return map.count(key) > 0;
+    }
+
+    /**
+     * Return true if `value` is a value in `map`, false otherwise.
+     */
+    template <class K, class V, template<class...> class MapT = std::map, class...Ts>
+    bool contains_value(const MapT<K, V, Ts...>& map, const V& value)
+    {
+        return std::find_if(map.begin(), map.end(), [&value](const std::pair<K, V>& item)
+        {
+            return item.second == value;
+        }) != map.end();
+    }
+
+
+    // Overloads for string literals (which otherwise don't match the previous definition).
+    template <class V, template<class...> class MapT = std::map, class...Ts>
+    bool contains_key(const MapT<std::string, V, Ts...>& map, const std::string& key)
+    {
+        return map.count(key) > 0;
+    }
+    template <class K, template<class...> class MapT = std::map, class...Ts>
+    bool contains_value(const MapT<K, std::string, Ts...>& map, const std::string& value)
+    {
+        return contains_value<K, std::string>(map, value);
+    }
+
+}
diff --git a/SimoxUtility/algorithm/get_map_keys_values.h b/SimoxUtility/algorithm/get_map_keys_values.h
index eeb88a71f332e332523d1596fe1f387cc64f3c23..7d869b102c3aa8ef85ae369b0c1b165fcdeb32f7 100644
--- a/SimoxUtility/algorithm/get_map_keys_values.h
+++ b/SimoxUtility/algorithm/get_map_keys_values.h
@@ -112,6 +112,24 @@ namespace simox::alg
     {
         return get_value_ptrs(map);
     }
+
+
+    /// Get a value from `map` if it exsits or a default value otherwise.
+    template <class K, class V, template<class...> class MapT = std::map, class...Ts>
+    V get_value_or_default(const MapT<K, V, Ts...>& map, const K& key, const V& default_value)
+    {
+        auto it = map.find(key);
+        return it != map.end() ? it->second : default_value;
+    }
+
+    // overload for `std::string` to match `char[]`.
+    template <class K, template<class...> class MapT = std::map, class...Ts>
+    std::string get_value_or_default(const MapT<K, std::string, Ts...>& map, const K& key, const std::string& default_value)
+    {
+        auto it = map.find(key);
+        return it != map.end() ? it->second : default_value;
+    }
+
 }
 
 
diff --git a/SimoxUtility/algorithm/string/string_tools.h b/SimoxUtility/algorithm/string/string_tools.h
index 327ec2516c1e7c2273850bed8bc2a70c8b783e2f..404f2c038daade083ec565f9be894ba151959b09 100644
--- a/SimoxUtility/algorithm/string/string_tools.h
+++ b/SimoxUtility/algorithm/string/string_tools.h
@@ -44,6 +44,7 @@ namespace simox::alg
      */
     std::string capitalize_words(const std::string& str);
 
+
     void trim(std::string& str, const std::locale& locale = DEFAULT_LOCALE);
 
     std::string trim_copy(const std::string& str, const std::locale& locale = DEFAULT_LOCALE);
@@ -52,6 +53,7 @@ namespace simox::alg
 
     std::string trim_copy_if(const std::string& str, const std::string& trim = "\t ");
 
+
     std::vector<std::string> split(const std::string& str, const std::string& splitBy = "\t ", bool trimElements = true,
                                    bool removeEmptyElements = true, const std::locale& locale = DEFAULT_LOCALE);
 
@@ -62,17 +64,27 @@ namespace simox::alg
     std::string join(const std::vector<std::string> vec, const std::string& delimiter = " ", bool trimElements = false,
                      bool ignoreEmptyElements = false, const std::locale& locale = DEFAULT_LOCALE);
 
+
     std::string replace_first(std::string const& input, std::string const& search, std::string const& replace);
 
     std::string replace_last(std::string const& input, std::string const& search, std::string const& replace);
 
     std::string replace_all(std::string const& input, std::string const& search, std::string const& replace);
 
+
     bool starts_with(std::string const& input, std::string const& search);
 
     bool ends_with(std::string const& input, std::string const& search);
 
     bool contains(const std::string& haystack, const std::string& needle);
+    inline bool contains(const std::string& haystack, const char* needle)
+    {
+        return contains(haystack, std::string(needle));
+    }
+    inline bool contains(const std::string& string, const char character)
+    {
+        return string.find(character) != std::string::npos;
+    }
 
 
     template <typename IterT>
diff --git a/SimoxUtility/math/pose.h b/SimoxUtility/math/pose.h
index 3ec8afc2556e947326845580b200e37a06c1cbf5..003bff68627adecdf200b16a0300e509bc23bf66 100644
--- a/SimoxUtility/math/pose.h
+++ b/SimoxUtility/math/pose.h
@@ -4,6 +4,7 @@
 
 #include "pose/align_box_orientation.h"
 #include "pose/check_rotation_matrix.h"
+#include "pose/interpolate.h"
 #include "pose/invert.h"
 #include "pose/is_homogeneous_transform.h"
 #include "pose/orthogonalize.h"
diff --git a/SimoxUtility/math/pose/interpolate.cpp b/SimoxUtility/math/pose/interpolate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..61dec29db6f41490a909e3c65477a95c085a799d
--- /dev/null
+++ b/SimoxUtility/math/pose/interpolate.cpp
@@ -0,0 +1,25 @@
+#include "interpolate.h"
+
+
+namespace simox::math {
+
+
+Eigen::Affine3f interpolatePose(const Eigen::Affine3f &posePre, const Eigen::Affine3f &poseNext, float t) {
+    
+    assert(0 <= t <= 1);
+    
+    Eigen::Affine3f pose = Eigen::Affine3f::Identity();
+
+    pose.translation() = (1 - t) * posePre.translation() + t * poseNext.translation();
+
+    Eigen::Quaternionf rotPrev(posePre.linear().matrix());
+    Eigen::Quaternionf rotNext(poseNext.linear().matrix());
+
+    Eigen::Quaternionf rotNew = rotPrev.slerp(t, rotNext);
+
+    pose.linear() = rotNew.toRotationMatrix();
+
+    return pose;
+}
+
+}  // namespace simox::math
\ No newline at end of file
diff --git a/SimoxUtility/math/pose/interpolate.h b/SimoxUtility/math/pose/interpolate.h
new file mode 100644
index 0000000000000000000000000000000000000000..f31e8dbe730afa2ec933967cf5dd04bc73e6f75c
--- /dev/null
+++ b/SimoxUtility/math/pose/interpolate.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <Eigen/Geometry>
+
+namespace simox::math {
+
+Eigen::Affine3f interpolatePose(const Eigen::Affine3f &posePre, const Eigen::Affine3f &poseNext, float t);
+
+} // namespace simox::math
\ No newline at end of file
diff --git a/SimoxUtility/tests/algorithm/CMakeLists.txt b/SimoxUtility/tests/algorithm/CMakeLists.txt
index 6ec915c9b370c4725eb4a4d0aca3b4b5360ccb21..24614998a1993c6f9306581d40aea7f51d4b6afe 100644
--- a/SimoxUtility/tests/algorithm/CMakeLists.txt
+++ b/SimoxUtility/tests/algorithm/CMakeLists.txt
@@ -1,5 +1,6 @@
 
 ADD_SU_TEST( apply )
+ADD_SU_TEST( contains )
 ADD_SU_TEST( for_each_if )
 ADD_SU_TEST( fuzzy_find )
 ADD_SU_TEST( get_map_keys_values )
diff --git a/SimoxUtility/tests/algorithm/contains.cpp b/SimoxUtility/tests/algorithm/contains.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7c34cd83e6c2d3b6e4de38c8a99e7291d7b3ffe
--- /dev/null
+++ b/SimoxUtility/tests/algorithm/contains.cpp
@@ -0,0 +1,172 @@
+/**
+* @package    SimoxUtility
+* @author     Rainer Kartmann
+* @copyright  2021 Rainer Kartmann
+*/
+
+#define BOOST_TEST_MODULE SimoxUtility/algorithm/contains
+
+#include <boost/test/included/unit_test.hpp>
+
+#include <SimoxUtility/algorithm/contains.h>
+#include <SimoxUtility/algorithm/string/string_tools.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+
+namespace contains_test
+{
+    struct Fixture
+    {
+        const std::vector<std::string> string_vec { "one", "two", "three" };
+        const std::vector<int> int_vec { 0, -1, 10 };
+
+        const std::set<std::string> string_set { string_vec.begin(), string_vec.end() };
+        const std::set<int> int_set { int_vec.begin(), int_vec.end() };
+
+        const std::string string = "my/string";
+
+
+        std::map<std::string, int> m_si;
+        std::map<std::string, std::string> m_ss;
+        std::map<int, std::string> m_is;
+        std::map<int, int> m_ii;
+
+        std::unordered_map<std::string, int> um_si;
+        std::unordered_map<std::string, std::string> um_ss;
+        std::unordered_map<int, std::string> um_is;
+        std::unordered_map<int, int> um_ii;
+
+        Fixture()
+        {
+            fillMap(m_si);
+            fillMap(m_ss);
+            fillMap(m_is);
+            fillMap(m_ii);
+            fillMap(um_si);
+            fillMap(um_ss);
+            fillMap(um_is);
+            fillMap(um_ii);
+        }
+        ~Fixture()
+        {
+        }
+
+        template <class K, class V, template<class...> class MapT = std::map, class...Ts>
+        static MapT<K, V, Ts...> makeMap()
+        {
+            MapT<K, V, Ts...> map;
+            fillMap(map);
+            return map;
+        }
+        template <class K, class V, template<class...> class MapT = std::map, class...Ts>
+        static void fillMap(MapT<K, V, Ts...>& map)
+        {
+            for (int i = 1; i <= 3; ++i)
+            {
+                map[as<K>(i)] = as<V>(i);
+            }
+        }
+
+        template <class T>
+        static T as(int i)
+        {
+            if constexpr(std::is_same_v<T, int>)
+            {
+                return i;
+            }
+            else if constexpr(std::is_same_v<T, std::string>)
+            {
+                return std::to_string(i);
+            }
+        }
+    };
+}
+
+
+BOOST_FIXTURE_TEST_SUITE(contains_test, contains_test::Fixture)
+
+
+BOOST_AUTO_TEST_CASE(test_contains)
+{
+    BOOST_CHECK_EQUAL(simox::alg::contains(string_vec, "two"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(string_vec, "something"), false);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains(int_vec, -1), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(int_vec, 1000), false);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains(int_set, -1), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(int_set, 1000), false);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains(string_set, "two"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(string_set, "something"), false);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_string_contains)
+{
+    // Uses string_tools.h
+    // char
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, '/'), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, ':'), false);
+
+    // single-character string
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, "/"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, ":"), false);
+
+    // multi-character string
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, "my"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains(string, "your"), false);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_map_contains)
+{
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_si, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_ss, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_si, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_ss, "2"), true);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_si, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_ss, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_si, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_ss, "something"), false);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_is, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_ii, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_is, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_ii, 2), true);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_is, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(m_ii, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_is, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_key(um_ii, -1), false);
+
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_is, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_ss, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_is, "2"), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_ss, "2"), true);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_is, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_ss, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_is, "something"), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_ss, "something"), false);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_si, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_ii, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_si, 2), true);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_ii, 2), true);
+
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_si, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(m_ii, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_si, -1), false);
+    BOOST_CHECK_EQUAL(simox::alg::contains_value(um_ii, -1), false);
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/SimoxUtility/threads/CountingSemaphore.cpp b/SimoxUtility/threads/CountingSemaphore.cpp
index 9e6687e62c9900e841dc2f6b73dd9fe50b5b8812..2f6e616dda67661925905c85d95b47d2312975fb 100644
--- a/SimoxUtility/threads/CountingSemaphore.cpp
+++ b/SimoxUtility/threads/CountingSemaphore.cpp
@@ -7,14 +7,22 @@ namespace simox::threads
     CountingSemaphore::CountingSemaphore()
     {}
 
-    CountingSemaphore::CountingSemaphore(unsigned int count) : _count(count)
+    CountingSemaphore::CountingSemaphore(unsigned int count) :
+        _count(count)
+    {}
+
+    CountingSemaphore::CountingSemaphore(unsigned int count, unsigned int maxCount) :
+        _count(count), _maxCount(maxCount)
     {}
 
 
     void CountingSemaphore::notify()
     {
         std::lock_guard<std::mutex> lock(_mutex);
-        ++_count;
+        if (!_maxCount || _count < *_maxCount)
+        {
+            ++_count;
+        }
         _condition.notify_one();
     }
 
diff --git a/SimoxUtility/threads/CountingSemaphore.h b/SimoxUtility/threads/CountingSemaphore.h
index a7bc78f9059c7584b0f3cda781ca487e8d2ad726..e784b6b6737816148605bfb9bf7103c317e3f534 100644
--- a/SimoxUtility/threads/CountingSemaphore.h
+++ b/SimoxUtility/threads/CountingSemaphore.h
@@ -2,6 +2,7 @@
 
 #include <condition_variable>
 #include <mutex>
+#include <optional>
 
 
 namespace simox::threads
@@ -14,33 +15,52 @@ namespace simox::threads
      * Notifiying the semaphore increments the count and allows threads to enter.
      * A thread can wait until it may enter. When it enters, it decrements
      * the internal count.
+     * An optional max count can limit the value of count (useful e.g. when your
+     * buffer has a limited number of items).
      *
-     * Can be used e.g. in a Producer-Consumer pattern.
-     * The producer signals new jobs via `notify()`, while the consumer waits
+     * A counting semaphore can be used e.g. in Producer-Consumer patterns.
+     * The producer signals new jobs/items via `notify()`, while the consumer waits
      * for new jobs via `wait()`.
      */
     class CountingSemaphore
     {
     public:
 
-        /// Construct an initially blocking semaphore (initial count 0).
+        /// Construct an initially blocking semaphore (initial count 0) without max count.
         CountingSemaphore();
-        /// Construct a semaphore with the given count.
+        /**
+         * @brief Construct a semaphore with the given initial count without max count.
+         * @param count The initial count (0 to block initially).
+         */
         CountingSemaphore(unsigned int count);
+        /**
+         * @brief Construct a semaphore with the given initial count and max count.
+         * @param count The initial count (0 to block initially).
+         * @param maxCount An optional count limit (1 for a binary semaphore).
+         */
+        CountingSemaphore(unsigned int count, unsigned int maxCount);
 
         /**
          * @brief Signal that one waiting thread may continue.
+         *
          * Also known as `post()` or `signal()`.
+         * Increments the count by 1, if it is below the optional max count.
          */
         void notify();
 
         /**
          * @brief Wait until a thread may enter.
+         *
+         * Decrements the count when resuming.
          */
         void wait();
 
         /**
-         * @brief Try to enter. If the semaphore is currently blocking, return false.
+         * @brief Try to enter.
+         *
+         * If the semaphore is currently blocking, return false.
+         * If the semaphore is free (count > 0), decrement the count and return true.
+         *
          * @return True if entering was successful, false if semaphore was blocking.
          */
         bool try_wait();
@@ -56,6 +76,9 @@ namespace simox::threads
         /// The current count. Waiting threads may enter when > 0.
         unsigned int _count = 0;
 
+        /// An optional maximal count. All `notifiy()`s increasing the counter above maxCount are ignored.
+        std::optional<unsigned int> _maxCount = std::nullopt;
+
     };
 
 }