From 68fde32fb4053d2661d5fe5628d9f527240b8b44 Mon Sep 17 00:00:00 2001
From: hagrid67 <jdhwatson@gmail.com>
Date: Mon, 27 May 2019 16:41:14 +0100
Subject: [PATCH] SVG with PIL

---
 examples/play_model.py         |   4 +-
 flatland/utils/graphics_pil.py | 245 ++++++++++++++++++++++++++++++++-
 flatland/utils/render_qt.py    |  24 +---
 flatland/utils/rendertools.py  |  38 ++---
 flatland/utils/svg.py          |  12 ++
 images/basic-env.npz           | Bin 10007 -> 23895 bytes
 tests/test_player.py           |   6 +-
 tests/test_rendertools.py      |   7 +-
 8 files changed, 287 insertions(+), 49 deletions(-)

diff --git a/examples/play_model.py b/examples/play_model.py
index 7d7ed11..08dadeb 100644
--- a/examples/play_model.py
+++ b/examples/play_model.py
@@ -107,7 +107,7 @@ def max_lt(seq, val):
     return None
 
 
-def main(render=True, delay=0.0, n_trials=3, n_steps=50, sGL="PIL"):
+def main(render=True, delay=0.0, n_trials=3, n_steps=50, sGL="PILSVG"):
     random.seed(1)
     np.random.seed(1)
 
@@ -277,4 +277,4 @@ def main_old(render=True, delay=0.0):
 
 
 if __name__ == "__main__":
-    main(render=True, delay=0)
+    main(render=True, delay=0.5)
diff --git a/flatland/utils/graphics_pil.py b/flatland/utils/graphics_pil.py
index 949628f..25ee27e 100644
--- a/flatland/utils/graphics_pil.py
+++ b/flatland/utils/graphics_pil.py
@@ -4,6 +4,12 @@ from PIL import Image, ImageDraw, ImageTk   # , ImageFont
 import tkinter as tk
 from numpy import array
 import numpy as np
+# from flatland.utils.svg import Track, Zug
+import time
+import io
+from cairosvg import svg2png
+from flatland.core.transitions import RailEnvTransitions
+# from copy import copy
 
 
 class PILGL(GraphicsLayer):
@@ -11,6 +17,7 @@ class PILGL(GraphicsLayer):
         self.nPixCell = 60
         self.yxBase = (0, 0)
         self.linewidth = 4
+        self.nAgentColors = 1  # overridden in loadAgent
         # self.tile_size = self.nPixCell
 
         self.width = width
@@ -30,6 +37,7 @@ class PILGL(GraphicsLayer):
         self.window_open = False
         # self.bShow = show
         self.firstFrame = True
+        self.create_layers()
         self.beginFrame()
 
     def plot(self, gX, gY, color=None, linewidth=3, layer=0, opacity=255, **kwargs):
@@ -49,6 +57,20 @@ class PILGL(GraphicsLayer):
         for x, y in gPoints:
             self.draws[layer].rectangle([(x - r, y - r), (x + r, y + r)], fill=color, outline=color)
 
+    def drawImageXY(self, pil_img, xyPixLeftTop, layer=0):
+        # self.layers[layer].alpha_composite(pil_img, offset=xyPixLeftTop)
+        if (pil_img.mode == "RGBA"): 
+            pil_mask = pil_img
+        else:
+            pil_mask = None
+            # print(pil_img, pil_img.mode, xyPixLeftTop, layer)
+        
+        self.layers[layer].paste(pil_img, xyPixLeftTop, pil_mask)
+
+    def drawImageRC(self, pil_img, rcTopLeft, layer=0):
+        xyPixLeftTop = tuple((array(rcTopLeft) * self.nPixCell)[[1, 0]])
+        self.drawImageXY(pil_img, xyPixLeftTop, layer=layer)
+
     def open_window(self):
         assert self.window_open is False, "Window is already open!"
         self.window = tk.Tk()
@@ -66,8 +88,8 @@ class PILGL(GraphicsLayer):
         pass
 
     def beginFrame(self):
-        self.create_layer(0)
-        self.create_layer(1)
+        # Create a new agent layer
+        self.create_layer(iLayer=1, clear=True)
 
     def show(self, block=False):
         img = self.alpha_composite_layers()
@@ -78,6 +100,7 @@ class PILGL(GraphicsLayer):
         tkimg = ImageTk.PhotoImage(img)
         
         if self.firstFrame:
+            # Do TK actions for a new panel (not sure what they really do)
             self.panel = tk.Label(self.window, image=tkimg)
             self.panel.pack(side="bottom", fill="both", expand="yes")
         else:
@@ -109,7 +132,8 @@ class PILGL(GraphicsLayer):
         img = Image.new("RGBA", (self.widthPx, self.heightPx), (255, 255, 255, opacity))
         return img
 
-    def create_layer(self, iLayer=0):
+    def create_layer(self, iLayer=0, clear=True):
+        # If we don't have the layers already, create them
         if len(self.layers) <= iLayer:
             for i in range(len(self.layers), iLayer+1):
                 if i == 0:
@@ -120,7 +144,216 @@ class PILGL(GraphicsLayer):
                 self.layers.append(img)
                 self.draws.append(ImageDraw.Draw(img))
         else:
-            opacity = 0 if iLayer > 0 else 255
-            self.layers[iLayer] = img = self.create_image(opacity)
-            self.draws[iLayer] = ImageDraw.Draw(img)
+            # We do already have this iLayer.  Clear it if requested.
+            if clear:
+                opacity = 0 if iLayer > 0 else 255
+                self.layers[iLayer] = img = self.create_image(opacity)
+                # We also need to maintain a Draw object for each layer
+                self.draws[iLayer] = ImageDraw.Draw(img)
+
+    def create_layers(self, clear=True):        
+        self.create_layer(0, clear=clear)
+        self.create_layer(1, clear=clear)
+
+
+class PILSVG(PILGL):
+    def __init__(self, width, height):
+        print(self, type(self))
+        oSuper = super()
+        print(oSuper, type(oSuper))
+        oSuper.__init__(width, height)
+
+        # self.track = self.track = Track()
+        # self.lwTrack = []
+        # self.zug = Zug()
+
+        self.lwAgents = []
+        self.agents_prev = []
+
+        self.loadRailSVGs()
+        self.loadAgentSVGs()
+
+    def is_raster(self):
+        return False
+
+    def processEvents(self):
+        # self.app.processEvents()
+        time.sleep(0.001)
+
+    def clear_rails(self):
+        print("Clear rails")
+        self.create_layers()
+        self.clear_agents()
+
+    def clear_agents(self):
+        # print("Clear Agents: ", len(self.lwAgents))
+        for wAgent in self.lwAgents:
+            self.layout.removeWidget(wAgent)
+        self.lwAgents = []
+        self.agents_prev = []
+
+    def pilFromSvgFile(self, sfPath):
+        with open(sfPath, "r") as fIn:
+            bytesPNG = svg2png(file_obj=fIn, output_height=self.nPixCell, output_width=self.nPixCell)
+        
+        with io.BytesIO(bytesPNG) as fIn:
+            pil_img = Image.open(fIn)
+            pil_img.load()
+            # print(pil_img.mode)
+        
+        return pil_img
+
+    def pilFromSvgBytes(self, bytesSVG):
+        bytesPNG = svg2png(bytesSVG, output_height=self.nPixCell, output_width=self.nPixCell)
+        with io.BytesIO(bytesPNG) as fIn:
+            pil_img = Image.open(fIn)
+            return pil_img
+
+    def loadRailSVGs(self):
+        """ Load the rail SVG images, apply rotations, and store as PIL images.
+        """
+        dFiles = {
+            "": "Background_#91D1DD.svg",
+            "WE": "Gleis_Deadend.svg",
+            "WW EE NN SS": "Gleis_Diamond_Crossing.svg",
+            "WW EE": "Gleis_horizontal.svg",
+            "EN SW": "Gleis_Kurve_oben_links.svg",
+            "WN SE": "Gleis_Kurve_oben_rechts.svg",
+            "ES NW": "Gleis_Kurve_unten_links.svg",
+            "NE WS": "Gleis_Kurve_unten_rechts.svg",
+            "NN SS": "Gleis_vertikal.svg",
+            "NN SS EE WW ES NW SE WN": "Weiche_Double_Slip.svg",
+            "EE WW EN SW": "Weiche_horizontal_oben_links.svg",
+            "EE WW SE WN": "Weiche_horizontal_oben_rechts.svg",
+            "EE WW ES NW": "Weiche_horizontal_unten_links.svg",
+            "EE WW NE WS": "Weiche_horizontal_unten_rechts.svg",
+            "NN SS EE WW NW ES": "Weiche_Single_Slip.svg",
+            "NE NW ES WS": "Weiche_Symetrical.svg",
+            "NN SS EN SW": "Weiche_vertikal_oben_links.svg",
+            "NN SS SE WN": "Weiche_vertikal_oben_rechts.svg",
+            "NN SS NW ES": "Weiche_vertikal_unten_links.svg",
+            "NN SS NE WS": "Weiche_vertikal_unten_rechts.svg"}
+
+        self.dPil = {}
+
+        transitions = RailEnvTransitions()
+
+        lDirs = list("NESW")
+
+        # svgBG = SVG("./svg/Background_#91D1DD.svg")
+
+        for sTrans, sFile in dFiles.items():
+            sPathSvg = "./svg/" + sFile
+
+            # Translate the ascii transition descption in the format  "NE WS" to the 
+            # binary list of transitions as per RailEnv - NESW (in) x NESW (out)
+            lTrans16 = ["0"] * 16
+            for sTran in sTrans.split(" "):
+                if len(sTran) == 2:
+                    iDirIn = lDirs.index(sTran[0])
+                    iDirOut = lDirs.index(sTran[1])
+                    iTrans = 4 * iDirIn + iDirOut
+                    lTrans16[iTrans] = "1"
+            sTrans16 = "".join(lTrans16)
+            binTrans = int(sTrans16, 2)
+            print(sTrans, sTrans16, sFile)
+
+            # Merge the transition svg image with the background colour.
+            # This is a shortcut / hack and will need re-working.
+            # if binTrans > 0:
+            #    svg = svg.merge(svgBG)
+
+            pilRail = self.pilFromSvgFile(sPathSvg)
+            self.dPil[binTrans] = pilRail
+
+            # Rotate both the transition binary and the image and save in the dict
+            for nRot in [90, 180, 270]:
+                binTrans2 = transitions.rotate_transition(binTrans, nRot)
+
+                # PIL rotates anticlockwise for positive theta
+                pilRail2 = pilRail.rotate(-nRot)
+                self.dPil[binTrans2] = pilRail2
+
+    def setRailAt(self, row, col, binTrans):
+        if binTrans in self.dPil:
+            pilTrack = self.dPil[binTrans]
+            self.drawImageRC(pilTrack, (row, col))
+        else:
+            print("Illegal rail:", row, col, format(binTrans, "#018b")[2:])
+
+    def rgb_s2i(self, sRGB):
+        """ convert a hex RGB string like 0091ea to 3-tuple of ints """
+        return tuple(int(sRGB[iRGB * 2:iRGB * 2 + 2], 16) for iRGB in [0, 1, 2])
+
+    def loadAgentSVGs(self):
+
+        # Seed initial train/zug files indexed by tuple(iDirIn, iDirOut):
+        dDirsFile = {
+            (0, 0): "svg/Zug_Gleis_#0091ea.svg",
+            (1, 2): "svg/Zug_1_Weiche_#0091ea.svg",
+            (0, 3): "svg/Zug_2_Weiche_#0091ea.svg"
+            }
+
+        sColors = "d50000#c51162#aa00ff#6200ea#304ffe#2962ff#0091ea#00b8d4#00bfa5#00c853" + \
+            "#64dd17#aeea00#ffd600#ffab00#ff6d00#ff3d00#5d4037#455a64"
+        lColors = sColors.split("#")
+        self.nAgentColors = len(lColors)
+
+        # "paint" color of the train images we load
+        a_base_color = self.rgb_s2i("0091ea")
+
+        self.dPilZug = {}
+
+        for tDirs, sPathSvg in dDirsFile.items():
+            iDirIn, iDirOut = tDirs
+            
+            pilZug = self.pilFromSvgFile(sPathSvg)
+
+            # Rotate both the directions and the image and save in the dict
+            for iDirRot in range(4):
+                nDegRot = iDirRot * 90
+                iDirIn2 = (iDirIn + iDirRot) % 4
+                iDirOut2 = (iDirOut + iDirRot) % 4
+
+                # PIL rotates anticlockwise for positive theta
+                pilZug2 = pilZug.rotate(-nDegRot)
+                rgbaZug2 = array(pilZug2)
+
+                for iColor, sColor in enumerate(lColors):
+                    tnNewColor = self.rgb_s2i(sColor)
+                    xy_color_mask = np.all(rgbaZug2[:, :, 0:3] - a_base_color == 0, axis=2)
+                    rgbaZug3 = np.copy(rgbaZug2)
+                    rgbaZug3[xy_color_mask, 0:3] = tnNewColor
+                    self.dPilZug[(iDirIn2, iDirOut2, iColor)] = Image.fromarray(rgbaZug3)
+
+    def setAgentAt(self, iAgent, row, col, iDirIn, iDirOut, color=None):
+        delta_dir = (iDirOut - iDirIn) % 4
+        iColor = iAgent % self.nAgentColors
+        # when flipping direction at a dead end, use the "iDirOut" direction.
+        if delta_dir == 2:
+            iDirIn = iDirOut
+        pilZug = self.dPilZug[(iDirIn % 4, iDirOut % 4, iColor)]
+        self.drawImageRC(pilZug, (row, col), layer=1)
+
+
+def main2():
+    gl = PILSVG(10, 10)
+    for i in range(10):
+        gl.beginFrame()
+        gl.plot([3 + i, 4], [-4 - i, -5], color="r")
+        gl.endFrame()
+        time.sleep(1)
+
+
+def main():
+    gl = PILSVG(width=10, height=10)
+
+    for i in range(1000):
+        gl.processEvents()
+        time.sleep(0.1)
+    time.sleep(1)
+
+
+if __name__ == "__main__":
+    main()
 
diff --git a/flatland/utils/render_qt.py b/flatland/utils/render_qt.py
index 73b8ca7..a8ccc78 100644
--- a/flatland/utils/render_qt.py
+++ b/flatland/utils/render_qt.py
@@ -16,10 +16,11 @@ def transform_string_svg(sSVG):
     bySVG = bytearray(sSVG, encoding='utf-8')
     return bySVG
 
+
 def create_QtSvgWidget_from_svg_string(sSVG):
     svgWidget = QtSvg.QSvgWidget()
     ret = svgWidget.renderer().load(transform_string_svg(sSVG))
-    if ret == False:
+    if ret is False:
         print("create_QtSvgWidget_from_svg_string : failed to parse:", sSVG)
     return svgWidget
 
@@ -132,26 +133,7 @@ class QTSVG(GraphicsLayer):
         self.lwAgents = []
         self.agents_prev = []
 
-        svgWidget = None
-
-        iArt = 0
-        iCol = 0
-        iRow = 0
-        nCols = 10
-
-        if False:
-            for binTrans in self.track.dSvg.keys():
-                sSVG = self.track.dSvg[binTrans].to_string()
-                self.layout.addWidget(create_QtSvgWidget_from_svg_string(sSVG), iRow, iCol)
-
-                iArt += 1
-                iRow = int(iArt / nCols)
-                iCol = iArt % nCols
-
-            svgWidget2 = QtSvg.QSvgWidget()
-            svgWidget2.renderer().load(bySVG)
-
-            self.layout.addWidget(svgWidget2, 0, 0)
+        # svgWidget = None
 
     def is_raster(self):
         return False
diff --git a/flatland/utils/rendertools.py b/flatland/utils/rendertools.py
index 1aa1748..9b99d1e 100644
--- a/flatland/utils/rendertools.py
+++ b/flatland/utils/rendertools.py
@@ -3,14 +3,12 @@ from collections import deque
 
 # import xarray as xr
 import matplotlib.pyplot as plt
-import numpy as np
-from numpy import array
-from recordtype import recordtype
-
-from flatland.utils.graphics_layer import GraphicsLayer
-from flatland.utils.graphics_pil import PILGL
 from flatland.utils.render_qt import QTGL, QTSVG
-
+from flatland.utils.graphics_pil import PILGL, PILSVG
+from flatland.utils.graphics_layer import GraphicsLayer
+import recordtype
+from numpy import array
+import numpy as np
 
 # TODO: suggested renaming to RailEnvRenderTool, as it will only work with RailEnv!
 
@@ -133,6 +131,8 @@ class RenderTool(object):
             self.gl = QTGL(env.width, env.height)
         elif gl == "PIL":
             self.gl = PILGL(env.width, env.height)
+        elif gl == "PILSVG":
+            self.gl = PILSVG(env.width, env.height)
         elif gl == "QTSVG":
             self.gl = QTSVG(env.width, env.height)
 
@@ -618,11 +618,11 @@ class RenderTool(object):
         """
 
         if not self.gl.is_raster():
-            self.renderEnv2(show, curves, spacing,
-                            arrows, agents, renderobs,sRailColor,
-                            frames, iEpisode, iStep,
-                            iSelectedAgent, action_dict)
-
+            self.renderEnv2(show=show, curves=curves, spacing=spacing,
+                            arrows=arrows, agents=agents, show_observations=show_observations,
+                            sRailColor=sRailColor,
+                            frames=frames, iEpisode=iEpisode, iStep=iStep,
+                            iSelectedAgent=iSelectedAgent, action_dict=action_dict)
             return
 
         if type(self.gl) in (QTGL, PILGL):
@@ -728,9 +728,11 @@ class RenderTool(object):
 
             gP0 = array([gX1, gY1, gZ1])
 
-    def renderEnv2(self, show=False, curves=True, spacing=False, arrows=False, agents=True, renderobs=True,
-                   sRailColor="gray", frames=False, iEpisode=None, iStep=None, iSelectedAgent=None,
-                   action_dict=dict()):
+    def renderEnv2(
+        self, show=False, curves=True, spacing=False, arrows=False, agents=True, 
+            show_observations=True, sRailColor="gray",
+            frames=False, iEpisode=None, iStep=None, iSelectedAgent=None,
+            action_dict=dict()):
         """
         Draw the environment using matplotlib.
         Draw into the figure if provided.
@@ -741,6 +743,8 @@ class RenderTool(object):
 
         env = self.env
 
+        self.gl.beginFrame()
+
         if self.new_rail:
             self.new_rail = False
             self.gl.clear_rails()
@@ -766,7 +770,6 @@ class RenderTool(object):
                 iAction = action_dict[iAgent]
                 new_direction, action_isValid = self.env.check_action(agent, iAction)
 
-
             # ** TODO ***
             # why should we only update if the action is valid ?
             if True:
@@ -779,7 +782,8 @@ class RenderTool(object):
             else:
                 self.gl.setAgentAt(iAgent, *agent.position, agent.direction, new_direction, color=oColor)
 
-        self.gl.show()
+        if show:
+            self.gl.show()
         for i in range(3):
             self.gl.processEvents()
 
diff --git a/flatland/utils/svg.py b/flatland/utils/svg.py
index fb8b987..89730af 100644
--- a/flatland/utils/svg.py
+++ b/flatland/utils/svg.py
@@ -104,6 +104,13 @@ class Zug(object):
 
 
 class Track(object):
+    """ Class to load and hold SVG track images.
+        Creates a mapping between
+        - cell entry and exit directions (ie transitions), and
+        - specific images provided by the SBB graphic artist.
+        The directions and images are also rotated by 90, 180 & 270 degrees.
+        (There is some redundancy in this process, given the images provided)
+    """
     def __init__(self):
         dFiles = {
             "": "Background_#9CCB89.svg",
@@ -138,6 +145,8 @@ class Track(object):
         for sTrans, sFile in dFiles.items():
             svg = SVG("./svg/" + sFile)
 
+            # Translate the ascii transition descption in the format  "NE WS" to the 
+            # binary list of transitions as per RailEnv - NESW (in) x NESW (out)
             lTrans16 = ["0"] * 16
             for sTran in sTrans.split(" "):
                 if len(sTran) == 2:
@@ -149,11 +158,14 @@ class Track(object):
             binTrans = int(sTrans16, 2)
             print(sTrans, sTrans16, sFile)
 
+            # Merge the transition svg image with the background colour.
+            # This is a shortcut / hack and will need re-working.
             if binTrans > 0:
                 svg = svg.merge(svgBG)
 
             self.dSvg[binTrans] = svg
 
+            # Rotate both the transition binary and the image and save in the dict
             for nRot in [90, 180, 270]:
                 binTrans2 = transitions.rotate_transition(binTrans, nRot)
                 svg2 = svg.copy()
diff --git a/images/basic-env.npz b/images/basic-env.npz
index 8ffaf023e1116b0c92702212ddb04c71b82f0655..e645113154b9e953575a12dcbadf4f9f3195b4ad 100644
GIT binary patch
literal 23895
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
WX|4h<Yv$ou2L6))pX-gGfBiq-dZ5Mt

literal 10007
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

diff --git a/tests/test_player.py b/tests/test_player.py
index a0e580b..668afb9 100644
--- a/tests/test_player.py
+++ b/tests/test_player.py
@@ -4,5 +4,9 @@ from examples.play_model import main
 
 
 def test_main():
-    main(render=True, n_steps=20, n_trials=2, sGL="PIL")
+    main(render=True, n_steps=20, n_trials=2, sGL="PILSVG")
+    # main(render=True, n_steps=20, n_trials=2, sGL="PIL")
 
+
+if __name__ == "__main__":
+    test_main()
diff --git a/tests/test_rendertools.py b/tests/test_rendertools.py
index 8204a30..1bfce03 100644
--- a/tests/test_rendertools.py
+++ b/tests/test_rendertools.py
@@ -12,6 +12,7 @@ import numpy as np
 import flatland.utils.rendertools as rt
 from flatland.envs.observations import TreeObsForRailEnv
 from flatland.envs.rail_env import RailEnv, random_rail_generator
+from flatland.envs.generators import empty_rail_generator
 
 
 def checkFrozenImage(oRT, sFileImage, resave=False):
@@ -39,14 +40,15 @@ def test_render_env(save_new_images=False):
     # random.seed(100)
     np.random.seed(100)
     oEnv = RailEnv(width=10, height=10,
-                   rail_generator=random_rail_generator(),
+                   # rail_generator=random_rail_generator(),
+                   rail_generator=empty_rail_generator(),
                    number_of_agents=0,
                    # obs_builder_object=GlobalObsForRailEnv())
                    obs_builder_object=TreeObsForRailEnv(max_depth=2)
                    )
     sfTestEnv = "env-data/tests/test1.npy"
     oEnv.rail.load_transition_map(sfTestEnv)
-    oRT = rt.RenderTool(oEnv, gl="PIL", show=False)
+    oRT = rt.RenderTool(oEnv, gl="PILSVG", show=False)
     oRT.renderEnv(show=False)
 
     checkFrozenImage(oRT, "basic-env.npz", resave=save_new_images)
@@ -82,6 +84,7 @@ def main():
         test_render_env(save_new_images=True)
     else:
         print("Run 'python test_rendertools.py save' to regenerate images")
+        test_render_env()
 
 
 if __name__ == "__main__":
-- 
GitLab