Skip to content

Commit

Permalink
Merge pull request #7 from skim0119/5_matplotlib_renderer
Browse files Browse the repository at this point in the history
Add matplotlib renderer for quick debugging. Resolve #5
  • Loading branch information
skim0119 authored Feb 5, 2022
2 parents a537ddf + 3a05f2c commit 363dc35
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ model
save
*.mp4
.DS_Store
.vscode

*.pov

Expand Down
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,26 @@ python -m gym_softrobot.debug.make # Make environment and run 10 steps
python -m gym_softrobot.debug.registry # Print gym-softrobot environment
```

We use [POVray](https://wiki.povray.org/content/HowTo:Install_POV) python wrapper [Vapory](https://github.com/Zulko/vapory) to visualize the motion in 3D.
Requirements:
- Python 3.8+
- OpenAI Gym 0.21.0
- PyElastica 0.2+
- Matplotlib (optional for display rendering and plotting)
- POVray (optional for 3D rendering)

### Rendering

We support two different backends for the rendering: [POVray](https://wiki.povray.org/content/HowTo:Install_POV) and [Matplotlib](https://matplotlib.org/).
The default is set to use POVray, but the configuration can be switched by adding following lines.

```py
from gym_softrobot.config import RendererType
gym_softrobot.RENDERER_CONFIG = RendererType.MATPLOTLIB # Default: POVRAY
```

#### POVray

To make a good-looking 3D videos and figures, we use [POVray](https://wiki.povray.org/content/HowTo:Install_POV) python wrapper [Vapory](https://github.com/Zulko/vapory).
POVray is not a requirement to run the environment, but it is necessary to use `env.render()` function as typical gym environment.

If you would like to test `POVray` with `gym-softrobot`, use
Expand All @@ -36,13 +55,9 @@ If you would like to test `POVray` with `gym-softrobot`, use
python -m gym_softrobot.debug.render # Render 10 frames using vapory
```

Requirements:
- Python 3.8+
- OpenAI Gym 0.21.0
- PyElastica 0.2+
- Matplotlib (optional for display rendering and plotting)
- POVray (optional for 3D rendering)
#### Matplotlib

We provide secondary rendering tool using [Matplotlib](https://matplotlib.org/) for a quick debugging and sanity checking.

## Reinforcement Learning Example

Expand Down
6 changes: 6 additions & 0 deletions gym_softrobot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from gym.envs.registration import register

from gym_softrobot.config import *

""" Octopus Environment """
register(
id='OctoFlat-v0',
Expand Down Expand Up @@ -43,3 +45,7 @@
# id='InertialPull-v0',
# entry_point='gym_softrobot.envs.simple_control:InertialPullEnv',
#)


""" Global Configuration Parameters """
RENDERER_CONFIG = RendererType.POVRAY
5 changes: 5 additions & 0 deletions gym_softrobot/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from enum import Enum

class RendererType(Enum):
POVRAY=1
MATPLOTLIB=2
6 changes: 5 additions & 1 deletion gym_softrobot/debug/render.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import gym
import gym_softrobot

from gym_softrobot.config import RendererType

#gym_softrobot.RENDERER_CONFIG = RendererType.MATPLOTLIB

def main():
env = gym.make('OctoFlat-v0', recording_fps=30)
env = gym.make('OctoArmSingle-v0', recording_fps=30)

observation = env.reset()
for step in range(10):
Expand Down
62 changes: 42 additions & 20 deletions gym_softrobot/envs/octopus/arm_single_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
from elastica.timestepper import extend_stepper_interface
from elastica._calculus import _isnan_check

from gym_softrobot import RENDERER_CONFIG
from gym_softrobot.config import RendererType
from gym_softrobot.envs.octopus.build import build_arm
from gym_softrobot.utils.custom_elastica.callback_func import RodCallBack, RigidCylinderCallBack
from gym_softrobot.utils.render.post_processing import plot_video
from gym_softrobot.utils.render.base_renderer import BaseRenderer, BaseElasticaRendererSession


class BaseSimulator(BaseSystemCollection, Constraints, Connections, Forcing, CallBacks):
Expand Down Expand Up @@ -134,6 +137,7 @@ def reset(self):
# Set Target
self._target = (2-0.5)*self.np_random.random(2) + 0.5
#self._target /= np.linalg.norm(self._target) # I don't see why this is here
print(self._target)

# Initial State
state = self.get_state()
Expand Down Expand Up @@ -252,29 +256,47 @@ def render(self, mode='human', close=False):

if self.viewer is None:
from gym_softrobot.utils.render import pyglet_rendering
from gym_softrobot.utils.render.povray_rendering import Session
self.viewer = pyglet_rendering.SimpleImageViewer(maxwidth=maxwidth)

if self.renderer is None:
# Switch renderer depending on configuration
if RENDERER_CONFIG == RendererType.POVRAY:
from gym_softrobot.utils.render.povray_renderer import Session
elif RENDERER_CONFIG == RendererType.MATPLOTLIB:
from gym_softrobot.utils.render.matplotlib_renderer import Session
else:
raise NotImplementedError("Rendering module is not imported properly")
assert issubclass(Session, BaseRenderer), \
"Rendering module is not properly subclassed"
assert issubclass(Session, BaseElasticaRendererSession), \
"Rendering module is not properly subclassed"
self.viewer = pyglet_rendering.SimpleImageViewer(maxwidth=maxwidth)
self.renderer = Session(width=maxwidth, height=int(maxwidth*aspect_ratio))
self.renderer.add_rods([self.shearable_rod]) # TODO: maybe need add_rod instead
self.renderer.add_point(self._target.tolist()+[0], 0.05)

# Temporary rendering to add side-view
state_image = self.renderer.render(maxwidth, int(maxwidth*aspect_ratio*0.7))
state_image_side = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.0, -0.5],'look_at',[0.0,0,0])
)
state_image_top = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.3, 0.0],'look_at',[0.0,0,0])
)

state_image = np.vstack([
state_image,
np.hstack([state_image_side, state_image_top])
])
self.renderer.add_point(self._target.tolist()+[0], 0.02)

# POVRAY
if RENDERER_CONFIG == RendererType.POVRAY:
state_image = self.renderer.render(maxwidth, int(maxwidth*aspect_ratio*0.7))
state_image_side = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.0, -0.5],'look_at',[0.0,0,0])
)
state_image_top = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.3, 0.0],'look_at',[0.0,0,0])
)

state_image = np.vstack([
state_image,
np.hstack([state_image_side, state_image_top])
])
elif RENDERER_CONFIG == RendererType.MATPLOTLIB:
state_image = self.renderer.render()
else:
raise NotImplementedError("Rendering module is not imported properly")

self.viewer.imshow(state_image)

Expand Down
4 changes: 4 additions & 0 deletions gym_softrobot/envs/octopus/build.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
__doc__="""
Module contains elastica interface to create octopus model.
"""

from typing import Optional, Tuple
import json

Expand Down
2 changes: 1 addition & 1 deletion gym_softrobot/envs/octopus/flat_3d_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def render(self, mode='human', close=False):

if self.viewer is None:
from gym_softrobot.utils.render import pyglet_rendering
from gym_softrobot.utils.render.povray_rendering import Session
from gym_softrobot.utils.render.povray_renderer import Session
self.viewer = pyglet_rendering.SimpleImageViewer(maxwidth=maxwidth)
self.renderer = Session(width=maxwidth, height=maxwidth*3//4)
self.renderer.add_rods(self.shearable_rods)
Expand Down
54 changes: 37 additions & 17 deletions gym_softrobot/envs/octopus/flat_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
from elastica.timestepper import extend_stepper_interface
from elastica._calculus import _isnan_check

from gym_softrobot import RENDERER_CONFIG
from gym_softrobot.config import RendererType
from gym_softrobot.envs.octopus.build import build_octopus
from gym_softrobot.utils.custom_elastica.callback_func import RodCallBack, RigidCylinderCallBack
from gym_softrobot.utils.intersection import intersection
from gym_softrobot.utils.render.post_processing import plot_video
from gym_softrobot.utils.render.base_renderer import BaseRenderer, BaseElasticaRendererSession


class BaseSimulator(BaseSystemCollection, Constraints, Connections, Forcing, CallBacks):
Expand Down Expand Up @@ -343,30 +346,47 @@ def render(self, mode='human', close=False):

if self.viewer is None:
from gym_softrobot.utils.render import pyglet_rendering
from gym_softrobot.utils.render.povray_rendering import Session
self.viewer = pyglet_rendering.SimpleImageViewer(maxwidth=maxwidth)

if self.renderer is None:
# Switch renderer depending on configuration
if RENDERER_CONFIG == RendererType.POVRAY:
from gym_softrobot.utils.render.povray_renderer import Session
elif RENDERER_CONFIG == RendererType.MATPLOTLIB:
from gym_softrobot.utils.render.matplotlib_renderer import Session
else:
raise NotImplementedError("Rendering module is not imported properly")
assert issubclass(Session, BaseRenderer), \
"Rendering module is not properly subclassed"
assert issubclass(Session, BaseElasticaRendererSession), \
"Rendering module is not properly subclassed"
self.renderer = Session(width=maxwidth, height=int(maxwidth*aspect_ratio))
self.renderer.add_rods(self.shearable_rods)
self.renderer.add_rigid_body(self.rigid_rod)
self.renderer.add_point(self._target.tolist()+[0], 0.05)

# Temporary rendering to add side-view
state_image = self.renderer.render(maxwidth, int(maxwidth*aspect_ratio*0.7))
state_image_side = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.0, -0.5],'look_at',[0.0,0,0])
)
state_image_top = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.3, 0.0],'look_at',[0.0,0,0])
)
# POVRAY
if RENDERER_CONFIG == RendererType.POVRAY:
state_image = self.renderer.render(maxwidth, int(maxwidth*aspect_ratio*0.7))
state_image_side = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.0, -0.5],'look_at',[0.0,0,0])
)
state_image_top = self.renderer.render(
maxwidth//2,
int(maxwidth*aspect_ratio*0.3),
camera_param=('location',[0.0, 0.3, 0.0],'look_at',[0.0,0,0])
)

state_image = np.vstack([
state_image,
np.hstack([state_image_side, state_image_top])
])
state_image = np.vstack([
state_image,
np.hstack([state_image_side, state_image_top])
])
elif RENDERER_CONFIG == RendererType.MATPLOTLIB:
state_image = self.renderer.render()
else:
raise NotImplementedError("Rendering module is not imported properly")

self.viewer.imshow(state_image)

Expand Down
2 changes: 1 addition & 1 deletion gym_softrobot/envs/snake/continuum_snake.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def render(self, mode='human', close=False):

if self.viewer is None:
from gym_softrobot.utils.render import pyglet_rendering
from gym_softrobot.utils.render.povray_rendering import Session
from gym_softrobot.utils.render.povray_renderer import Session
self.viewer = pyglet_rendering.SimpleImageViewer(maxwidth=maxwidth)
self.renderer = Session(width=maxwidth, height=int(maxwidth*aspect_ratio))
self.renderer.add_rods(self.shearable_rods)
Expand Down
40 changes: 40 additions & 0 deletions gym_softrobot/utils/render/base_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from abc import ABC, abstractmethod

from gym_softrobot.config import RendererType


class BaseRenderer(ABC):
"""
Renderer should contains the methods below.
"""

@property
@abstractmethod
def type(self) -> RendererType:
pass

@abstractmethod
def render(self):
pass

@abstractmethod
def close(self):
pass

class BaseElasticaRendererSession(ABC):
"""
Elastica-renderer should contains the methods below
"""

@abstractmethod
def add_rods(self):
pass

@abstractmethod
def add_rigid_body(self):
pass

@abstractmethod
def add_point(self):
pass

Loading

0 comments on commit 363dc35

Please sign in to comment.