diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index f0cd86551..fd4d16fd0 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -13,16 +13,6 @@ if (SWIG_FOUND) include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PYTHON_INCLUDE_PATH}) - - set(swig_files - Angle - GaussMarkovProcess - PID - Rand - SemanticVersion - Vector2 - Vector3 - Vector4) endif() ################################# @@ -71,6 +61,7 @@ if (PYTHONLIBS_FOUND) # Add the Python tests set(python_tests Angle_TEST + Color_TEST GaussMarkovProcess_TEST Kmeans_TEST Line2_TEST @@ -78,14 +69,16 @@ if (PYTHONLIBS_FOUND) PID_TEST python_TEST Rand_TEST + RollingMean_TEST SemanticVersion_TEST SignalStats_TEST + Spline_TEST + Temperature_TEST + Triangle_TEST Vector2_TEST Vector3_TEST Vector3Stats_TEST Vector4_TEST - Temperature_TEST - Triangle_TEST ) foreach (test ${python_tests}) diff --git a/src/python/Color.i b/src/python/Color.i new file mode 100644 index 000000000..d916614e2 --- /dev/null +++ b/src/python/Color.i @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +%module Color +%{ +#include +#include +#include +#include +%} + +%include "std_string.i" + +namespace ignition +{ + namespace math + { + class Color + { + %rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) ""; + %rename("%(uppercase)s", %$isstatic, %$isvariable) ""; + public: static const Color White; + public: static const Color Black; + public: static const Color Red; + public: static const Color Green; + public: static const Color Blue; + public: static const Color Yellow; + public: static const Color Magenta; + public: static const Color Cyan; + public: typedef unsigned int RGBA; + public: typedef unsigned int BGRA; + public: typedef unsigned int ARGB; + public: typedef unsigned int ABGR; + + public: Color(); + public: Color(const float _r, const float _g, const float _b, + const float _a = 1.0); + public: Color(const Color &_clr); + public: virtual ~Color(); + public: void Reset(); + public: void Set(const float _r = 1, const float _g = 1, + const float _b = 1, const float _a = 1); + public: Vector3 HSV() const; + public: void SetFromHSV(const float _h, const float _s, const float _v); + public: Vector3 YUV() const; + public: void SetFromYUV(const float _y, const float _u, const float _v); + public: RGBA AsRGBA() const; + public: BGRA AsBGRA() const; + public: ARGB AsARGB() const; + public: ABGR AsABGR() const; + public: void SetFromRGBA(const RGBA _v); + public: void SetFromBGRA(const BGRA _v); + public: void SetFromARGB(const ARGB _v); + public: void SetFromABGR(const ABGR _v); + public: Color operator+(const Color &_pt) const; + public: Color operator+(const float &_v) const; + public: const Color &operator+=(const Color &_pt); + public: Color operator-(const Color &_pt) const; + public: Color operator-(const float &_v) const; + public: const Color &operator-=(const Color &_pt); + public: const Color operator/(const Color &_pt) const; + public: const Color operator/(const float &_v) const; + public: const Color &operator/=(const Color &_pt); + public: const Color operator*(const Color &_pt) const; + public: const Color operator*(const float &_v) const; + public: const Color &operator*=(const Color &_pt); + public: bool operator==(const Color &_pt) const; + public: bool operator!=(const Color &_pt) const; + private: void Clamp(); + public: float R() const; + public: float G() const; + public: float B() const; + public: float A() const; + public: void R(const float _r); + public: void G(const float _g); + public: void B(const float _b); + public: void A(const float _a); + }; + + %extend Color{ + float __getitem__(const unsigned int i) + { + return (*$self)[i]; + } + } + + %extend Color { + std::string __str__() const { + std::ostringstream out; + out << *$self; + return out.str(); + } + } + } +} diff --git a/src/python/Color_TEST.py b/src/python/Color_TEST.py new file mode 100644 index 000000000..6a433b846 --- /dev/null +++ b/src/python/Color_TEST.py @@ -0,0 +1,334 @@ +# Copyright (C) 2021 Open Source Robotics Foundation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import unittest +from ignition.math import Color +from ignition.math import Vector3f + + +class TestColor(unittest.TestCase): + + def test_const_color(self): + self.assertAlmostEqual(1.0, Color.WHITE.r()) + self.assertAlmostEqual(1.0, Color.WHITE.g()) + self.assertAlmostEqual(1.0, Color.WHITE.b()) + self.assertAlmostEqual(1.0, Color.WHITE.a()) + + self.assertAlmostEqual(0.0, Color.BLACK.r()) + self.assertAlmostEqual(0.0, Color.BLACK.g()) + self.assertAlmostEqual(0.0, Color.BLACK.b()) + self.assertAlmostEqual(1.0, Color.BLACK.a()) + + self.assertAlmostEqual(1.0, Color.RED.r()) + self.assertAlmostEqual(0.0, Color.RED.g()) + self.assertAlmostEqual(0.0, Color.RED.b()) + self.assertAlmostEqual(1.0, Color.RED.a()) + + self.assertAlmostEqual(0.0, Color.GREEN.r()) + self.assertAlmostEqual(1.0, Color.GREEN.g()) + self.assertAlmostEqual(0.0, Color.GREEN.b()) + self.assertAlmostEqual(1.0, Color.GREEN.a()) + + self.assertAlmostEqual(0.0, Color.BLUE.r()) + self.assertAlmostEqual(0.0, Color.BLUE.g()) + self.assertAlmostEqual(1.0, Color.BLUE.b()) + self.assertAlmostEqual(1.0, Color.BLUE.a()) + + self.assertAlmostEqual(1.0, Color.YELLOW.r()) + self.assertAlmostEqual(1.0, Color.YELLOW.g()) + self.assertAlmostEqual(0.0, Color.YELLOW.b()) + self.assertAlmostEqual(1.0, Color.YELLOW.a()) + + self.assertAlmostEqual(1.0, Color.MAGENTA.r()) + self.assertAlmostEqual(0.0, Color.MAGENTA.g()) + self.assertAlmostEqual(1.0, Color.MAGENTA.b()) + self.assertAlmostEqual(1.0, Color.MAGENTA.a()) + + self.assertAlmostEqual(0.0, Color.CYAN.r()) + self.assertAlmostEqual(1.0, Color.CYAN.g()) + self.assertAlmostEqual(1.0, Color.CYAN.b()) + self.assertAlmostEqual(1.0, Color.CYAN.a()) + + def test_color(self): + clr0 = Color() + self.assertAlmostEqual(0.0, clr0.r()) + self.assertAlmostEqual(0.0, clr0.g()) + self.assertAlmostEqual(0.0, clr0.b()) + self.assertAlmostEqual(1.0, clr0.a()) + self.assertEqual(clr0.as_rgba(), 255) + clr0.a(0.0) + self.assertEqual(clr0.as_rgba(), 0) + + clr = Color(.1, .2, .3, 1.0) + self.assertAlmostEqual(0.1, clr.r()) + self.assertAlmostEqual(0.2, clr.g()) + self.assertAlmostEqual(0.3, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set(1, 0, 0, 0) + self.assertAlmostEqual(clr.as_rgba(), 255 << 24) + self.assertAlmostEqual(clr.as_bgra(), 255 << 8) + self.assertAlmostEqual(clr.as_argb(), 255 << 16) + self.assertAlmostEqual(clr.as_abgr(), 255) + clr0.set_from_rgba(255 << 24) + self.assertEqual(clr0, clr) + clr0.set_from_bgra(255 << 8) + self.assertEqual(clr0, clr) + clr0.set_from_argb(255 << 16) + self.assertEqual(clr0, clr) + clr0.set_from_abgr(255) + self.assertEqual(clr0, clr) + + clr.set(0, 1, 0, 0) + self.assertAlmostEqual(clr.as_rgba(), 255 << 16) + self.assertAlmostEqual(clr.as_bgra(), 255 << 16) + self.assertAlmostEqual(clr.as_argb(), 255 << 8) + self.assertAlmostEqual(clr.as_abgr(), 255 << 8) + clr0.set_from_rgba(255 << 16) + self.assertEqual(clr0, clr) + clr0.set_from_bgra(255 << 16) + self.assertEqual(clr0, clr) + clr0.set_from_argb(255 << 8) + self.assertEqual(clr0, clr) + clr0.set_from_abgr(255 << 8) + self.assertEqual(clr0, clr) + + clr.set(0, 0, 1, 0) + self.assertAlmostEqual(clr.as_rgba(), 255 << 8) + self.assertAlmostEqual(clr.as_bgra(), 255 << 24) + self.assertAlmostEqual(clr.as_argb(), 255) + self.assertAlmostEqual(clr.as_abgr(), 255 << 16) + clr0.set_from_rgba(255 << 8) + self.assertEqual(clr0, clr) + clr0.set_from_bgra(255 << 24) + self.assertEqual(clr0, clr) + clr0.set_from_argb(255) + self.assertEqual(clr0, clr) + clr0.set_from_abgr(255 << 16) + self.assertEqual(clr0, clr) + + clr.set(0, 0, 0, 1) + self.assertAlmostEqual(clr.as_rgba(), 255) + self.assertAlmostEqual(clr.as_bgra(), 255) + self.assertAlmostEqual(clr.as_argb(), 255 << 24) + self.assertAlmostEqual(clr.as_abgr(), 255 << 24) + clr0.set_from_rgba(255) + self.assertEqual(clr0, clr) + clr0.set_from_bgra(255) + self.assertEqual(clr0, clr) + clr0.set_from_argb(255 << 24) + self.assertEqual(clr0, clr) + clr0.set_from_abgr(255 << 24) + self.assertEqual(clr0, clr) + + clr.reset() + self.assertAlmostEqual(0.0, clr.r()) + self.assertAlmostEqual(0.0, clr.g()) + self.assertAlmostEqual(0.0, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set_from_hsv(0, 0.5, 1.0) + self.assertAlmostEqual(1.0, clr.r()) + self.assertAlmostEqual(0.5, clr.g()) + self.assertAlmostEqual(0.5, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + self.assertTrue(clr.hsv() == Vector3f(6, 0.5, 1)) + + clr.set_from_hsv(60, 0.0, 1.0) + self.assertAlmostEqual(1.0, clr.r()) + self.assertAlmostEqual(1.0, clr.g()) + self.assertAlmostEqual(1.0, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set_from_hsv(120, 0.5, 1.0) + self.assertAlmostEqual(0.5, clr.r()) + self.assertAlmostEqual(1.0, clr.g()) + self.assertAlmostEqual(0.5, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set_from_hsv(180, 0.5, 1.0) + self.assertAlmostEqual(0.5, clr.r()) + self.assertAlmostEqual(1.0, clr.g()) + self.assertAlmostEqual(1.0, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set_from_hsv(240, 0.5, 1.0) + self.assertAlmostEqual(0.5, clr.r()) + self.assertAlmostEqual(0.5, clr.g()) + self.assertAlmostEqual(1.0, clr.b()) + self.assertAlmostEqual(1.0, clr.a()) + + clr.set_from_hsv(300, 0.5, 1.0) + self.assertAlmostEqual(1.0, clr[0]) + self.assertAlmostEqual(0.5, clr[1]) + self.assertAlmostEqual(1.0, clr[2]) + self.assertAlmostEqual(1.0, clr[3]) + self.assertTrue(math.isnan(clr[4])) + + clr.set(0.1, 0.2, 0.3, 0.4) + clr = clr + 0.2 + self.assertTrue(clr == Color(0.3, 0.4, 0.5, 0.6)) + + clr.set(0.1, 0.2, 0.3, 0.4) + clr += Color(0.2, 0.2, 0.2, 0.2) + self.assertTrue(clr == Color(0.3, 0.4, 0.5, 0.6)) + + clr.set(0.1, 0.2, 0.3, 0.4) + clr = clr - 0.1 + self.assertTrue(clr == Color(0.0, 0.1, 0.2, 0.3)) + + clr.set(0.1, 0.2, 0.3, 0.4) + clr -= Color(0.1, 0.1, 0.1, 0.1) + self.assertTrue(clr == Color(0.0, 0.1, 0.2, 0.3)) + + clr.set(1.0, 1.0, 1.0, 1.0) + clr = clr / 1.6 + self.assertTrue(clr == Color(0.625, 0.625, 0.625, 0.625)) + + clr.set(1.0, 1.0, 1.0, 1.0) + clr /= Color(1.0, 1.0, 1.0, 1.0) + self.assertTrue(clr == Color(1.0, 1.0, 1.0, 1.0)) + + clr.set(.1, .2, .3, .4) + clr = clr * .1 + self.assertTrue(clr == Color(0.01, 0.02, 0.03, 0.04)) + + clr.set(.1, .2, .3, .4) + clr *= Color(0.1, 0.1, 0.1, 0.1) + self.assertTrue(clr == Color(0.01, 0.02, 0.03, 0.04)) + + clr.set_from_yuv(0.5, 0.2, 0.8) + self.assertAlmostEqual(0.00553, clr.r(), delta=1e-3) + self.assertAlmostEqual(0.0, clr.g()) + self.assertAlmostEqual(0.9064, clr.b(), delta=1e-3) + self.assertAlmostEqual(0.04, clr.a()) + + self.assertTrue(clr.yuv() == Vector3f(0.104985, 0.95227, 0.429305)) + + clr = Color(1.0, 0.0, 0.5, 1.0) + Color(0.1, 0.3, 0.4, 1.0) + self.assertAlmostEqual(0.00431373, clr.r(), delta=1e-4) + self.assertAlmostEqual(0.3, clr.g(), delta=1e-4) + self.assertAlmostEqual(0.9, clr.b(), delta=1e-4) + self.assertAlmostEqual(1.0, clr.a(), delta=1e-4) + + clr = Color(1.0, 0.0, 0.5, 1.0) - Color(0.1, 0.3, 0.4, 1.0) + self.assertAlmostEqual(0.9, clr.r(), delta=1e-4) + self.assertAlmostEqual(0.0, clr.g(), delta=1e-4) + self.assertAlmostEqual(0.1, clr.b(), delta=1e-4) + self.assertAlmostEqual(0.0, clr.a(), delta=1e-4) + + clr = Color(0.5, 0.2, 0.4, 0.6) / 2.0 + self.assertAlmostEqual(0.25, clr.r(), delta=1e-4) + self.assertAlmostEqual(0.1, clr.g(), delta=1e-4) + self.assertAlmostEqual(0.2, clr.b(), delta=1e-4) + self.assertAlmostEqual(0.3, clr.a(), delta=1e-4) + + def test_mul(self): + clr = Color(0.0, 0.01, 0.2, 1.0) + clr2 = Color(1.0, 0.2, 0.2, 0.0) + clr3 = clr * clr2 + + self.assertAlmostEqual(clr3.r(), 0.0) + self.assertAlmostEqual(clr3.g(), 0.002) + self.assertAlmostEqual(clr3.b(), 0.04) + self.assertAlmostEqual(clr3.a(), 0.0) + + def test_division(self): + clr = Color(0.0, 0.01, 0.2, 1.0) + clr2 = clr / 0.2 + self.assertAlmostEqual(clr2.r(), 0.0) + self.assertAlmostEqual(clr2.g(), 0.05) + self.assertAlmostEqual(clr2.b(), 1.0) + self.assertAlmostEqual(clr2.a(), 1.0) + + clr2 = clr / 2.0 + self.assertAlmostEqual(clr2.r(), 0.0) + self.assertAlmostEqual(clr2.g(), 0.005) + self.assertAlmostEqual(clr2.b(), 0.1) + self.assertAlmostEqual(clr2.a(), 0.5) + + clr2.set(0.0, 0.2, 0.4, 0.5) + clr3 = clr / clr2 + self.assertAlmostEqual(clr3.r(), 0.0) + self.assertAlmostEqual(clr3.g(), 0.05) + self.assertAlmostEqual(clr3.b(), 0.5) + self.assertAlmostEqual(clr3.a(), 1.0) + + clr.set(0.0, 0.0, 0.0, 0.0) + clr2.set(0.0, 0.0, 0.0, 0.0) + clr3 = clr / clr2 + self.assertAlmostEqual(clr3.r(), 0.0) + self.assertAlmostEqual(clr3.g(), 0.0) + self.assertAlmostEqual(clr3.b(), 0.0) + self.assertAlmostEqual(clr3.a(), 0.0) + + def test_const_set(self): + clr = Color(0.1, 0.2, 0.3, 0.4) + self.assertAlmostEqual(clr.r(), 0.1) + self.assertAlmostEqual(clr.g(), 0.2) + self.assertAlmostEqual(clr.b(), 0.3) + self.assertAlmostEqual(clr.a(), 0.4) + + clr2 = Color() + clr2.r(0.4) + clr2.g(0.3) + clr2.b(0.2) + clr2.a(0.1) + self.assertAlmostEqual(clr2.r(), 0.4) + self.assertAlmostEqual(clr2.g(), 0.3) + self.assertAlmostEqual(clr2.b(), 0.2) + self.assertAlmostEqual(clr2.a(), 0.1) + + self.assertTrue(clr2 != clr) + + def test_stream_out(self): + c = Color(0.1, 0.2, 0.3, 0.5) + self.assertAlmostEqual(str(c), "0.1 0.2 0.3 0.5") + + def test_HSV(self): + clr = Color() + hsv = clr.hsv() + self.assertAlmostEqual(hsv.x(), -1.0) + self.assertAlmostEqual(hsv.y(), 0.0) + self.assertAlmostEqual(hsv.z(), 0.0) + + clr.set(0.1, 0.2, 0.3, 1.0) + hsv = clr.hsv() + self.assertAlmostEqual(hsv.x(), 3.5, delta=1e-3) + self.assertAlmostEqual(hsv.y(), 0.666667, delta=1e-3) + self.assertAlmostEqual(hsv.z(), 0.3, delta=1e-3) + + clr.set(0.3, 0.2, 0.1, 1.0) + hsv = clr.hsv() + self.assertAlmostEqual(hsv.x(), 0.5, delta=1e-3) + self.assertAlmostEqual(hsv.y(), 0.666667, delta=1e-3) + self.assertAlmostEqual(hsv.z(), 0.3, delta=1e-3) + + clr.set_from_hsv(60, 10, 5) + self.assertAlmostEqual(clr.r(), 0.0196078, delta=1e-3) + self.assertAlmostEqual(clr.g(), 0.0196078, delta=1e-3) + self.assertAlmostEqual(clr.b(), 0.0, delta=1e-3) + self.assertAlmostEqual(clr.a(), 1.0, delta=1e-3) + + clr.set_from_hsv(360.0, 0.5, 0.6) + self.assertAlmostEqual(clr.r(), 0.6, delta=1e-3) + self.assertAlmostEqual(clr.g(), 0.3, delta=1e-3) + self.assertAlmostEqual(clr.b(), 0.3, delta=1e-3) + self.assertAlmostEqual(clr.a(), 1.0, delta=1e-3) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/RollingMean.i b/src/python/RollingMean.i new file mode 100644 index 000000000..be96792d4 --- /dev/null +++ b/src/python/RollingMean.i @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +%module rollingMean +%{ +#include +%} + +namespace ignition +{ + namespace math + { + class RollingMean + { + %rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) ""; + public: explicit RollingMean(size_t _windowSize = 10); + public: ~RollingMean(); + public: double Mean() const; + public: size_t Count() const; + public: void Push(double _value); + public: void Clear(); + public: void SetWindowSize(size_t _windowSize); + public: size_t WindowSize() const; + }; + } +} diff --git a/src/python/RollingMean_TEST.py b/src/python/RollingMean_TEST.py new file mode 100644 index 000000000..90c7044f2 --- /dev/null +++ b/src/python/RollingMean_TEST.py @@ -0,0 +1,55 @@ +# Copyright (C) 2021 Open Source Robotics Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import unittest +from ignition.math import RollingMean + + +class TestRollingMean(unittest.TestCase): + + def test_rolling_mean(self): + mean = RollingMean() + self.assertEqual(0, mean.count()) + self.assertEqual(10, mean.window_size()) + + mean.set_window_size(4) + self.assertEqual(4, mean.window_size()) + mean.set_window_size(0) + self.assertEqual(4, mean.window_size()) + + mean.push(1.0) + self.assertAlmostEqual(1.0, mean.mean()) + mean.push(2.0) + self.assertAlmostEqual(1.5, mean.mean()) + mean.push(3.0) + self.assertAlmostEqual(2.0, mean.mean()) + mean.push(10.0) + self.assertAlmostEqual(4.0, mean.mean()) + mean.push(20.0) + self.assertAlmostEqual(8.75, mean.mean()) + + mean.clear() + self.assertTrue(math.isnan(mean.mean())) + + mean.push(100.0) + mean.push(200.0) + mean.push(300.0) + self.assertEqual(3, mean.count()) + mean.set_window_size(2) + self.assertEqual(0, mean.count()) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/Spline.i b/src/python/Spline.i new file mode 100644 index 000000000..cf086a577 --- /dev/null +++ b/src/python/Spline.i @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +%module spline +%{ +#include +#include +#include +#include +%} + +namespace ignition +{ + namespace math + { + class ControlPoint; + + class Spline + { + %rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) ""; + public: Spline(); + public: ~Spline(); + public: void Tension(double _t); + public: double Tension() const; + public: double ArcLength() const; + public: double ArcLength(const double _t) const; + public: double ArcLength(const unsigned int _index, + const double _t) const; + public: void AddPoint(const Vector3 &_p); + public: void AddPoint(const Vector3 &_p, const Vector3 &_t); + private: void AddPoint(const ControlPoint &_cp, const bool _fixed); + public: Vector3 Point(const unsigned int _index) const; + public: Vector3 Tangent(const unsigned int _index) const; + public: Vector3 MthDerivative(const unsigned int _index, + const unsigned int _mth) const; + public: size_t PointCount() const; + public: void Clear(); + public: bool UpdatePoint(const unsigned int _index, + const Vector3 &_p); + public: bool UpdatePoint(const unsigned int _index, + const Vector3 &_p, + const Vector3 &_t); + private: bool UpdatePoint(const unsigned int _index, + const ControlPoint &_cp, + const bool _fixed); + public: Vector3 Interpolate(const double _t) const; + public: Vector3 Interpolate(const unsigned int _fromIndex, + const double _t) const; + public: Vector3 InterpolateTangent(const double _t) const; + public: Vector3 InterpolateTangent(const unsigned int _fromIndex, + const double _t) const; + public: Vector3 InterpolateMthDerivative(const unsigned int _mth, + const double _1) const; + public: Vector3 InterpolateMthDerivative(const unsigned int _fromIndex, + const unsigned int _mth, + const double _s) const; + public: void AutoCalculate(bool _autoCalc); + public: void RecalcTangents(); + }; + } +} diff --git a/src/python/Spline_TEST.py b/src/python/Spline_TEST.py new file mode 100644 index 000000000..a043ed5ac --- /dev/null +++ b/src/python/Spline_TEST.py @@ -0,0 +1,166 @@ +# Copyright (C) 2021 Open Source Robotics Foundation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import unittest +from ignition.math import Spline +from ignition.math import Vector3d + + +class TestSpline(unittest.TestCase): + + def test_spline(self): + s = Spline() + + s.add_point(Vector3d(0, 0, 0)) + self.assertEqual(1, s.point_count()) + + s.clear() + self.assertEqual(0, s.point_count()) + + s.add_point(Vector3d(0, 0, 0)) + self.assertTrue(s.point(0) == Vector3d(0, 0, 0)) + s.add_point(Vector3d(1, 1, 1)) + self.assertTrue(s.point(1) == Vector3d(1, 1, 1)) + + # update_point + self.assertFalse(s.update_point(2, Vector3d(2, 2, 2))) + + self.assertTrue(s.update_point(1, Vector3d(2, 2, 2))) + s.auto_calculate(False) + self.assertTrue(s.update_point(0, Vector3d(-1, -1, -1))) + s.auto_calculate(True) + + # interpolate + self.assertAlmostEqual(s.interpolate(0.0), Vector3d(-1, -1, -1)) + self.assertAlmostEqual(s.interpolate(0.5), Vector3d(0.5, 0.5, 0.5)) + self.assertAlmostEqual(s.interpolate(1.0), Vector3d(2, 2, 2)) + + # interpolate + s.add_point(Vector3d(4, 4, 4)) + self.assertAlmostEqual(s.interpolate(1, 0.2), + Vector3d(2.496, 2.496, 2.496)) + + def test_fixed_tangent(self): + s = Spline() + + # add_point + s.auto_calculate(False) + s.add_point(Vector3d(0, 0, 0)) + s.add_point(Vector3d(0, 0.5, 0), Vector3d(0, 1, 0)) + s.add_point(Vector3d(0.5, 1, 0), Vector3d(1, 0, 0)) + s.add_point(Vector3d(1, 1, 0), Vector3d(1, 0, 0)) + + # update_point + s.update_point(0, Vector3d(0, 0, 0), Vector3d(0, 1, 0)) + + s.auto_calculate(True) + + s.recalc_tangents() + + # interpolate + self.assertAlmostEqual(s.interpolate(0, 0.5), Vector3d(0, 0.25, 0)) + self.assertAlmostEqual(s.interpolate_tangent(0, 0.5), + Vector3d(0, 0.25, 0)) + self.assertAlmostEqual(s.interpolate(1, 0.5), + Vector3d(0.125, 0.875, 0)) + self.assertAlmostEqual(s.interpolate(2, 0.5), Vector3d(0.75, 1, 0)) + self.assertAlmostEqual(s.interpolate_tangent(2, 0.5), + Vector3d(0.25, 0, 0)) + + def test_arc_length(self): + s = Spline() + self.assertFalse(math.isfinite(s.arc_length())) + s.add_point(Vector3d(1, 1, 1), Vector3d(1, 1, 1)) + self.assertFalse(math.isfinite(s.arc_length(0))) + s.add_point(Vector3d(3, 3, 3), Vector3d(1, 1, 1)) + s.add_point(Vector3d(4, 4, 4), Vector3d(1, 1, 1)) + self.assertAlmostEqual(s.arc_length(0, 1.0), + 3.46410161513775, delta=1e-14) + self.assertAlmostEqual(s.arc_length(), 5.19615242270663, delta=1e-14) + self.assertAlmostEqual(s.arc_length(), s.arc_length(1.0)) + self.assertFalse(math.isfinite(s.arc_length(-1.0))) + self.assertFalse(math.isfinite(s.arc_length(4, 0.0))) + + def test_tension(self): + s = Spline() + + s.tension(0.1) + self.assertAlmostEqual(s.tension(), 0.1) + + def test_interpolate(self): + s = Spline() + self.assertFalse(s.interpolate(0, 0.1).is_finite()) + + s.add_point(Vector3d(0, 0, 0)) + self.assertAlmostEqual(s.interpolate(0, 0.1), Vector3d(0, 0, 0)) + self.assertFalse(s.interpolate_tangent(0.1).is_finite()) + + s.add_point(Vector3d(1, 2, 3)) + self.assertAlmostEqual(s.interpolate(0, 0.0), s.point(0)) + self.assertFalse(s.interpolate(0, -0.1).is_finite()) + self.assertAlmostEqual(s.interpolate_tangent(0, 0.0), s.tangent(0)) + + # Fast and slow call variations + self.assertAlmostEqual(s.interpolate(0, 0.5), Vector3d(0.5, 1.0, 1.5)) + self.assertAlmostEqual(s.interpolate(0, 1.0), s.point(1)) + self.assertAlmostEqual(s.interpolate_tangent(0, 0.5), + Vector3d(1.25, 2.5, 3.75)) + self.assertAlmostEqual(s.interpolate_tangent(0, 1.0), s.tangent(1)) + self.assertAlmostEqual(s.interpolate_mth_derivative(2, 0.5), + Vector3d(0, 0, 0)) + self.assertAlmostEqual(s.interpolate_mth_derivative(2, 1.0), + Vector3d(-3, -6, -9)) + self.assertAlmostEqual(s.interpolate_mth_derivative(3, 0.5), + Vector3d(-6, -12, -18)) + self.assertAlmostEqual(s.interpolate_mth_derivative(3, 1.0), + Vector3d(-6, -12, -18)) + self.assertAlmostEqual(s.interpolate_mth_derivative(4, 0.5), + Vector3d(0, 0, 0)) + self.assertAlmostEqual(s.interpolate_mth_derivative(4, 1.0), + Vector3d(0, 0, 0)) + + def test_point(self): + s = Spline() + self.assertFalse(s.point(0).is_finite()) + + def test_tangent(self): + s = Spline() + self.assertFalse(s.tangent(0).is_finite()) + + s.add_point(Vector3d(0, 0, 0)) + self.assertFalse(s.tangent(0).is_finite()) + + s.add_point(Vector3d(1, 0, 0)) + self.assertAlmostEqual(s.tangent(0), Vector3d(0.5, 0, 0)) + + s.add_point(Vector3d(1, 1, 0), Vector3d(-1, 1, 0)) + self.assertAlmostEqual(s.tangent(1), Vector3d(0.5, 0.5, 0)) + self.assertAlmostEqual(s.tangent(2), Vector3d(-1, 1, 0)) + + def test_recalc_tangents(self): + s = Spline() + + s.add_point(Vector3d(0, 0, 0)) + s.add_point(Vector3d(.4, .4, .4)) + s.add_point(Vector3d(0, 0, 0)) + + s.recalc_tangents() + + self.assertAlmostEqual(s.interpolate(0, 0.5), Vector3d(0.2, 0.2, 0.2)) + self.assertAlmostEqual(s.interpolate(1, 0.5), Vector3d(0.2, 0.2, 0.2)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/python.i b/src/python/python.i index 9d89fe8fd..ae23a58e6 100644 --- a/src/python/python.i +++ b/src/python/python.i @@ -5,11 +5,14 @@ %include Vector2.i %include Vector3.i %include Vector4.i +%include Color.i %include Line2.i %include Line3.i %include PID.i +%include RollingMean.i %include SemanticVersion.i %include SignalStats.i +%include Spline.i %include Temperature.i %include Triangle.i %include Kmeans.i