diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d579b9a074657960670aa4eedd0dcf8c2bc73fed..e9fe349ea93f5abb4e30e39fbdcea9ee1dc81754 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,7 @@ stages: artifacts: paths: - report.xml + - htmlcov expire_in: 1 day script: - ./ci/test.py diff --git a/ci/test.py b/ci/test.py index c6d90a39d599fc668a6a1ed525c2a940ddeeec4f..cce250734d5cf4fa9724c3155a4714ea22c129e4 100755 --- a/ci/test.py +++ b/ci/test.py @@ -34,7 +34,7 @@ if __name__ == "__main__": "--prompt", "ci", ".venv", - ], + ] ) check_call( @@ -44,9 +44,16 @@ if __name__ == "__main__": "pip", "install", str(sorted(Path("dist").glob("*.whl"))[-1].resolve()), - ], + ] ) check_call( - [python_venv_executable(), "-m", "pytest", "--junitxml=report.xml"], + [ + python_venv_executable(), + "-m", + "pytest", + "--junitxml=report.xml", + "--cov=src", + "--cov-report=html", + ] ) diff --git a/examples/example_basic.ipynb b/examples/example_basic.ipynb index 5445fe80256a9ee78bc0ae8e3c439135750b41b8..e737a4c023bb58873e9331b73d68a8af40ad8fda 100644 --- a/examples/example_basic.ipynb +++ b/examples/example_basic.ipynb @@ -14,155 +14,160 @@ "['fish_1', 'fish_2', 'fish_3', 'obstacle_1', 'robot']\n", "\n", "All poses\n", - "[[[8.86584103e-01 2.35670820e-01 5.41754842e-01 4.49850202e-01]\n", - " [8.15511882e-01 4.78223324e-01 6.29803419e-01 1.12592392e-01]\n", - " [1.53732300e-01 7.24954247e-01 9.38574493e-01 4.65665817e-01]\n", - " [9.10354614e-01 4.47880208e-01 3.81429136e-01 9.67544317e-01]\n", - " [6.07822955e-01 5.20158827e-01 8.17965686e-01 8.42760384e-01]]\n", + "[[[5.56598067e-01 2.42021829e-01 2.45265663e-01 2.95294344e-01]\n", + " [9.83367860e-01 4.36241180e-01 5.68063319e-01 8.96367550e-01]\n", + " [8.71030211e-01 9.32705551e-02 8.51928890e-01 9.48658109e-01]\n", + " ...\n", + " [6.94550753e-01 6.52826130e-01 6.08456850e-01 4.88614887e-01]\n", + " [4.84500438e-01 1.14949882e-01 6.08556986e-01 7.93362781e-02]\n", + " [9.54909027e-01 3.18913072e-01 4.58294243e-01 7.45387852e-01]]\n", "\n", - " [[2.29353935e-01 8.80753636e-01 7.94585168e-01 2.22074524e-01]\n", - " [6.13970399e-01 1.33511815e-02 2.89155185e-01 2.65219092e-01]\n", - " [6.62197351e-01 6.47982001e-01 9.46004018e-02 6.59599364e-01]\n", - " [4.86104101e-01 4.23153102e-01 1.39821902e-01 3.11809748e-01]\n", - " [8.03322852e-01 9.52799857e-01 3.89638603e-01 6.43237352e-01]]\n", + " [[7.51943365e-02 9.95502114e-01 5.04003823e-01 8.36720586e-01]\n", + " [8.18848014e-01 4.04324770e-01 5.49858093e-01 3.51742476e-01]\n", + " [1.66903093e-01 1.78061739e-01 2.81622916e-01 8.88221264e-01]\n", + " ...\n", + " [7.69020915e-01 6.33118331e-01 4.15713340e-01 4.24170971e-01]\n", + " [7.64205098e-01 7.78579533e-01 7.44598091e-01 3.30398619e-01]\n", + " [4.45223182e-01 9.25011218e-01 2.36187894e-02 7.62242600e-02]]\n", "\n", - " [[9.70978260e-01 6.75936878e-01 6.23196602e-01 8.42264950e-01]\n", - " [4.07079160e-01 8.46290290e-01 5.64092159e-01 3.56871307e-01]\n", - " [4.84096229e-01 8.60232174e-01 1.39015794e-01 7.82253265e-01]\n", - " [1.24170482e-01 2.21511930e-01 8.88282284e-02 4.53450561e-01]\n", - " [1.28404438e-01 2.87771430e-02 4.57022637e-01 9.80571806e-01]]\n", + " [[7.73669600e-01 3.54849041e-01 4.21867281e-01 9.16552365e-01]\n", + " [4.73650604e-01 1.79305673e-01 8.38760436e-01 3.96051705e-01]\n", + " [2.01547332e-02 8.12301695e-01 2.78097481e-01 8.67732406e-01]\n", + " ...\n", + " [8.12627971e-01 6.28660858e-01 2.13196307e-01 6.49513781e-01]\n", + " [5.58035910e-01 4.63277161e-01 8.21570277e-01 6.79726541e-01]\n", + " [7.10023165e-01 5.45146585e-01 8.51007760e-01 9.56029415e-01]]\n", "\n", - " [[5.00000000e+01 5.00000000e+01 0.00000000e+00 0.00000000e+00]\n", + " [[5.00000000e+01 5.00000000e+01 1.00000000e+00 0.00000000e+00]\n", " [ nan nan nan nan]\n", " [ nan nan nan nan]\n", + " ...\n", + " [ nan nan nan nan]\n", " [ nan nan nan nan]\n", " [ nan nan nan nan]]\n", "\n", - " [[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]\n", - " [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]\n", - " [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]\n", - " [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]\n", - " [ nan nan nan nan]]]\n", + " [[5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]\n", + " [5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]\n", + " [5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]\n", + " ...\n", + " [5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]\n", + " [5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]\n", + " [5.00000000e+01 5.00000000e+01 5.00000000e+01 5.00000000e+01]]]\n", "\n", "Fish poses\n", - "[[[0.8865841 0.23567082 0.54175484 0.4498502 ]\n", - " [0.81551188 0.47822332 0.62980342 0.11259239]\n", - " [0.1537323 0.72495425 0.93857449 0.46566582]\n", - " [0.91035461 0.44788021 0.38142914 0.96754432]\n", - " [0.60782295 0.52015883 0.81796569 0.84276038]]\n", + "[[[0.55659807 0.24202183 0.24526566 0.29529434]\n", + " [0.98336786 0.43624118 0.56806332 0.89636755]\n", + " [0.87103021 0.09327056 0.85192889 0.94865811]\n", + " ...\n", + " [0.69455075 0.65282613 0.60845685 0.48861489]\n", + " [0.48450044 0.11494988 0.60855699 0.07933628]\n", + " [0.95490903 0.31891307 0.45829424 0.74538785]]\n", "\n", - " [[0.22935393 0.88075364 0.79458517 0.22207452]\n", - " [0.6139704 0.01335118 0.28915519 0.26521909]\n", - " [0.66219735 0.647982 0.0946004 0.65959936]\n", - " [0.4861041 0.4231531 0.1398219 0.31180975]\n", - " [0.80332285 0.95279986 0.3896386 0.64323735]]\n", + " [[0.07519434 0.99550211 0.50400382 0.83672059]\n", + " [0.81884801 0.40432477 0.54985809 0.35174248]\n", + " [0.16690309 0.17806174 0.28162292 0.88822126]\n", + " ...\n", + " [0.76902092 0.63311833 0.41571334 0.42417097]\n", + " [0.7642051 0.77857953 0.74459809 0.33039862]\n", + " [0.44522318 0.92501122 0.02361879 0.07622426]]\n", "\n", - " [[0.97097826 0.67593688 0.6231966 0.84226495]\n", - " [0.40707916 0.84629029 0.56409216 0.35687131]\n", - " [0.48409623 0.86023217 0.13901579 0.78225327]\n", - " [0.12417048 0.22151193 0.08882823 0.45345056]\n", - " [0.12840444 0.02877714 0.45702264 0.98057181]]]\n", + " [[0.7736696 0.35484904 0.42186728 0.91655236]\n", + " [0.4736506 0.17930567 0.83876044 0.3960517 ]\n", + " [0.02015473 0.8123017 0.27809748 0.86773241]\n", + " ...\n", + " [0.81262797 0.62866086 0.21319631 0.64951378]\n", + " [0.55803591 0.46327716 0.82157028 0.67972654]\n", + " [0.71002316 0.54514658 0.85100776 0.95602942]]]\n", "\n", "File structure\n", - " version:\t[1 0]\n", - " world size:\t[100. 100.]\n", + " format_url:\thttps://git.imp.fu-berlin.de/bioroboticslab/robofish/track_format/-/releases/1.0\n", + " format_version:\t[1 0]\n", + " world_size_cm:\t[100. 100.]\n", "| entities\n", "|---| fish_1\n", - "|---|--- type:\tfish\n", - "|---|--- poses:\t Shape (5, 4)\n", - "|---|---| time\n", - "|---|---|--- monotonic points:\t Shape (5,)\n", + "|---|--- category:\tfish\n", + "|---|--- orientations:\t Shape (1000, 2)\n", + "|---|--- positions:\t Shape (1000, 2)\n", "|---| fish_2\n", - "|---|--- type:\tfish\n", - "|---|--- poses:\t Shape (5, 4)\n", - "|---|---| time\n", - "|---|---|--- monotonic points:\t Shape (5,)\n", + "|---|--- category:\tfish\n", + "|---|--- orientations:\t Shape (1000, 2)\n", + "|---|--- positions:\t Shape (1000, 2)\n", "|---| fish_3\n", - "|---|--- type:\tfish\n", - "|---|--- poses:\t Shape (5, 4)\n", - "|---|---| time\n", - "|---|---|--- monotonic points:\t Shape (5,)\n", + "|---|--- category:\tfish\n", + "|---|--- orientations:\t Shape (1000, 2)\n", + "|---|--- positions:\t Shape (1000, 2)\n", "|---| obstacle_1\n", - "|---|--- type:\tobstacle\n", + "|---|--- category:\tobstacle\n", + "|---|--- orientations:\t Shape (1, 2)\n", "|---|--- outlines:\t Shape (1, 4, 2)\n", - "|---|--- poses:\t Shape (1, 4)\n", - "|---|---| time\n", + "|---|--- positions:\t Shape (1, 2)\n", "|---| robot\n", - "|---|--- type:\trobot\n", - "|---|--- poses:\t Shape (4, 4)\n", - "|---|---| time\n", - "|---|---|--- monotonic step:\t40\n", + "|---|--- category:\trobot\n", + "|---|--- orientations:\t Shape (1000, 2)\n", + "|---|--- positions:\t Shape (1000, 2)\n", + "| samplings\n", + "|--- default:\t25 hz\n", + "|---| 25 hz\n", + "|---|--- frequency_hz:\t25.0\n", "\n" ] } ], "source": [ - "#! /usr/bin/env python3\n", - "\n", "import robofish.io\n", "import numpy as np\n", - "from pathlib import Path\n", - "import os\n", - "\n", "\n", - "# Helper function to enable relative paths from this file\n", - "def full_path(path):\n", - " return (Path(os.path.abspath(\"__file__\")).parent / path).resolve()\n", "\n", - "\n", - "if __name__ == \"__main__\":\n", + "def create_example_file(path):\n", " # Create a new io file object with a 100x100cm world\n", - " sf = robofish.io.File(world_size=[100, 100])\n", + " sf = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0)\n", "\n", " # create a simple obstacle, fixed in place, fixed outline\n", - " obstacle_pose = [[50, 50, 0, 0]]\n", " obstacle_outline = [[[-10, -10], [-10, 0], [0, 0], [0, -10]]]\n", " obstacle_name = sf.create_entity(\n", - " \"obstacle\", poses=obstacle_pose, outlines=obstacle_outline\n", + " \"obstacle\", positions=[[50, 50]], orientations=[[0]], outlines=obstacle_outline\n", " )\n", "\n", - " # create a robofish with 100 timesteps and 40ms between the timesteps. If we would not give a name, the name would be generated to be robot_1.\n", - " robofish_timesteps = 4\n", - " robofish_poses = np.zeros((robofish_timesteps, 4))\n", - " sf.create_entity(\"robot\", robofish_poses, name=\"robot\", monotonic_step=40)\n", + " # create a robofish with 1000 timesteps. If we would not give a name, the name would be generated to be robot_1.\n", + " robofish_timesteps = 1000\n", + " robofish_poses = np.ones((robofish_timesteps, 4)) * 50\n", + " robot = sf.create_entity(\"robot\", robofish_poses, name=\"robot\")\n", "\n", " # create multiple fishes with timestamps. Since we don't specify names, but only the type \"fish\" the fishes will be named [\"fish_1\", \"fish_2\", \"fish_3\"]\n", " agents = 3\n", - " timesteps = 5\n", - " timestamps = np.linspace(0, timesteps + 1, timesteps)\n", + " timesteps = 1000\n", + " # timestamps = np.linspace(0, timesteps + 1, timesteps)\n", " agent_poses = np.random.random((agents, timesteps, 4))\n", "\n", - " fish_names = sf.create_multiple_entities(\n", - " \"fish\", agent_poses, monotonic_points=timestamps\n", - " )\n", + " fishes = sf.create_multiple_entities(\"fish\", agent_poses)\n", "\n", " # This would throw an exception if the file was invalid\n", " sf.validate()\n", "\n", " # Save file validates aswell\n", - " example_file = full_path(\"example.hdf5\")\n", - " sf.save(example_file)\n", + "\n", + " sf.save_as(path)\n", "\n", " # Closing and opening files (just for demonstration)\n", " sf.close()\n", - " sf = robofish.io.File(path=example_file)\n", + " sf = robofish.io.File(path=path)\n", "\n", " print(\"\\nEntity Names\")\n", " print(sf.entity_names)\n", "\n", - " # Get an array with all poses. As the length of poses varies per agent, it\n", - " # is filled up with nans. The result is not interpolated and the time scales\n", - " # per agent are different. It is planned to create a warning in the case of\n", - " # different time scales and have another function, which generates an\n", - " # interpolated array.\n", + " # Get an array with all poses. As the length of poses varies per agent, it is filled up with nans.\n", " print(\"\\nAll poses\")\n", - " print(sf.select_poses())\n", + " print(sf.entity_poses)\n", "\n", " print(\"\\nFish poses\")\n", - " print(sf.select_poses(lambda e: e.category == \"fish\"))\n", + " print(sf.select_entity_poses(lambda e: e.category == \"fish\"))\n", "\n", " print(\"\\nFile structure\")\n", - " print(sf)\n" + " print(sf)\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " create_example_file(\"example.hdf5\")\n" ] } ], @@ -187,4 +192,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/examples/example_basic.py b/examples/example_basic.py index dec8e3be1c7997b33b36f9868f0df8b0c6c3fbe6..b9e356c7547a37cdfc66e75f8f93269bb4365a1c 100755 --- a/examples/example_basic.py +++ b/examples/example_basic.py @@ -1,7 +1,6 @@ #! /usr/bin/env python3 import robofish.io -from robofish.io import utils import numpy as np diff --git a/setup.py b/setup.py index 5be2fe4573c17f9dbb776058058ea0e38c4674aa..7999f5ac38332788fb314af928b99b67c06040d7 100644 --- a/setup.py +++ b/setup.py @@ -14,9 +14,15 @@ entry_points = { ] } + def source_version(): version_parts = ( - run(["git", "describe", "--tags", "--dirty"], check=True, stdout=PIPE, encoding="utf-8") + run( + ["git", "describe", "--tags", "--dirty"], + check=True, + stdout=PIPE, + encoding="utf-8", + ) .stdout.strip() .split("-") ) @@ -36,12 +42,20 @@ def source_version(): return version + setup( name="robofish-io", version=source_version(), author="", author_email="", - install_requires=["h5py>=3", "numpy", "seaborn", "pandas", "deprecation"], + install_requires=[ + "h5py>=3", + "numpy", + "seaborn", + "pandas", + "deprecation", + "testbook", + ], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", diff --git a/src/robofish/io/entity.py b/src/robofish/io/entity.py index 8ef557c3d7d0bdcdd010f47055a121ca3452114f..f791a125e2e7ef9f22c539e9b9810061e444de88 100644 --- a/src/robofish/io/entity.py +++ b/src/robofish/io/entity.py @@ -98,7 +98,7 @@ class Entity(h5py.Group): else: if poses is not None: - assert poses.shape[1] == 3 or poses.shape[1] == 4 + assert poses.shape[1] in [3, 4] positions = poses[:, :2] orientations = poses[:, 2:] if orientations is not None and orientations.shape[1] == 1: diff --git a/tests/robofish/evaluate/test_app_evaluate.py b/tests/robofish/evaluate/test_app_evaluate.py index 58ca278882b1756830fd66b7a37a4cb823d8199a..2a7fe486a95c961f41f591ea7c8ab533db6b2ccf 100644 --- a/tests/robofish/evaluate/test_app_evaluate.py +++ b/tests/robofish/evaluate/test_app_evaluate.py @@ -22,7 +22,7 @@ def test_app_validate(): self.names = None self.save_path = graphics_out - # TODO: Get rid of deprecated get_poses function + # TODO: Get rid of deprecation with pytest.warns(DeprecationWarning): app.evaluate(DummyArgs("speed")) graphics_out.unlink() diff --git a/tests/robofish/io/test_examples.py b/tests/robofish/io/test_examples.py index 1d0379fb1b06448e5f733c3690ff48a4f623592c..deb626a6faa1d3675b00fb7bf18ecff39488022e 100644 --- a/tests/robofish/io/test_examples.py +++ b/tests/robofish/io/test_examples.py @@ -1,12 +1,13 @@ import robofish.io from robofish.io import utils from pathlib import Path +from testbook import testbook import sys sys.path.append(str(utils.full_path(__file__, "../../../examples/"))) - +ipynb_path = utils.full_path(__file__, "../../../examples/example_basic.ipynb") path = utils.full_path(__file__, "../../../examples/tmp_example.hdf5") if path.exists(): path.unlink() @@ -24,3 +25,9 @@ def test_example_basic(): example_basic.create_example_file(path) path.unlink() + + +def test_example_basic_ipynb(): + # Executing the notebook should not lead to an exception + with testbook(str(ipynb_path), execute=True) as tb: + pass