From f835e2c28d8df75d8a24066045254bb7f870cbdd Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Fri, 14 Jun 2024 19:47:49 +0200 Subject: [PATCH] Replaced LinearSweep wrapper buy projecting in the base env Also fixed reset for the projected case, extended interfaces to better deal with actions in various formats Added multiple getters to the LabelmapEnv and made names more explicit --- docs/02_notebooks/L5_linear_sweep.ipynb | 37123 ++++++++++++++++- src/armscan_env/envs/base.py | 6 +- src/armscan_env/envs/labelmaps_navigation.py | 163 +- src/armscan_env/envs/observations.py | 12 +- src/armscan_env/envs/state_action.py | 2 +- src/armscan_env/wrapper.py | 36 +- 6 files changed, 37203 insertions(+), 139 deletions(-) diff --git a/docs/02_notebooks/L5_linear_sweep.ipynb b/docs/02_notebooks/L5_linear_sweep.ipynb index f94cced..767613f 100644 --- a/docs/02_notebooks/L5_linear_sweep.ipynb +++ b/docs/02_notebooks/L5_linear_sweep.ipynb @@ -2,10 +2,24 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "a4e98c0276b6012d", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T17:08:02.514102Z", + "start_time": "2024-06-14T17:08:02.504115Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2" @@ -13,38 +27,111 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "50b440b37fd9414b", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T17:08:04.770482Z", + "start_time": "2024-06-14T17:08:02.515693Z" + } + }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import SimpleITK as sitk\n", "from armscan_env.config import get_config\n", + "from armscan_env.envs.base import EnvRollout\n", "from armscan_env.envs.labelmaps_navigation import (\n", " LabelmapClusteringBasedReward,\n", " LabelmapEnv,\n", " LabelmapEnvTerminationCriterion,\n", ")\n", "from armscan_env.envs.observations import LabelmapSliceAsChannelsObservation\n", - "from armscan_env.wrapper import LinearSweepWrapper\n", - "from IPython.core.display import HTML\n", + "from tqdm import tqdm\n", "\n", "config = get_config()" ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "# The scanning sub-problem in fewer dimensions", - "id": "88415eb119bd928d" + "id": "88415eb119bd928d", + "metadata": {}, + "source": [ + "# The scanning sub-problem in fewer dimensions" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, + "id": "9ed46c7b", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T17:08:04.793789Z", + "start_time": "2024-06-14T17:08:04.772423Z" + } + }, + "outputs": [], + "source": [ + "def walk_through_env(\n", + " env: LabelmapEnv,\n", + " n_steps: int = 100,\n", + " reset: bool = True,\n", + " show_pbar: bool = True,\n", + " render_title: str = \"Labelmap slice\",\n", + ") -> EnvRollout:\n", + " env_rollout = EnvRollout()\n", + "\n", + " if reset:\n", + " obs, info = env.reset()\n", + " env.render(title=render_title)\n", + "\n", + " # add initial state to the rollout\n", + " reward = env.compute_cur_reward()\n", + " terminated = env.should_terminate()\n", + " truncated = env.should_truncate()\n", + " env_rollout.append_reset(\n", + " obs,\n", + " info,\n", + " reward=reward,\n", + " terminated=terminated,\n", + " truncated=truncated,\n", + " )\n", + "\n", + " env_is_1d = env.action_space.shape == (1,)\n", + "\n", + " y_lower_bound = -1 if env_is_1d else env.translation_bounds[0]\n", + " y_upper_bound = 1 if env_is_1d else env.translation_bounds[1]\n", + "\n", + " y_actions = np.linspace(y_lower_bound, y_upper_bound, n_steps)\n", + " if show_pbar:\n", + " y_actions = tqdm(y_actions, desc=\"Step:\")\n", + "\n", + " print(f\"Walking through y-axis from {y_lower_bound} to {y_upper_bound} in {n_steps} steps\")\n", + " for y_action in y_actions:\n", + " if not env_is_1d:\n", + " cur_y_action = env.get_optimal_action()\n", + " cur_y_action.translation = (cur_y_action.translation[0], y_action)\n", + " else:\n", + " # projected environment\n", + " cur_y_action = np.array([y_action])\n", + " obs, reward, terminated, truncated, info = env.step(cur_y_action)\n", + "\n", + " env_rollout.append_step(cur_y_action, obs, reward, terminated, truncated, info)\n", + " env.render(title=render_title)\n", + " return env_rollout" + ] + }, + { + "cell_type": "code", + "execution_count": 28, "id": "da45ed45bb7b8f3b", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T17:08:04.872689Z", + "start_time": "2024-06-14T17:08:04.795853Z" + } + }, "outputs": [], "source": [ "volume_1 = sitk.ReadImage(config.get_labels_path(1))\n", @@ -55,9 +142,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "63dd92db3829d7db", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T17:08:04.893774Z", + "start_time": "2024-06-14T17:08:04.874935Z" + } + }, "outputs": [], "source": [ "volume_size = volume_1.GetSize()\n", @@ -80,61 +172,18323 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "16a139f61aaafd19", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T18:06:53.565748Z", + "start_time": "2024-06-13T18:06:42.215656Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Step:: 40%|████ | 4/10 [00:00<00:00, 30.79it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Walking through y-axis from 0.0 to 213.93069076538086 in 10 steps\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Step:: 100%|██████████| 10/10 [00:00<00:00, 16.69it/s]\n" + ] + }, + { + "data": { + "image/png": "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zW48+uijWL9+PTZv3gxPT0/jdWYvLy+4urpKXJ398PT0vGLflbu7O/z8/Lgfy8SeeOIJjBkzBq+++iruuusupKSk4OOPP8bHH38sdWl25ZZbbsErr7yCiIgIDBw4EOnp6XjrrbfwwAMPSF2aTauursbZs2eNf75w4QIyMjLg6+uLiIgILFq0CC+//DL69OmDnj174rnnnkNoaCimT59u/uLMfi6N2vTuu++KERERorOzszhy5EjxwIEDUpdkVwC0+fH5559LXZrd4/F58/nf//4nDho0SFQqlWJMTIz48ccfS12S3VGr1eLChQvFiIgI0cXFRYyOjhb/+c9/ig0NDVKXZtN+++23Nn8mz5o1SxRF/RH65557TgwKChKVSqU4efJkMTMz0yK1CaLIdplERETkmLhHiIiIiBwWgxARERE5LAYhIiIiclgMQkREROSwGISIiIjIYTEIERERkcNiECIiIiKHxSBEREREDotBiIiIiBwWgxAR2YWSkhI88sgjiIiIgFKpRHBwMKZMmYJ9+/YBAARBwKZNm6QtkoisDoeuEpFduOOOO9DY2Ig1a9YgOjoaRUVF2LFjB0pLS6UujYisGGeNEZHNq6iogI+PD3bt2oWJEyde8fmoqChkZWUZ/xwZGYmLFy8CADZv3oyXXnoJJ06cQGhoKGbNmoV//vOfUCj0vycKgoAPPvgAW7Zswa5duxASEoLly5fjzjvvtMjXRkTmxUtjRGTzPDw84OHhgU2bNqGhoeGKzx86dAgA8Pnnn6OgoMD45z179uD+++/HwoULceLECXz00UdYvXo1XnnllVaPf+6553DHHXfgyJEjSEpKwsyZM3Hy5Enzf2FEZHZcESIiu7Bx40bMnz8fdXV1iI2NxcSJEzFz5kwMGTIEgH5l5/vvv8f06dONj0lMTMTkyZOxdOlS421ffvklnnnmGeTn5xsf9/DDD+PDDz803mf06NGIjY3FBx98YJkvjojMhitCRGQX7rjjDuTn52PLli2YOnUqdu3ahdjYWKxevbrdxxw5cgT/+te/jCtKHh4emD9/PgoKClBbW2u8X0JCQqvHJSQkcEWIyE5wszQR2Q0XFxdcf/31uP766/Hcc89h3rx5eOGFFzB79uw2719dXY2XXnoJt99+e5vPRUT2jytCRGS3BgwYgJqaGgCAk5MTtFptq8/HxsYiMzMTvXv3vuJDJrv84/HAgQOtHnfgwAH079/f/F8AEZkdV4SIyOaVlpZixowZeOCBBzBkyBB4enoiNTUVy5cvx2233QZAf3Jsx44dGDt2LJRKJXx8fPD888/j5ptvRkREBO68807IZDIcOXIEx44dw8svv2x8/m+//Rbx8fEYN24c1q1bh5SUFHz66adSfblEZELcLE1ENq+hoQEvvvgifvnlF5w7dw5NTU0IDw/HjBkz8I9//AOurq743//+h8WLF+PixYsICwszHp/ftm0b/vWvfyE9PR1OTk6IiYnBvHnzMH/+fAD6zdLvv/8+Nm3ahN27dyMkJASvvfYa7rrrLgm/YiIyFQYhIqKraOu0GRHZD+4RIiIiIofFIEREREQOi5uliYiugrsHiOwbV4SIiIjIYTEIERERkcNiECIiIiKHxSBEREREDotBiIiIiBwWgxARERE5LAYhIiIiclgMQkREROSw/j+NikbwEQ/0kgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "y_slice_rewards = []\n", + "env_rollout = walk_through_env(env, 10)\n", "\n", - "env.reset()\n", - "for y_action in np.linspace(0, env.translation_bounds[1], 500):\n", - " cur_y_action = env.get_optimal_action()\n", - " cur_y_action.translation = (cur_y_action.translation[0], y_action)\n", - " observation, reward, terminated, truncated, info = env.step(cur_y_action)\n", - " y_slice_rewards.append(reward)\n", - " env.render()\n", + "plt.plot(env_rollout.rewards)\n", + "plt.xlabel(\"Step\")\n", + "plt.ylabel(\"Reward\")\n", + "plt.show()\n", "\n", - " if terminated or truncated:\n", - " observation, info = env.reset(reset_render=True)\n", - "animation = env.get_cur_animation()\n", - "env.close()" + "# env.close()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "id": "6cdf855cc85a743a", - "metadata": {}, - "outputs": [], - "source": [ - "HTML(animation.to_jshtml())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "edf25c95f777b16b", - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T18:07:05.195530Z", + "start_time": "2024-06-13T18:07:02.524034Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "plt.plot(np.linspace(0, env.translation_bounds[1], 500), y_slice_rewards)\n", - "plt.xlabel(\"Y translation\")\n", - "plt.ylabel(\"Reward\")\n", - "plt.show()" + "env.get_cur_animation_as_html()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "id": "519dde5f1cea8a5f", "metadata": {}, "outputs": [], "source": [ "volume_size = volume_1.GetSize()\n", "\n", - "env = LabelmapEnv(\n", - " name2volume={\"1\": volume_1, \"2\": volume_2},\n", + "projected_env = LabelmapEnv(\n", + " name2volume={\"1\": volume_1},\n", " observation=LabelmapSliceAsChannelsObservation(\n", " slice_shape=(volume_size[0], volume_size[2]),\n", " action_shape=(4,),\n", @@ -146,52 +18500,18667 @@ " rotation_bounds=(30.0, 10.0),\n", " translation_bounds=(0.0, None),\n", " render_mode=\"animation\",\n", + " project_actions_to=\"y\",\n", ")" ] }, { "cell_type": "code", - "execution_count": null, - "id": "3f9dff9336682907", - "metadata": {}, - "outputs": [], - "source": [ - "env = LinearSweepWrapper(env)" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 34, "id": "22877ab71fed2eb0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Step:: 40%|████ | 4/10 [00:00<00:00, 25.85it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Walking through y-axis from -1 to 1 in 10 steps\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Step:: 100%|██████████| 10/10 [00:00<00:00, 11.51it/s]\n" + ] + }, + { + "data": { + "image/png": "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b9++SE9Px6JFixAWFsafs53i1JiFBQQEQC6X4+LFi43uv3jxIkJCQiSqyn4tWLAAW7duxa5du9C5c2epy7E7KSkpKCwsRExMDBQKBRQKBX7//XesWLECCoUCOp1O6hLtRmhoKPr06dPovt69eyMrK0uiiuzTk08+iSVLlmDatGno378/ZsyYgcceewzLli2TujS7Zfjsk+pzkUHIwpydnREbG4sdO3YY79Pr9dixYwfi4+MlrMy+iKKIBQsW4Pvvv8fOnTvRpUsXqUuyS+PGjcPRo0eRnp5uvMXFxSExMRHp6emQy+VSl2g3Ro4cedUREKdOnUJkZKREFdmn6upqyGSNPxrlcjn0er1EFdm/Ll26ICQkpNHnolqtxsGDBy3yucipMQksXrwYs2bNQlxcHIYOHYq3334bVVVVmDNnjtSl2Y2HH34Y69evx+bNm+Hp6WmcZ/b29oarq6vE1dkPT0/Pq9Zdubu7w9/fn+uxTOyxxx7DiBEj8Oqrr+Kuu+7CoUOH8NFHH+Gjjz6SujS7csstt+CVV15BREQE+vbti7S0NLz11lu47777pC7NplVWVuLMmTPGr8+fP4/09HT4+fkhIiICixYtwssvv4wePXqgS5cuePbZZxEWFobJkyebvziz70ujJr377rtiRESE6OzsLA4dOlQ8cOCA1CXZFQBN3j7//HOpS7N73D5vPj/88IPYr18/UalUitHR0eJHH30kdUl2R61WiwsXLhQjIiJEFxcXsWvXruIzzzwjajQaqUuzabt27Wryd/KsWbNEUWzYQv/ss8+KwcHBolKpFMeNGydmZGRYpDZBFHlcJhERETkmrhEiIiIih8UgRERERA6LQYiIiIgcFoMQEREROSwGISIiInJYDEJERETksBiEiIiIyGExCBEREZHDYhAiIiIih8UgRER2oaioCA899BAiIiKgVCoREhKC8ePH448//gAACIKATZs2SVskEVkdNl0lIrswZcoU1NXVYc2aNejatSsuXryIHTt2oKSkROrSiMiKsdcYEdk8lUoFX19f7N69G2PGjLnq8aioKGRmZhq/joyMxIULFwAAmzdvxosvvoi//voLYWFhmDVrFp555hkoFA1/JwqCgPfffx9btmzB7t27ERoaiuXLl+POO++0yPdGRObFqTEisnkeHh7w8PDApk2boNFornr88OHDAIDPP/8c+fn5xq/37t2LmTNnYuHChfjrr7/w4YcfYvXq1XjllVcaXf/ss89iypQpOHLkCBITEzFt2jScOHHC/N8YEZkdR4SIyC5s3LgR8+fPR01NDWJiYjBmzBhMmzYNAwYMANAwsvP9999j8uTJxmsSEhIwbtw4LF261Hjfl19+iaeeegp5eXnG6x588EF88MEHxucMHz4cMTExeP/99y3zzRGR2XBEiIjswpQpU5CXl4ctW7ZgwoQJ2L17N2JiYrB69eprXnPkyBH8+9//No4oeXh4YP78+cjPz0d1dbXxefHx8Y2ui4+P54gQkZ3gYmkishsuLi644YYbcMMNN+DZZ5/FvHnz8Pzzz2P27NlNPr+yshIvvvgi7rjjjiZfi4jsH0eEiMhu9enTB1VVVQAAJycn6HS6Ro/HxMQgIyMD3bt3v+omk13+9XjgwIFG1x04cAC9e/c2/zdARGbHESEisnklJSWYOnUq7rvvPgwYMACenp5ITk7G8uXLcdtttwFo2Dm2Y8cOjBw5EkqlEr6+vnjuuedw8803IyIiAnfeeSdkMhmOHDmCY8eO4eWXXza+/oYNGxAXF4dRo0Zh3bp1OHToED799FOpvl0iMiEuliYim6fRaPDCCy/g119/xdmzZ1FfX4/w8HBMnToVTz/9NFxdXfHDDz9g8eLFuHDhAjp16mTcPv/LL7/g3//+N9LS0uDk5ITo6GjMmzcP8+fPB9CwWHrlypXYtGkT9uzZg9DQULz22mu46667JPyOichUGISIiJrR1G4zIrIfXCNEREREDotBiIiIiBwWF0sTETWDqweI7BtHhIiIiMhhMQgRERGRw2IQIiIiIofFIEREREQOi0GIiIiIHBaDEBERETksBiEiIiJyWAxCRERE5LD+H8kPs6qmnItWAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "env.reset()\n", - "for _ in range(50):\n", - " action = env.action_space.sample()\n", - " observation, reward, terminated, truncated, info = env.step(action)\n", - " env.render()\n", + "projected_env_rollout = walk_through_env(projected_env, 10, render_title=\"Projected labelmap slice\")\n", "\n", - " if terminated or truncated:\n", - " observation, info = env.reset(reset_render=False)\n", - "animation = env.get_cur_animation()\n", - "env.close()" + "plt.plot(projected_env_rollout.rewards)\n", + "plt.xlabel(\"Step\")\n", + "plt.ylabel(\"Reward\")\n", + "plt.show()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "id": "6ada94c94fe77de0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "HTML(animation.to_jshtml())" + "projected_env.get_cur_animation_as_html()" ] }, { "cell_type": "code", "execution_count": null, - "id": "2cf8f44a857de93e", + "id": "49fcecf1", "metadata": {}, "outputs": [], "source": [] @@ -206,14 +37175,14 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.11.4" } }, "nbformat": 4, diff --git a/src/armscan_env/envs/base.py b/src/armscan_env/envs/base.py index 50c422e..394784f 100644 --- a/src/armscan_env/envs/base.py +++ b/src/armscan_env/envs/base.py @@ -14,7 +14,7 @@ class EnvPreconditionError(RuntimeError): @dataclass(kw_only=True) class StateAction: - action: Any + normalized_action_arr: Any # state of the env will be reflected by fields added to subclasses # but action is a reserved field name. Subclasses should override the # type of action to be more specific @@ -249,16 +249,16 @@ def append_step( action: TAction, observation: TObs, reward: float, - info: dict[str, Any], terminated: bool, truncated: bool, + info: dict[str, Any], ) -> None: self.observations.append(observation) self.rewards.append(reward) self.actions.append(action) - self.infos.append(info) self.terminated.append(terminated) self.truncated.append(truncated) + self.infos.append(info) def append_reset( self, diff --git a/src/armscan_env/envs/labelmaps_navigation.py b/src/armscan_env/envs/labelmaps_navigation.py index 714ebb3..18ea849 100644 --- a/src/armscan_env/envs/labelmaps_navigation.py +++ b/src/armscan_env/envs/labelmaps_navigation.py @@ -1,6 +1,6 @@ import logging from abc import ABC -from copy import copy +from copy import copy, deepcopy from typing import Any, ClassVar, Literal import gymnasium as gym @@ -28,7 +28,6 @@ log = logging.getLogger(__name__) - _VOL_NAME_TO_OPTIMAL_ACTION = { "1": ManipulatorAction(rotation=(19.3, 0.0), translation=(0.0, 140.0)), "2": ManipulatorAction(rotation=(0.0, 0.0), translation=(0.0, 115.0)), @@ -59,7 +58,7 @@ class LabelmapEnv(ModularEnv[LabelmapStateAction, np.ndarray, np.ndarray]): :param seed: seed for the random number generator """ - _INITIAL_POS_ROTATION = np.zeros(4) + _INITIAL_FULL_NORMALIZED_ACTION_ARR = np.zeros(4) metadata: ClassVar[dict] = {"render_modes": ["plt", "animation", None], "render_fps": 10} @@ -75,6 +74,7 @@ def __init__( translation_bounds: tuple[float | None, float | None] = (None, None), render_mode: Literal["plt", "animation"] | None = None, seed: int | None = DEFAULT_SEED, + project_actions_to: Literal["x", "y", "xy"] | None = None, ): if not name2volume: raise ValueError("name2volume must not be empty") @@ -87,6 +87,7 @@ def __init__( self.name2volume = name2volume self._slice_shape = slice_shape self._seed = seed + self._project_actions_to = project_actions_to # set at reset self._cur_labelmap_name: str | None = None @@ -101,27 +102,59 @@ def __init__( self._axes: tuple[plt.Axes, plt.Axes, plt.Axes, plt.Axes, plt.Axes] | None = None self._camera: Camera | None = None + @property + def project_actions_to(self) -> Literal["x", "y", "xy"] | None: + return self._project_actions_to + def get_optimal_action(self) -> ManipulatorAction: if self.cur_labelmap_name is None: raise RuntimeError("The labelmap name must not be None, did you call reset?") - return copy(_VOL_NAME_TO_OPTIMAL_ACTION[self.cur_labelmap_name]) + return deepcopy(_VOL_NAME_TO_OPTIMAL_ACTION[self.cur_labelmap_name]) - def step_to_solution(self) -> None: - self.step(self.get_optimal_action()) + def get_full_optimal_action_array(self) -> np.ndarray: + return self.get_optimal_action().to_normalized_array( + self.rotation_bounds, + self.translation_bounds, + ) - def unnormalize_rotation_translation(self, action: np.ndarray) -> ManipulatorAction: - """Unnormalizes an array with values in the range [-1, 1] to the original range that is - consumed by :func:`slice_volume`. + def _get_projected_action_arr_idx(self) -> list[int]: + match self._project_actions_to: + case None: + return list(range(4)) + case "x": + return [2] + case "y": + return [3] + case "xy": + return [2, 3] + case _: + raise ValueError(f"Unknown {self._project_actions_to=}") - :param action: normalized action with values in the range [-1, 1] - :return: unnormalized action that can be used with :func:`slice_volume` - """ + def _get_full_action_arr_len(self) -> int: + return len(self._INITIAL_FULL_NORMALIZED_ACTION_ARR) + + def _get_full_action_leading_to_initial_state_normalized_arr(self) -> np.ndarray: + initial_action_arr = self.get_full_optimal_action_array() + project_idx = self._get_projected_action_arr_idx() + initial_action_arr[project_idx] = copy( + self._INITIAL_FULL_NORMALIZED_ACTION_ARR[project_idx], + ) + return initial_action_arr + + def _get_action_leading_to_initial_state(self) -> ManipulatorAction: return ManipulatorAction.from_normalized_array( - action, + self._get_full_action_leading_to_initial_state_normalized_arr(), self.rotation_bounds, self.translation_bounds, ) + def get_optimal_action_array(self) -> np.ndarray: + full_action_arr = self.get_full_optimal_action_array() + return full_action_arr[self._get_projected_action_arr_idx()] + + def step_to_optimal_state(self) -> None: + self.step(self.get_optimal_action()) + @property def cur_labelmap_name(self) -> str | None: return self._cur_labelmap_name @@ -132,11 +165,8 @@ def cur_labelmap_volume(self) -> sitk.Image | None: @property def action_space(self) -> gym.spaces.Space[np.ndarray]: - return gym.spaces.Box( - low=-1, - high=1.0, - shape=(4,), - ) # 2 rotations, 2 translations. + action_dim = len(self._get_projected_action_arr_idx()) + return gym.spaces.Box(low=-1, high=1, shape=(action_dim,), dtype=np.float32) def close(self) -> None: super().close() @@ -144,8 +174,51 @@ def close(self) -> None: self._cur_labelmap_name = None self._cur_labelmap_volume = None - def _get_slice_from_action(self, action: np.ndarray) -> np.ndarray: - manipulator_action = self.unnormalize_rotation_translation(action) + def get_full_action_array_from_projected_action( + self, + normalized_action_arr: np.ndarray, + ) -> np.ndarray: + """Converts a (potentially projected and) normalized action array to a full action array. + + If `project_actions_to` is not None, the `normalized_action_arr` is assumed to be a projection + of the correct dimension. + """ + full_action_arr = self.get_full_optimal_action_array() + project_idx = self._get_projected_action_arr_idx() + if len(normalized_action_arr) != len(project_idx): + raise ValueError( + f"Expected {len(project_idx)} elements in normalized_action_arr, " + f"but got {len(normalized_action_arr)}", + ) + full_action_arr[project_idx] = normalized_action_arr + return full_action_arr + + def get_manipulator_action_from_normalized_action( + self, + normalized_action_arr: np.ndarray, + ) -> ManipulatorAction: + """Converts a (potentially projected and) normalized action array to a ManipulatorAction. + + Passing a full action array is also supported, even if `project_actions_to` is not None. + If `normalized_action_arr` is of a lower len and `project_actions_to` is not None, the + `normalized_action_arr` is assumed to be a projection + of the correct dimension. + """ + if len(normalized_action_arr) != self._get_full_action_arr_len(): + normalized_action_arr = self.get_full_action_array_from_projected_action( + normalized_action_arr, + ) + return ManipulatorAction.from_normalized_array( + normalized_action_arr, + self.rotation_bounds, + self.translation_bounds, + ) + + def _get_slice_from_action(self, action: np.ndarray | ManipulatorAction) -> np.ndarray: + if isinstance(action, np.ndarray): + manipulator_action = self.get_manipulator_action_from_normalized_action(action) + else: + manipulator_action = action sliced_volume = slice_volume( volume=self.cur_labelmap_volume, slice_shape=self._slice_shape, @@ -157,12 +230,16 @@ def _get_slice_from_action(self, action: np.ndarray) -> np.ndarray: return sitk.GetArrayFromImage(sliced_volume)[:, 0, :].T def _get_initial_slice(self) -> np.ndarray: - return self._get_slice_from_action(self._INITIAL_POS_ROTATION) + action_to_initial_slice = self._get_action_leading_to_initial_state() + return self._get_slice_from_action(action_to_initial_slice) - def compute_next_state(self, action: np.ndarray) -> LabelmapStateAction: - new_slice = self._get_slice_from_action(action) + def compute_next_state( + self, + normalized_action_arr: np.ndarray | ManipulatorAction, + ) -> LabelmapStateAction: + new_slice = self._get_slice_from_action(normalized_action_arr) return LabelmapStateAction( - action=action, + normalized_action_arr=normalized_action_arr, labels_2d_slice=new_slice, last_reward=self.reward_metric.compute_reward(self.cur_state_action), # cur_state_action is the previous state, so this reward is computed for the previous state @@ -184,9 +261,12 @@ def sample_initial_state(self) -> LabelmapStateAction: self.compute_slice_shape(volume=self.cur_labelmap_volume) initial_slice = self._get_initial_slice() return LabelmapStateAction( - action=self._INITIAL_POS_ROTATION, + normalized_action_arr=copy( + self._INITIAL_FULL_NORMALIZED_ACTION_ARR[self._get_projected_action_arr_idx()], + ), labels_2d_slice=initial_slice, last_reward=-1.0, + # TODO: pass the env's optimal position and labelmap or remove them from the StateAction? optimal_position=None, optimal_labelmap=None, ) @@ -214,6 +294,19 @@ def compute_translation_bounds(self) -> None: def get_translation_bounds(self) -> tuple[float | None, float | None]: return self.translation_bounds + def get_cur_full_normalized_action_arr(self) -> np.ndarray: + return self.get_full_action_array_from_projected_action( + self.cur_state_action.normalized_action_arr, + ) + + def get_cur_manipulator_action(self) -> ManipulatorAction: + cur_full_action_arr = self.get_cur_full_normalized_action_arr() + return ManipulatorAction.from_normalized_array( + cur_full_action_arr, + self.rotation_bounds, + self.translation_bounds, + ) + def step( self, action: np.ndarray | ManipulatorAction, @@ -240,13 +333,13 @@ def reset( obs, info = super().reset(seed=self._seed, **kwargs) return obs, info - def render(self) -> plt.Figure | Camera | None: + def render(self, title: str = "Labelmap slice") -> plt.Figure | Camera | None: match self.render_mode: case "plt": - return self.get_cur_state_plot(create_new_figure=False) + return self.get_cur_state_plot(create_new_figure=False, title=title) case "animation": camera = self.get_camera() - self.get_cur_state_plot(create_new_figure=False) + self.get_cur_state_plot(create_new_figure=False, title=title) camera.snap() return camera case None: @@ -261,7 +354,11 @@ def render(self) -> plt.Figure | Camera | None: f"Unknown render mode: {self.render_mode}, this should not happen.", ) - def get_cur_state_plot(self, create_new_figure: bool = True) -> plt.Figure | None: + def get_cur_state_plot( + self, + create_new_figure: bool = True, + title: str = "Labelmap slice", + ) -> plt.Figure | None: """Retrieve a figure visualizing the current state of the environment. :param create_new_figure: if True, a new figure will be created. Otherwise, a single figure will be used @@ -277,7 +374,9 @@ def get_cur_state_plot(self, create_new_figure: bool = True) -> plt.Figure | Non volume = self.cur_labelmap_volume o = volume.GetOrigin() img_array = sitk.GetArrayFromImage(volume) - action = self.unnormalize_rotation_translation(self.cur_state_action.action) + action = self.get_manipulator_action_from_normalized_action( + self.cur_state_action.normalized_action_arr, + ) translation = action.translation rotation = action.rotation @@ -290,7 +389,7 @@ def get_cur_state_plot(self, create_new_figure: bool = True) -> plt.Figure | Non y_dash = np.tan(np.deg2rad(rotation[0])) * x_dash + b_x y_dash = np.clip(y_dash, 0, img_array.shape[1] - 1) ax1.plot(x_dash, y_dash, linestyle="--", color="red") - ax1.set_title("Slice cut") + ax1.set_title(f"Slice cut (labelmap name: {self.cur_labelmap_name})") # Subplot 2: from the side ix = volume.GetSize()[0] // 2 @@ -324,6 +423,8 @@ def get_cur_state_plot(self, create_new_figure: bool = True) -> plt.Figure | Non # REWARD ax5.text(0, 0, f"Reward: {self.cur_reward:.2f}", fontsize=12, color="red") + fig.suptitle(title, x=0.2, y=0.95) + plt.close() return fig diff --git a/src/armscan_env/envs/observations.py b/src/armscan_env/envs/observations.py index d3ad783..3d26f25 100644 --- a/src/armscan_env/envs/observations.py +++ b/src/armscan_env/envs/observations.py @@ -94,7 +94,11 @@ def compute_observation( state: LabelmapStateAction, ) -> ChanneledLabelmapsObsWithActReward: """Return the observation as a dictionary of the type ChanneledLabelmapsObsWithActReward.""" - return self.compute_from_slice(state.labels_2d_slice, state.action, state.last_reward) + return self.compute_from_slice( + state.labels_2d_slice, + state.normalized_action_arr, + state.last_reward, + ) def compute_from_slice( self, @@ -176,7 +180,11 @@ def compute_observation( state: LabelmapStateAction, ) -> ChanneledLabelmapsObsWithActReward: """Return the observation as a dictionary of the type ChanneledLabelmapsObsWithActReward.""" - return self.compute_from_slice(state.labels_2d_slice, state.action, state.last_reward) + return self.compute_from_slice( + state.labels_2d_slice, + state.normalized_action_arr, + state.last_reward, + ) def compute_from_slice( self, diff --git a/src/armscan_env/envs/state_action.py b/src/armscan_env/envs/state_action.py index 6b03f92..b4a4c33 100644 --- a/src/armscan_env/envs/state_action.py +++ b/src/armscan_env/envs/state_action.py @@ -79,7 +79,7 @@ def from_normalized_array( @dataclass(kw_only=True) class LabelmapStateAction(StateAction): - action: np.ndarray + normalized_action_arr: np.ndarray """Array of shape (4,) representing two angles and two translations""" labels_2d_slice: np.ndarray """Two-dimensional slice of the labelmap, i.e., an array of shape (N, M) with integer values. diff --git a/src/armscan_env/wrapper.py b/src/armscan_env/wrapper.py index 2c6ebcd..cad5dcf 100644 --- a/src/armscan_env/wrapper.py +++ b/src/armscan_env/wrapper.py @@ -4,7 +4,6 @@ from abc import ABC, abstractmethod from typing import Any, Literal, SupportsFloat, cast -import gymnasium as gym import numpy as np import SimpleITK as sitk from armscan_env.envs.base import Observation, RewardMetric, TerminationCriterion @@ -28,6 +27,12 @@ log = logging.getLogger(__name__) +class PatchedFrameStackObservation(FrameStackObservation): + def __init__(self, env: Env[ObsType, ActType], n_stack: int): + super().__init__(env, n_stack) + self.observation_space = MultiBoxSpace(self.observation_space) + + class ArmscanEnvFactory(EnvFactory): """:param name2volume: the gymnasium task/environment identifier :param observation: the observation space to use @@ -64,7 +69,7 @@ def __init__( venv_type: VectorEnvType = VectorEnvType.SUBPROC_SHARED_MEM_AUTO, seed: int | None = None, n_stack: int = 1, - project_to_x_translation: bool = False, + project_actions_to: Literal["x", "y", "xy"] | None = None, remove_rotation_actions: bool = False, **make_kwargs: Any, ) -> None: @@ -84,7 +89,7 @@ def __init__( } self.seed = seed self.n_stack = n_stack - self.project_to_x_translation = project_to_x_translation + self.project_actions_to = project_actions_to self.remove_rotation_actions = remove_rotation_actions self.make_kwargs = make_kwargs @@ -111,19 +116,15 @@ def create_env(self, mode: EnvMode) -> LabelmapEnv: translation_bounds=self.translation_bounds, render_mode=self.render_modes.get(mode), seed=self.seed, + project_actions_to=self.project_actions_to, ) - if self.project_to_x_translation: - env = LinearSweepWrapper(env) - if self.n_stack > 1: - env = FrameStackObservation(env, self.n_stack) - env.observation_space = MultiBoxSpace(env.observation_space) - + env = PatchedFrameStackObservation(env, self.n_stack) return env -# Todo: Issue on gymnasyum for not overwriting reset method +# Todo: Issue on gymnasium for not overwriting reset method class PatchedWrapper(Wrapper[np.ndarray, float, np.ndarray, np.ndarray]): def __init__(self, env: LabelmapEnv | Env): super().__init__(env) @@ -150,18 +151,3 @@ def step( @abstractmethod def action(self, action: WrapperActType) -> np.ndarray: pass - - -class LinearSweepWrapper(PatchedActionWrapper): - def __init__(self, env: LabelmapEnv) -> None: - super().__init__(env) - self.env: LabelmapEnv = env - self.action_space = gym.spaces.Box(-1.0, 1.0, shape=(1,)) - - def action(self, action: WrapperActType) -> np.ndarray: - action = np.array(action) - normalized_optimal_action = self.env.get_optimal_action().to_normalized_array( - self.env.rotation_bounds, - self.env.translation_bounds, - ) - return np.append(normalized_optimal_action[:3], action)