Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • flatland/flatland
  • stefan_otte/flatland
  • jiaodaxiaozi/flatland
  • sfwatergit/flatland
  • utozx126/flatland
  • ChenKuanSun/flatland
  • ashivani/flatland
  • minhhoa/flatland
  • pranjal_dhole/flatland
  • darthgera123/flatland
  • rivesunder/flatland
  • thomaslecat/flatland
  • joel_joseph/flatland
  • kchour/flatland
  • alex_zharichenko/flatland
  • yoogottamk/flatland
  • troye_fang/flatland
  • elrichgro/flatland
  • jun_jin/flatland
  • nimishsantosh107/flatland
20 results
Show changes
Showing
with 3043 additions and 1157 deletions
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 240" style="enable-background:new 0 0 240 240;" xml:space="preserve">
<style type="text/css">
.st0{fill:#444444;}
.st1{fill:#CDCDCD;}
.st2{fill:#0091EA;}
.st3{fill:#767676;}
.st4{fill:#5B5A5A;}
.st5{fill:#FBFBFB;}
.st6{fill:none;}
</style>
<path class="st0" d="M110.77,195.57c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,195.38,110.1,195.54,110.77,195.57z"/>
<path class="st0" d="M110.77,141.88c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,141.7,110.1,141.86,110.77,141.88z"/>
<path class="st0" d="M121.47,131.12h-2.63c-0.22,0-0.46,0.19-0.54,0.44l-1.07,3.32h5.85l-1.07-3.32
C121.93,131.3,121.69,131.12,121.47,131.12z"/>
<g>
<g>
<path class="st0" d="M115.44,135.6c0.09-0.15-0.03-0.31-0.16-0.49c-0.04-0.06-0.11-0.15-0.13-0.21c0.06-0.02,0.15-0.03,0.22-0.04
c0.22-0.04,0.44-0.07,0.48-0.24c0.05-0.16-0.1-0.3-0.27-0.44c-0.05-0.05-0.13-0.12-0.17-0.17c0.05-0.03,0.15-0.06,0.21-0.08
c0.21-0.07,0.41-0.14,0.44-0.31c0.02-0.17-0.14-0.28-0.32-0.41c-0.06-0.04-0.14-0.1-0.19-0.15c0.05-0.03,0.14-0.07,0.2-0.1
c0.2-0.09,0.39-0.18,0.4-0.35c0.01-0.17-0.17-0.27-0.36-0.38c-0.06-0.03-0.15-0.09-0.2-0.13c0.05-0.04,0.14-0.08,0.19-0.12
c0.19-0.11,0.38-0.21,0.37-0.37c0-0.17-0.19-0.26-0.38-0.36c-0.06-0.03-0.15-0.08-0.2-0.11c0.05-0.04,0.13-0.09,0.19-0.13
c0.19-0.12,0.36-0.23,0.35-0.39c-0.01-0.17-0.2-0.25-0.4-0.34c-0.06-0.03-0.15-0.07-0.21-0.1c0.04-0.04,0.13-0.1,0.18-0.14
c0.18-0.13,0.35-0.25,0.33-0.41c-0.02-0.17-0.22-0.24-0.42-0.32c-0.06-0.02-0.16-0.06-0.21-0.09c0.04-0.04,0.12-0.11,0.18-0.15
c0.17-0.13,0.33-0.26,0.3-0.43c-0.03-0.17-0.23-0.23-0.44-0.29c-0.06-0.02-0.16-0.05-0.21-0.07c0.04-0.05,0.12-0.11,0.17-0.16
c0.16-0.14,0.32-0.28,0.28-0.44c-0.04-0.16-0.24-0.22-0.46-0.27c-0.06-0.02-0.16-0.04-0.22-0.06c0.04-0.05,0.11-0.12,0.16-0.17
c0.15-0.15,0.3-0.3,0.25-0.46c-0.05-0.16-0.26-0.2-0.48-0.24c-0.06-0.01-0.16-0.03-0.22-0.05c0.03-0.05,0.1-0.14,0.15-0.19
c0.14-0.17,0.27-0.32,0.21-0.47c-0.07-0.16-0.28-0.17-0.51-0.19c-0.08-0.01-0.22-0.01-0.26-0.03l-0.23,0.16
c0.07,0.14,0.26,0.15,0.48,0.17c0.07,0,0.17,0.01,0.23,0.02c-0.03,0.05-0.09,0.12-0.13,0.17c-0.14,0.17-0.27,0.33-0.22,0.48
c0.06,0.15,0.24,0.18,0.46,0.22c0.07,0.01,0.16,0.03,0.22,0.05c-0.04,0.05-0.1,0.11-0.15,0.16c-0.15,0.15-0.3,0.3-0.25,0.46
c0.04,0.16,0.23,0.2,0.45,0.26c0.06,0.02,0.16,0.04,0.22,0.06c-0.04,0.05-0.11,0.11-0.16,0.15c-0.16,0.14-0.32,0.28-0.28,0.44
c0.03,0.16,0.22,0.22,0.43,0.28c0.06,0.02,0.16,0.05,0.21,0.07c-0.04,0.04-0.12,0.1-0.17,0.14c-0.17,0.13-0.33,0.26-0.31,0.42
c0.03,0.16,0.22,0.23,0.42,0.31c0.06,0.02,0.15,0.06,0.21,0.09c-0.05,0.04-0.13,0.1-0.18,0.14c-0.18,0.12-0.34,0.24-0.33,0.41
c0.02,0.16,0.2,0.25,0.4,0.33c0.06,0.03,0.15,0.07,0.2,0.1c-0.05,0.04-0.13,0.09-0.19,0.13c-0.18,0.12-0.35,0.22-0.35,0.39
c0.01,0.16,0.19,0.26,0.38,0.35c0.06,0.03,0.14,0.07,0.19,0.11c-0.05,0.04-0.14,0.08-0.19,0.12c-0.19,0.1-0.37,0.2-0.37,0.36
c0,0.17,0.17,0.27,0.36,0.38c0.05,0.03,0.14,0.08,0.19,0.12c-0.05,0.03-0.14,0.07-0.2,0.1c-0.2,0.09-0.38,0.17-0.39,0.33
c-0.02,0.17,0.15,0.29,0.33,0.41c0.05,0.04,0.13,0.09,0.17,0.13c-0.06,0.03-0.15,0.06-0.21,0.08c-0.21,0.07-0.39,0.13-0.42,0.29
c-0.03,0.16,0.12,0.3,0.28,0.45c0.05,0.04,0.12,0.11,0.16,0.15c-0.05,0.01-0.16,0.02-0.23,0.04c-0.21,0.04-0.4,0.07-0.45,0.21
c-0.06,0.15,0.06,0.32,0.18,0.49c0.05,0.07,0.13,0.18,0.14,0.22c0-0.01,0-0.02,0.01-0.04L115.44,135.6z"/>
</g>
<g>
<path class="st0" d="M124.86,135.6l0.25-0.14c0.01,0.02,0.01,0.04,0.01,0.04c0.01-0.04,0.09-0.15,0.14-0.22
c0.13-0.18,0.24-0.34,0.18-0.49c-0.06-0.14-0.24-0.17-0.45-0.21c-0.07-0.01-0.16-0.03-0.22-0.05c0.04-0.04,0.11-0.1,0.15-0.15
c0.16-0.15,0.31-0.29,0.28-0.45c-0.03-0.16-0.21-0.22-0.42-0.29c-0.06-0.02-0.16-0.05-0.21-0.08c0.04-0.04,0.12-0.1,0.17-0.13
c0.18-0.13,0.34-0.25,0.33-0.41c-0.02-0.16-0.19-0.24-0.39-0.33c-0.06-0.03-0.15-0.07-0.2-0.1c0.05-0.04,0.13-0.09,0.19-0.12
c0.19-0.11,0.36-0.21,0.36-0.38c0-0.16-0.18-0.26-0.37-0.36c-0.06-0.03-0.15-0.08-0.2-0.12c0.05-0.03,0.14-0.08,0.2-0.11
c0.19-0.1,0.38-0.19,0.38-0.35c0.01-0.16-0.16-0.27-0.35-0.39c-0.06-0.04-0.14-0.09-0.19-0.13c0.05-0.03,0.14-0.07,0.2-0.1
c0.2-0.09,0.39-0.17,0.4-0.33c0.02-0.16-0.15-0.28-0.33-0.41c-0.05-0.04-0.13-0.09-0.18-0.14c0.05-0.03,0.15-0.06,0.21-0.09
c0.2-0.08,0.39-0.15,0.42-0.31c0.03-0.16-0.14-0.29-0.31-0.42c-0.05-0.04-0.13-0.1-0.17-0.14c0.06-0.03,0.15-0.05,0.21-0.07
c0.21-0.07,0.4-0.12,0.43-0.28c0.03-0.16-0.12-0.3-0.28-0.44c-0.05-0.04-0.12-0.1-0.16-0.15c0.06-0.02,0.15-0.05,0.22-0.06
c0.22-0.05,0.4-0.1,0.45-0.26c0.04-0.16-0.1-0.31-0.26-0.46c-0.04-0.04-0.11-0.11-0.15-0.16c0.06-0.02,0.16-0.04,0.22-0.05
c0.22-0.04,0.41-0.07,0.46-0.22c0.06-0.16-0.08-0.31-0.22-0.48c-0.04-0.05-0.11-0.13-0.14-0.18c0.05,0,0.16-0.01,0.24-0.01
c0.22-0.01,0.4-0.02,0.48-0.17l-0.26-0.13c0.01-0.02,0.02-0.02,0.02-0.03c-0.03,0.02-0.18,0.03-0.26,0.03
c-0.22,0.01-0.44,0.03-0.51,0.19c-0.06,0.15,0.06,0.3,0.2,0.47c0.04,0.05,0.11,0.13,0.15,0.19c-0.06,0.02-0.15,0.03-0.22,0.05
c-0.22,0.04-0.43,0.07-0.48,0.24c-0.05,0.16,0.09,0.3,0.25,0.46c0.05,0.05,0.12,0.12,0.16,0.17c-0.06,0.02-0.15,0.05-0.22,0.06
c-0.21,0.05-0.42,0.1-0.46,0.27c-0.04,0.16,0.12,0.3,0.28,0.44c0.05,0.04,0.13,0.11,0.17,0.16c-0.06,0.03-0.15,0.06-0.21,0.07
c-0.21,0.07-0.41,0.13-0.44,0.29c-0.03,0.16,0.13,0.29,0.3,0.43c0.05,0.04,0.13,0.1,0.17,0.15c-0.05,0.03-0.15,0.06-0.21,0.09
c-0.2,0.08-0.4,0.15-0.42,0.32c-0.02,0.17,0.15,0.29,0.33,0.41c0.05,0.04,0.14,0.1,0.18,0.14c-0.05,0.03-0.15,0.07-0.21,0.1
c-0.2,0.09-0.39,0.17-0.4,0.34c-0.01,0.17,0.16,0.28,0.35,0.4c0.06,0.04,0.14,0.09,0.19,0.13c-0.05,0.04-0.14,0.08-0.2,0.11
c-0.19,0.1-0.38,0.19-0.38,0.36c0,0.17,0.18,0.27,0.37,0.37c0.06,0.03,0.15,0.08,0.19,0.12c-0.05,0.04-0.14,0.09-0.2,0.13
c-0.19,0.11-0.36,0.21-0.36,0.38c0.01,0.17,0.2,0.26,0.4,0.35c0.06,0.03,0.15,0.07,0.2,0.1c-0.04,0.04-0.13,0.11-0.19,0.15
c-0.18,0.13-0.34,0.25-0.32,0.41c0.02,0.17,0.22,0.24,0.44,0.31c0.06,0.02,0.16,0.05,0.21,0.08c-0.04,0.05-0.12,0.12-0.17,0.17
c-0.16,0.15-0.31,0.28-0.27,0.44c0.05,0.17,0.26,0.21,0.48,0.24c0.06,0.01,0.16,0.03,0.22,0.04c-0.03,0.06-0.09,0.15-0.13,0.21
C124.9,135.28,124.78,135.45,124.86,135.6z"/>
</g>
</g>
<path class="st1" d="M131.2,137.35v53.99c0,1.77-1.44,3.21-3.21,3.21H112.5c-1.77,0-3.21-1.44-3.21-3.21v-53.99
c0-1.74,1.43-3.19,3.21-3.21h15.49C129.77,134.16,131.21,135.6,131.2,137.35z"/>
<path class="st2" d="M129.66,138.81v51.41c0,1.74-1.44,3.14-3.21,3.14h-12.42c-1.77,0-3.21-1.41-3.21-3.14v-51.41
c-0.01-1.48,1.23-2.69,2.76-2.7h13.32C128.43,136.13,129.67,137.34,129.66,138.81z"/>
<path class="st3" d="M114.14,152.96h12.02c0.89,0,1.6,0.72,1.6,1.6v1.12c0,0.89-0.72,1.6-1.6,1.6h-12.02c-0.89,0-1.6-0.72-1.6-1.6
v-1.12C112.54,153.68,113.26,152.96,114.14,152.96z"/>
<rect x="124.6" y="157.29" class="st4" width="0.29" height="36.07"/>
<rect x="115.48" y="157.29" class="st4" width="0.29" height="36.07"/>
<rect x="126.35" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="125.4" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="124.45" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="123.5" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="113.5" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="114.45" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="115.4" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="116.35" y="155.88" class="st4" width="0.45" height="0.45"/>
<path class="st0" d="M110.77,129.27c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,129.09,110.1,129.25,110.77,129.27z"/>
<path class="st0" d="M121.47,131.12h-2.63c-0.22,0-0.46-0.19-0.54-0.44l-1.07-3.32h5.85l-1.07,3.32
C121.93,130.93,121.69,131.12,121.47,131.12z"/>
<path class="st1" d="M112.5,128.09c5.16,0,10.33,0,15.49,0c1.78-0.02,3.22-1.45,3.21-3.21c0-24.98,0-36.72,0-61.7
c0-5.61-2.63-18.92-10.95-18.91l0,0c-8.32-0.01-10.95,13-10.95,18.91c0,24.98,0,36.72,0,61.7
C109.29,126.63,110.72,128.07,112.5,128.09z"/>
<path class="st2" d="M113.59,126.12c4.44,0,8.88,0,13.32,0c1.53-0.01,2.76-1.25,2.76-2.76c0-21.47,0-29.71,0-51.18
c0.21-4.9-2.26-16.26-9.42-16.26l0,0c-7.15,0-9.63,11.36-9.42,16.26c0,21.47,0,29.71,0,51.18
C110.82,124.87,112.06,126.11,113.59,126.12z"/>
<path class="st0" d="M129.68,65.06c0-3.83-3.6-11.58-9.43-11.58v-6.65C125.49,46.83,129.68,52.8,129.68,65.06z"/>
<path class="st0" d="M110.82,65.06c0-3.83,3.6-11.58,9.43-11.58v-6.65C115,46.83,110.82,52.8,110.82,65.06z"/>
<path class="st3" d="M114.14,82.87h12.02c0.89,0,1.6,0.72,1.6,1.6v1.12c0,0.89-0.72,1.6-1.6,1.6h-12.02c-0.89,0-1.6-0.72-1.6-1.6
v-1.12C112.54,83.59,113.26,82.87,114.14,82.87z"/>
<rect x="124.6" y="87.21" class="st4" width="0.29" height="38.91"/>
<rect x="115.48" y="87.21" class="st4" width="0.29" height="38.91"/>
<rect x="126.35" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="125.4" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="124.45" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="123.5" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="113.5" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="114.45" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="115.4" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="116.35" y="85.79" class="st4" width="0.45" height="0.45"/>
<path class="st5" d="M128.74,51.72c0.15,0.42-0.22,1.25-0.61,1.09c-0.38-0.16-1.22-3.62-1.06-3.94
C127.23,48.54,128.61,51.36,128.74,51.72z"/>
<path class="st5" d="M111.75,51.72c-0.15,0.42,0.22,1.25,0.61,1.09c0.38-0.16,1.22-3.62,1.06-3.94
C113.26,48.54,111.88,51.36,111.75,51.72z"/>
<g>
<rect class="st6" width="240" height="240"/>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 240" style="enable-background:new 0 0 240 240;" xml:space="preserve">
<style type="text/css">
.st0{fill:#444444;}
.st1{fill:#CDCDCD;}
.st2{fill:#00C853;}
.st3{fill:#767676;}
.st4{fill:#5B5A5A;}
.st5{fill:#FBFBFB;}
.st6{fill:none;}
</style>
<path class="st0" d="M110.77,195.57c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,195.38,110.1,195.54,110.77,195.57z"/>
<path class="st0" d="M110.77,141.88c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,141.7,110.1,141.86,110.77,141.88z"/>
<path class="st0" d="M121.47,131.12h-2.63c-0.22,0-0.46,0.19-0.54,0.44l-1.07,3.32h5.85l-1.07-3.32
C121.93,131.3,121.69,131.12,121.47,131.12z"/>
<g>
<g>
<path class="st0" d="M115.44,135.6c0.09-0.15-0.03-0.31-0.16-0.49c-0.04-0.06-0.11-0.15-0.13-0.21c0.06-0.02,0.15-0.03,0.22-0.04
c0.22-0.04,0.44-0.07,0.48-0.24c0.05-0.16-0.1-0.3-0.27-0.44c-0.05-0.05-0.13-0.12-0.17-0.17c0.05-0.03,0.15-0.06,0.21-0.08
c0.21-0.07,0.41-0.14,0.44-0.31c0.02-0.17-0.14-0.28-0.32-0.41c-0.06-0.04-0.14-0.1-0.19-0.15c0.05-0.03,0.14-0.07,0.2-0.1
c0.2-0.09,0.39-0.18,0.4-0.35c0.01-0.17-0.17-0.27-0.36-0.38c-0.06-0.03-0.15-0.09-0.2-0.13c0.05-0.04,0.14-0.08,0.19-0.12
c0.19-0.11,0.38-0.21,0.37-0.37c0-0.17-0.19-0.26-0.38-0.36c-0.06-0.03-0.15-0.08-0.2-0.11c0.05-0.04,0.13-0.09,0.19-0.13
c0.19-0.12,0.36-0.23,0.35-0.39c-0.01-0.17-0.2-0.25-0.4-0.34c-0.06-0.03-0.15-0.07-0.21-0.1c0.04-0.04,0.13-0.1,0.18-0.14
c0.18-0.13,0.35-0.25,0.33-0.41c-0.02-0.17-0.22-0.24-0.42-0.32c-0.06-0.02-0.16-0.06-0.21-0.09c0.04-0.04,0.12-0.11,0.18-0.15
c0.17-0.13,0.33-0.26,0.3-0.43c-0.03-0.17-0.23-0.23-0.44-0.29c-0.06-0.02-0.16-0.05-0.21-0.07c0.04-0.05,0.12-0.11,0.17-0.16
c0.16-0.14,0.32-0.28,0.28-0.44c-0.04-0.16-0.24-0.22-0.46-0.27c-0.06-0.02-0.16-0.04-0.22-0.06c0.04-0.05,0.11-0.12,0.16-0.17
c0.15-0.15,0.3-0.3,0.25-0.46c-0.05-0.16-0.26-0.2-0.48-0.24c-0.06-0.01-0.16-0.03-0.22-0.05c0.03-0.05,0.1-0.14,0.15-0.19
c0.14-0.17,0.27-0.32,0.21-0.47c-0.07-0.16-0.28-0.17-0.51-0.19c-0.08-0.01-0.22-0.01-0.26-0.03l-0.23,0.16
c0.07,0.14,0.26,0.15,0.48,0.17c0.07,0,0.17,0.01,0.23,0.02c-0.03,0.05-0.09,0.12-0.13,0.17c-0.14,0.17-0.27,0.33-0.22,0.48
c0.06,0.15,0.24,0.18,0.46,0.22c0.07,0.01,0.16,0.03,0.22,0.05c-0.04,0.05-0.1,0.11-0.15,0.16c-0.15,0.15-0.3,0.3-0.25,0.46
c0.04,0.16,0.23,0.2,0.45,0.26c0.06,0.02,0.16,0.04,0.22,0.06c-0.04,0.05-0.11,0.11-0.16,0.15c-0.16,0.14-0.32,0.28-0.28,0.44
c0.03,0.16,0.22,0.22,0.43,0.28c0.06,0.02,0.16,0.05,0.21,0.07c-0.04,0.04-0.12,0.1-0.17,0.14c-0.17,0.13-0.33,0.26-0.31,0.42
c0.03,0.16,0.22,0.23,0.42,0.31c0.06,0.02,0.15,0.06,0.21,0.09c-0.05,0.04-0.13,0.1-0.18,0.14c-0.18,0.12-0.34,0.24-0.33,0.41
c0.02,0.16,0.2,0.25,0.4,0.33c0.06,0.03,0.15,0.07,0.2,0.1c-0.05,0.04-0.13,0.09-0.19,0.13c-0.18,0.12-0.35,0.22-0.35,0.39
c0.01,0.16,0.19,0.26,0.38,0.35c0.06,0.03,0.14,0.07,0.19,0.11c-0.05,0.04-0.14,0.08-0.19,0.12c-0.19,0.1-0.37,0.2-0.37,0.36
c0,0.17,0.17,0.27,0.36,0.38c0.05,0.03,0.14,0.08,0.19,0.12c-0.05,0.03-0.14,0.07-0.2,0.1c-0.2,0.09-0.38,0.17-0.39,0.33
c-0.02,0.17,0.15,0.29,0.33,0.41c0.05,0.04,0.13,0.09,0.17,0.13c-0.06,0.03-0.15,0.06-0.21,0.08c-0.21,0.07-0.39,0.13-0.42,0.29
c-0.03,0.16,0.12,0.3,0.28,0.45c0.05,0.04,0.12,0.11,0.16,0.15c-0.05,0.01-0.16,0.02-0.23,0.04c-0.21,0.04-0.4,0.07-0.45,0.21
c-0.06,0.15,0.06,0.32,0.18,0.49c0.05,0.07,0.13,0.18,0.14,0.22c0-0.01,0-0.02,0.01-0.04L115.44,135.6z"/>
</g>
<g>
<path class="st0" d="M124.86,135.6l0.25-0.14c0.01,0.02,0.01,0.04,0.01,0.04c0.01-0.04,0.09-0.15,0.14-0.22
c0.13-0.18,0.24-0.34,0.18-0.49c-0.06-0.14-0.24-0.17-0.45-0.21c-0.07-0.01-0.16-0.03-0.22-0.05c0.04-0.04,0.11-0.1,0.15-0.15
c0.16-0.15,0.31-0.29,0.28-0.45c-0.03-0.16-0.21-0.22-0.42-0.29c-0.06-0.02-0.16-0.05-0.21-0.08c0.04-0.04,0.12-0.1,0.17-0.13
c0.18-0.13,0.34-0.25,0.33-0.41c-0.02-0.16-0.19-0.24-0.39-0.33c-0.06-0.03-0.15-0.07-0.2-0.1c0.05-0.04,0.13-0.09,0.19-0.12
c0.19-0.11,0.36-0.21,0.36-0.38c0-0.16-0.18-0.26-0.37-0.36c-0.06-0.03-0.15-0.08-0.2-0.12c0.05-0.03,0.14-0.08,0.2-0.11
c0.19-0.1,0.38-0.19,0.38-0.35c0.01-0.16-0.16-0.27-0.35-0.39c-0.06-0.04-0.14-0.09-0.19-0.13c0.05-0.03,0.14-0.07,0.2-0.1
c0.2-0.09,0.39-0.17,0.4-0.33c0.02-0.16-0.15-0.28-0.33-0.41c-0.05-0.04-0.13-0.09-0.18-0.14c0.05-0.03,0.15-0.06,0.21-0.09
c0.2-0.08,0.39-0.15,0.42-0.31c0.03-0.16-0.14-0.29-0.31-0.42c-0.05-0.04-0.13-0.1-0.17-0.14c0.06-0.03,0.15-0.05,0.21-0.07
c0.21-0.07,0.4-0.12,0.43-0.28c0.03-0.16-0.12-0.3-0.28-0.44c-0.05-0.04-0.12-0.1-0.16-0.15c0.06-0.02,0.15-0.05,0.22-0.06
c0.22-0.05,0.4-0.1,0.45-0.26c0.04-0.16-0.1-0.31-0.26-0.46c-0.04-0.04-0.11-0.11-0.15-0.16c0.06-0.02,0.16-0.04,0.22-0.05
c0.22-0.04,0.41-0.07,0.46-0.22c0.06-0.16-0.08-0.31-0.22-0.48c-0.04-0.05-0.11-0.13-0.14-0.18c0.05,0,0.16-0.01,0.24-0.01
c0.22-0.01,0.4-0.02,0.48-0.17l-0.26-0.13c0.01-0.02,0.02-0.02,0.02-0.03c-0.03,0.02-0.18,0.03-0.26,0.03
c-0.22,0.01-0.44,0.03-0.51,0.19c-0.06,0.15,0.06,0.3,0.2,0.47c0.04,0.05,0.11,0.13,0.15,0.19c-0.06,0.02-0.15,0.03-0.22,0.05
c-0.22,0.04-0.43,0.07-0.48,0.24c-0.05,0.16,0.09,0.3,0.25,0.46c0.05,0.05,0.12,0.12,0.16,0.17c-0.06,0.02-0.15,0.05-0.22,0.06
c-0.21,0.05-0.42,0.1-0.46,0.27c-0.04,0.16,0.12,0.3,0.28,0.44c0.05,0.04,0.13,0.11,0.17,0.16c-0.06,0.03-0.15,0.06-0.21,0.07
c-0.21,0.07-0.41,0.13-0.44,0.29c-0.03,0.16,0.13,0.29,0.3,0.43c0.05,0.04,0.13,0.1,0.17,0.15c-0.05,0.03-0.15,0.06-0.21,0.09
c-0.2,0.08-0.4,0.15-0.42,0.32c-0.02,0.17,0.15,0.29,0.33,0.41c0.05,0.04,0.14,0.1,0.18,0.14c-0.05,0.03-0.15,0.07-0.21,0.1
c-0.2,0.09-0.39,0.17-0.4,0.34c-0.01,0.17,0.16,0.28,0.35,0.4c0.06,0.04,0.14,0.09,0.19,0.13c-0.05,0.04-0.14,0.08-0.2,0.11
c-0.19,0.1-0.38,0.19-0.38,0.36c0,0.17,0.18,0.27,0.37,0.37c0.06,0.03,0.15,0.08,0.19,0.12c-0.05,0.04-0.14,0.09-0.2,0.13
c-0.19,0.11-0.36,0.21-0.36,0.38c0.01,0.17,0.2,0.26,0.4,0.35c0.06,0.03,0.15,0.07,0.2,0.1c-0.04,0.04-0.13,0.11-0.19,0.15
c-0.18,0.13-0.34,0.25-0.32,0.41c0.02,0.17,0.22,0.24,0.44,0.31c0.06,0.02,0.16,0.05,0.21,0.08c-0.04,0.05-0.12,0.12-0.17,0.17
c-0.16,0.15-0.31,0.28-0.27,0.44c0.05,0.17,0.26,0.21,0.48,0.24c0.06,0.01,0.16,0.03,0.22,0.04c-0.03,0.06-0.09,0.15-0.13,0.21
C124.9,135.28,124.78,135.45,124.86,135.6z"/>
</g>
</g>
<path class="st1" d="M131.2,137.35v53.99c0,1.77-1.44,3.21-3.21,3.21H112.5c-1.77,0-3.21-1.44-3.21-3.21v-53.99
c0-1.74,1.43-3.19,3.21-3.21h15.49C129.77,134.16,131.21,135.6,131.2,137.35z"/>
<path class="st2" d="M129.66,138.81v51.41c0,1.74-1.44,3.14-3.21,3.14h-12.42c-1.77,0-3.21-1.41-3.21-3.14v-51.41
c-0.01-1.48,1.23-2.69,2.76-2.7h13.32C128.43,136.13,129.67,137.34,129.66,138.81z"/>
<path class="st3" d="M114.14,152.96h12.02c0.89,0,1.6,0.72,1.6,1.6v1.12c0,0.89-0.72,1.6-1.6,1.6h-12.02c-0.89,0-1.6-0.72-1.6-1.6
v-1.12C112.54,153.68,113.26,152.96,114.14,152.96z"/>
<rect x="124.6" y="157.29" class="st4" width="0.29" height="36.07"/>
<rect x="115.48" y="157.29" class="st4" width="0.29" height="36.07"/>
<rect x="126.35" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="125.4" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="124.45" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="123.5" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="113.5" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="114.45" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="115.4" y="155.88" class="st4" width="0.45" height="0.45"/>
<rect x="116.35" y="155.88" class="st4" width="0.45" height="0.45"/>
<path class="st0" d="M110.77,129.27c6.22,0.22,12.54,0.22,18.76,0c0.67-0.02,1.2-0.18,1.2-0.35c0-2.74,0-5.49,0-8.23
c0-0.17-0.54-0.31-1.21-0.31c-6.25,0-12.5,0-18.75,0c-0.67,0-1.21,0.14-1.21,0.31c0,2.74,0,5.49,0,8.23
C109.56,129.09,110.1,129.25,110.77,129.27z"/>
<path class="st0" d="M121.47,131.12h-2.63c-0.22,0-0.46-0.19-0.54-0.44l-1.07-3.32h5.85l-1.07,3.32
C121.93,130.93,121.69,131.12,121.47,131.12z"/>
<path class="st1" d="M112.5,128.09c5.16,0,10.33,0,15.49,0c1.78-0.02,3.22-1.45,3.21-3.21c0-24.98,0-36.72,0-61.7
c0-5.61-2.63-18.92-10.95-18.91l0,0c-8.32-0.01-10.95,13-10.95,18.91c0,24.98,0,36.72,0,61.7
C109.29,126.63,110.72,128.07,112.5,128.09z"/>
<path class="st2" d="M113.59,126.12c4.44,0,8.88,0,13.32,0c1.53-0.01,2.76-1.25,2.76-2.76c0-21.47,0-29.71,0-51.18
c0.21-4.9-2.26-16.26-9.42-16.26l0,0c-7.15,0-9.63,11.36-9.42,16.26c0,21.47,0,29.71,0,51.18
C110.82,124.87,112.06,126.11,113.59,126.12z"/>
<path class="st0" d="M129.68,65.06c0-3.83-3.6-11.58-9.43-11.58v-6.65C125.49,46.83,129.68,52.8,129.68,65.06z"/>
<path class="st0" d="M110.82,65.06c0-3.83,3.6-11.58,9.43-11.58v-6.65C115,46.83,110.82,52.8,110.82,65.06z"/>
<path class="st3" d="M114.14,82.87h12.02c0.89,0,1.6,0.72,1.6,1.6v1.12c0,0.89-0.72,1.6-1.6,1.6h-12.02c-0.89,0-1.6-0.72-1.6-1.6
v-1.12C112.54,83.59,113.26,82.87,114.14,82.87z"/>
<rect x="124.6" y="87.21" class="st4" width="0.29" height="38.91"/>
<rect x="115.48" y="87.21" class="st4" width="0.29" height="38.91"/>
<rect x="126.35" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="125.4" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="124.45" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="123.5" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="113.5" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="114.45" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="115.4" y="85.79" class="st4" width="0.45" height="0.45"/>
<rect x="116.35" y="85.79" class="st4" width="0.45" height="0.45"/>
<path class="st5" d="M128.74,51.72c0.15,0.42-0.22,1.25-0.61,1.09c-0.38-0.16-1.22-3.62-1.06-3.94
C127.23,48.54,128.61,51.36,128.74,51.72z"/>
<path class="st5" d="M111.75,51.72c-0.15,0.42,0.22,1.25,0.61,1.09c0.38-0.16,1.22-3.62,1.06-3.94
C113.26,48.54,111.88,51.36,111.75,51.72z"/>
<g>
<rect class="st6" width="240" height="240"/>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 240" style="enable-background:new 0 0 240 240;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#CDCDCD;}
.st2{fill:#D50000;}
.st3{fill:#444444;}
.st4{fill:#5B5A5A;}
.st5{fill:#FBFBFB;}
</style>
<g>
<rect class="st0" width="240" height="240"/>
</g>
<g>
<path class="st1" d="M157.42,204c5.6-0.02,10.48-1.27,13.18-3.12c1.41-0.97,2.23-2.09,2.22-3.3c0-49.95,0-73.45,0-123.4
c0-11.22-12.64-37.84-52.58-37.83c-39.94-0.01-52.58,26-52.58,37.83c0,49.95,0,73.45,0,123.4c-0.01,0.92,0.47,1.79,1.31,2.58
c2.39,2.24,7.79,3.82,14.09,3.84C107.85,204,132.64,204,157.42,204z M153.01,45.55c0.77-0.64,7.38,5,8,5.71
c0.74,0.85-1.08,2.5-2.92,2.18C156.24,53.11,152.24,46.19,153.01,45.55z M79.48,51.25c0.62-0.71,7.23-6.35,8-5.71
c0.77,0.64-3.23,7.56-5.08,7.89C80.56,53.75,78.74,52.1,79.48,51.25z M120.25,41.49c25.16,0,45.26,11.93,45.26,36.45
c0-7.65-17.29-23.16-45.26-23.16c-27.97,0-45.26,15.51-45.26,23.16C74.99,53.41,95.08,41.49,120.25,41.49z M75.05,194.55
c0-42.94,0-59.42,0-102.36c-1.03-9.8,10.87-32.53,45.2-32.52c34.33-0.01,46.23,22.72,45.2,32.52c0,42.94,0,59.42,0,102.36
c0.03,3.02-5.91,5.49-13.24,5.52c-3.22,0-6.44,0-9.66,0c-0.46,0-0.92,0-1.38,0c-14.14,0-28.29,0-42.43,0c-0.46,0-0.92,0-1.38,0
c-3.02,0-6.04,0-9.07,0C80.96,200.04,75.02,197.57,75.05,194.55z"/>
<path class="st2" d="M98.73,122.24v77.83c14.14,0,28.29,0,42.43,0v-77.83H98.73z"/>
<path class="st2" d="M97.35,200.07v-77.83h-6.41c-4.25,0-7.7-1.44-7.7-3.21v-2.25c0-1.77,3.45-3.21,7.7-3.21h57.69
c4.25,0,7.7,1.44,7.7,3.21v2.25c0,1.77-3.45,3.21-7.7,3.21h-6.08v77.83c3.22,0,6.44,0,9.66,0c7.33-0.03,13.27-2.5,13.24-5.52
c0-42.94,0-59.42,0-102.36c1.03-9.8-10.87-32.53-45.2-32.52c-34.33-0.01-46.23,22.72-45.2,32.52c0,42.94,0,59.42,0,102.36
c-0.03,3.02,5.91,5.49,13.24,5.52C91.31,200.07,94.33,200.07,97.35,200.07z"/>
<path class="st3" d="M120.25,54.78c27.97,0,45.26,15.51,45.26,23.16c0-24.53-20.1-36.45-45.26-36.45
c-25.16,0-45.26,11.93-45.26,36.45C74.99,70.29,92.28,54.78,120.25,54.78z"/>
<path class="st4" d="M148.63,113.57H90.95c-4.25,0-7.7,1.44-7.7,3.21v2.25c0,1.77,3.45,3.21,7.7,3.21h6.41v77.83
c0.46,0,0.92,0,1.38,0v-77.83h42.43v77.83c0.46,0,0.92,0,1.38,0v-77.83h6.08c4.25,0,7.7-1.44,7.7-3.21v-2.25
C156.33,115.01,152.88,113.57,148.63,113.57z"/>
<path class="st5" d="M158.09,53.43c1.85,0.32,3.66-1.33,2.92-2.18c-0.62-0.71-7.23-6.35-8-5.71
C152.24,46.19,156.24,53.11,158.09,53.43z"/>
<path class="st5" d="M82.4,53.43c1.85-0.32,5.85-7.24,5.08-7.89c-0.77-0.64-7.38,5-8,5.71C78.74,52.1,80.56,53.75,82.4,53.43z"/>
</g>
</svg>
import numpy as np
from numpy import array
import os
import time
from collections import deque
from matplotlib import pyplot as plt
import threading
import os
# from contextlib import redirect_stdout
# import os
# import sys
# import io
# from PIL import Image
# from ipywidgets import IntSlider, link, VBox
from flatland.envs.rail_env import RailEnv, random_rail_generator
from flatland.envs.generators import complex_rail_generator
# from flatland.core.transitions import RailEnvTransitions
from flatland.core.env_observation_builder import TreeObsForRailEnv
import flatland.utils.rendertools as rt
from examples.play_model import Player
from flatland.envs.env_utils import mirror
from flatland.envs.agent_utils import EnvAgent, EnvAgentStatic
import ipywidgets
from ipywidgets import IntSlider, VBox, HBox, Checkbox, Output, Text, RadioButtons, Tab
import jpy_canvas
import numpy as np
from ipywidgets import IntSlider, VBox, HBox, Checkbox, Output, Text, RadioButtons, Tab
from numpy import array
import flatland.utils.rendertools as rt
from flatland.core.grid.grid4_utils import mirror
from flatland.envs.agent_utils import EnvAgent
from flatland.envs.line_generators import sparse_line_generator
from flatland.envs.observations import TreeObsForRailEnv
from flatland.envs.predictions import ShortestPathPredictorForRailEnv
from flatland.envs.rail_env import RailEnv
from flatland.envs.rail_generators import sparse_rail_generator, empty_rail_generator
from flatland.envs.persistence import RailEnvPersister
class EditorMVC(object):
def __init__(self, env=None, sGL="MPL"):
""" EditorMVC - a class to encompass and assemble the Jupyter Editor Model-View-Controller.
"""
def __init__(self, env=None, sGL="PIL", env_filename="temp.pkl"):
""" Create an Editor MVC assembly around a railenv, or create one if None.
"""
if env is None:
env = RailEnv(width=10,
height=10,
rail_generator=random_rail_generator(),
number_of_agents=0,
obs_builder_object=TreeObsForRailEnv(max_depth=2))
nAgents = 3
n_cities = 2
max_rails_between_cities = 2
max_rails_in_city = 4
seed = 0
env = RailEnv(
width=20,
height=30,
rail_generator=sparse_rail_generator(
max_num_cities=n_cities,
seed=seed,
grid_mode=True,
max_rails_between_cities=max_rails_between_cities,
max_rail_pairs_in_city=max_rails_in_city
),
line_generator=sparse_line_generator(),
number_of_agents=nAgents,
obs_builder_object=TreeObsForRailEnv(max_depth=3, predictor=ShortestPathPredictorForRailEnv())
)
env.reset()
self.editor = EditorModel(env)
self.editor = EditorModel(env, env_filename=env_filename)
self.editor.view = self.view = View(self.editor, sGL=sGL)
self.view.controller = self.editor.controller = self.controller = Controller(self.editor, self.view)
self.view.init_canvas()
self.view.init_widgets() # has to be done after controller
self.view.init_widgets() # has to be done after controller
class View(object):
def __init__(self, editor, sGL="MPL"):
""" The Jupyter Editor View - creates and holds the widgets comprising the Editor.
"""
def __init__(self, editor, sGL="MPL", screen_width=1200, screen_height=1200):
self.editor = self.model = editor
self.sGL = sGL
self.xyScreen = (screen_width, screen_height)
def display(self):
self.wOutput.clear_output()
self.output_generator.clear_output()
return self.wMain
def init_canvas(self):
# update the rendertool with the env
self.new_env()
self.oRT.renderEnv(spacing=False, arrows=False, sRailColor="gray", show=False)
img = self.oRT.getImage()
plt.clf()
self.oRT.render_env(show=False)
img = self.oRT.get_image()
self.wImage = jpy_canvas.Canvas(img)
self.yxSize = self.wImage.data.shape[:2]
self.writableData = np.copy(self.wImage.data) # writable copy of image - wid_img.data is somehow readonly
self.wImage.register_move(self.controller.on_mouse_move)
self.wImage.register_click(self.controller.on_click)
# TODO: These are currently estimated values
# self.yxBase = array([6, 21]) # pixel offset
# self.nPixCell = 700 / self.model.env.rail.width # 35
self.yxBase = self.oRT.gl.yxBase
self.nPixCell = self.oRT.gl.nPixCell
def init_widgets(self):
# Radiobutton for drawmode - TODO: replace with shift/ctrl/alt keys
# self.wDrawMode = RadioButtons(options=["Draw", "Erase", "Origin", "Destination"])
# self.wDrawMode.observe(self.editor.setDrawMode, names="value")
# Debug checkbox - enable logging in the Output widget
self.wDebug = ipywidgets.Checkbox(description="Debug")
self.wDebug.observe(self.controller.setDebug, names="value")
self.debug = ipywidgets.Checkbox(description="Debug")
self.debug.observe(self.controller.set_debug, names="value")
# Separate checkbox for mouse move events - they are very verbose
self.wDebug_move = Checkbox(description="Debug mouse move")
self.wDebug_move.observe(self.controller.setDebugMove, names="value")
self.debug_move = Checkbox(description="Debug mouse move")
self.debug_move.observe(self.controller.set_debug_move, names="value")
# This is like a cell widget where loggin goes
self.wOutput = Output()
self.output_generator = Output()
# Filename textbox
self.wFilename = Text(description="Filename")
self.wFilename.value = self.model.env_filename
self.wFilename.observe(self.controller.setFilename, names="value")
self.filename = Text(description="Filename")
self.filename.value = self.model.env_filename
self.filename.observe(self.controller.set_filename, names="value")
# Size of environment when regenerating
self.wSize = IntSlider(value=10, min=5, max=30, step=5, description="Regen Size")
self.wSize.observe(self.controller.setRegenSize, names="value")
self.regen_width = IntSlider(value=10, min=5, max=100, step=5, description="Regen Size (Width)",
tip="Click Regenerate after changing this")
self.regen_width.observe(self.controller.set_regen_width, names="value")
self.regen_height = IntSlider(value=10, min=5, max=100, step=5, description="Regen Size (Height)",
tip="Click Regenerate after changing this")
self.regen_height.observe(self.controller.set_regen_height, names="value")
# Number of Agents when regenerating
self.wNAgents = IntSlider(value=1, min=0, max=20, step=1, description="# Agents")
self.regen_n_agents = IntSlider(value=1, min=0, max=5, step=1, description="# Agents",
tip="Click regenerate or reset after changing this")
self.regen_method = RadioButtons(description="Regen\nMethod", options=["Empty", "Sparse"])
self.wRegenMethod = RadioButtons(description="Regen\nMethod", options=["Random Cell", "Path-based"])
self.wReplaceAgents = Checkbox(value=True, description="Replace Agents")
self.replace_agents = Checkbox(value=True, description="Replace Agents")
self.wTab = Tab()
tab_contents = ["Debug", "Regen"]
tab_contents = ["Regen", "Observation"]
for i, title in enumerate(tab_contents):
self.wTab.set_title(i, title)
self.wTab.children = [
VBox([self.wDebug, self.wDebug_move]),
VBox([self.wRegenMethod, self.wReplaceAgents])
]
# Progress bar intended for stepping in the background (not yet working)
self.wProg_steps = ipywidgets.IntProgress(value=0, min=0, max=20, step=1, description="Step")
VBox([self.regen_width, self.regen_height, self.regen_n_agents, self.regen_method])
]
# abbreviated description of buttons and the methods they call
ldButtons = [
dict(name="Refresh", method=self.controller.refresh, tip="Redraw only"),
dict(name="Clear", method=self.controller.clear, tip="Clear rails and agents"),
dict(name="Reset", method=self.controller.reset,
tip="Standard env reset, including regen rail + agents"),
dict(name="Restart Agents", method=self.controller.restartAgents,
tip="Move agents back to start positions"),
dict(name="Rotate Agent", method=self.controller.rotate_agent, tip="Rotate selected agent"),
dict(name="Restart Agents", method=self.controller.reset_agents,
tip="Move agents back to start positions"),
dict(name="Random", method=self.controller.reset,
tip="Generate a randomized scene, including regen rail + agents"),
dict(name="Regenerate", method=self.controller.regenerate,
tip="Regenerate the rails using the method selected below"),
tip="Regenerate the rails using the method selected below"),
dict(name="Load", method=self.controller.load),
dict(name="Save", method=self.controller.save),
dict(name="Step", method=self.controller.step),
dict(name="Run Steps", method=self.controller.start_run),
dict(name="Save as image", method=self.controller.save_image)
]
self.lwButtons = []
for dButton in ldButtons:
wButton = ipywidgets.Button(description=dButton["name"],
tooltip=dButton["tip"] if "tip" in dButton else dButton["name"])
tooltip=dButton["tip"] if "tip" in dButton else dButton["name"])
wButton.on_click(dButton["method"])
self.lwButtons.append(wButton)
self.wVbox_controls = VBox([
self.wFilename, # self.wDrawMode,
self.filename,
*self.lwButtons,
self.wSize,
self.wNAgents,
self.wProg_steps,
self.wTab
])
self.wTab])
self.wMain = HBox([self.wImage, self.wVbox_controls])
def drawStroke(self):
def draw_stroke(self):
pass
def new_env(self):
""" Tell the view to update its graphics when a new env is created.
"""
self.oRT = rt.RenderTool(self.editor.env, gl=self.sGL)
self.oRT = rt.RenderTool(self.editor.env, gl=self.sGL, show_debug=True,
screen_height=self.xyScreen[1], screen_width=self.xyScreen[0])
def redraw(self):
# TODO: bit of a hack - can we suppress the console messages from MPL at source?
# with redirect_stdout(stdout_dest):
with self.wOutput:
# plt.figure(figsize=(10, 10))
self.oRT.renderEnv(spacing=False, arrows=False, sRailColor="gray",
show=False, iSelectedAgent=self.model.iSelectedAgent)
img = self.oRT.getImage()
# plt.clf()
# plt.close()
with self.output_generator:
self.oRT.set_new_rail()
self.model.env.reset_agents()
for a in self.model.env.agents:
if hasattr(a, 'old_position') is False:
a.old_position = a.position
if hasattr(a, 'old_direction') is False:
a.old_direction = a.direction
self.oRT.render_env(show_agents=True,
show_inactive_agents=True,
show=False,
selected_agent=self.model.selected_agent,
show_observations=False,
)
img = self.oRT.get_image()
self.wImage.data = img
self.writableData = np.copy(self.wImage.data)
# the size should only be updated on regenerate at most
self.yxSize = self.wImage.data.shape[:2]
return img
def redisplayImage(self):
def redisplay_image(self):
if self.writableData is not None:
# This updates the image in the browser to be the new edited version
self.wImage.data = self.writableData
......@@ -184,12 +198,18 @@ class View(object):
self.writableData[(y - 2):(y + 2), (x - 2):(x + 2), :3] = 0
def xy_to_rc(self, x, y):
rcCell = ((array([y, x]) - self.yxBase) / self.nPixCell).astype(int)
return rcCell
rc_cell = ((array([y, x]) - self.yxBase))
nX = np.floor((self.yxSize[0] - self.yxBase[0]) / self.model.env.height)
nY = np.floor((self.yxSize[1] - self.yxBase[1]) / self.model.env.width)
rc_cell[0] = max(0, min(np.floor(rc_cell[0] / nY), self.model.env.height - 1))
rc_cell[1] = max(0, min(np.floor(rc_cell[1] / nX), self.model.env.width - 1))
# Using numpy arrays for coords not currently supported downstream in the env, observations, etc
return tuple(rc_cell)
def log(self, *args, **kwargs):
if self.wOutput:
with self.wOutput:
if self.output_generator:
with self.output_generator:
print(*args, **kwargs)
else:
print(*args, **kwargs)
......@@ -202,93 +222,118 @@ class Controller(object):
Calls the View directly for things which do not directly effect the model
(this means the mouse drag path before it is interpreted as transitions)
"""
def __init__(self, model, view):
self.editor = self.model = model
self.view = view
self.qEvents = deque()
self.q_events = deque()
self.drawMode = "Draw"
def setModel(self, model):
def set_model(self, model):
self.model = model
def on_click(self, wid, event):
x = event['canvasX']
y = event['canvasY']
self.debug("debug:", event)
self.debug("debug:", x, y)
rcCell = self.view.xy_to_rc(x, y)
rc_cell = self.view.xy_to_rc(x, y)
bShift = event["shiftKey"]
bCtrl = event["ctrlKey"]
if bCtrl and not bShift:
self.model.click_agent(rcCell)
bAlt = event["altKey"]
if bCtrl and not bShift and not bAlt:
self.model.click_agent(rc_cell)
self.lrcStroke = []
elif bShift and bCtrl:
self.model.add_target(rcCell)
self.model.add_target(rc_cell)
self.lrcStroke = []
elif bAlt and not bShift and not bCtrl:
self.model.clear_cell(rc_cell)
self.lrcStroke = []
self.debug("click in cell", rc_cell)
self.model.debug_cell(rc_cell)
self.debug("click in cell", rcCell)
self.model.debug_cell(rcCell)
if self.model.selected_agent is not None:
self.lrcStroke = []
def setDebug(self, dEvent):
self.model.setDebug(dEvent["new"])
def set_debug(self, event):
self.model.set_debug(event["new"])
def setDebugMove(self, dEvent):
self.model.setDebug_move(dEvent["new"])
def set_debug_move(self, event):
self.model.set_debug_move(event["new"])
def setDrawMode(self, dEvent):
self.drawMode = dEvent["new"]
def set_draw_mode(self, event):
self.set_draw_mode = event["new"]
def setFilename(self, event):
self.model.setFilename(event["new"])
def set_filename(self, event):
self.model.set_filename(event["new"])
def on_mouse_move(self, wid, event):
"""Mouse motion event handler for drawing.
"""
x = event['canvasX']
y = event['canvasY']
qEvents = self.qEvents
if self.model.bDebug and (event["buttons"] > 0 or self.model.bDebug_move):
self.debug("debug:", len(qEvents), event)
q_events = self.q_events
# assert wid == self.wid_img, "wid not same as wid_img"
if self.model.debug_bool and (event["buttons"] > 0 or self.model.debug_move_bool):
self.debug("debug:", len(q_events), event)
# If the mouse is held down, enqueue an event in our own queue
# The intention was to avoid too many redraws.
# Reset the lrcStroke list, if ALT, CTRL or SHIFT pressed
if event["buttons"] > 0:
qEvents.append((time.time(), x, y))
q_events.append((time.time(), x, y))
bShift = event["shiftKey"]
bCtrl = event["ctrlKey"]
bAlt = event["altKey"]
if bShift:
self.lrcStroke = []
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
if bCtrl:
self.lrcStroke = []
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
if bAlt:
self.lrcStroke = []
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
else:
self.lrcStroke = []
# JW: I think this clause causes all editing to fail once an agent is selected.
# I also can't see why it's necessary. So I've if-falsed it out.
if False:
if self.model.selected_agent is not None:
self.lrcStroke = []
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
# Process the events in our queue:
# Draw a black square to indicate a trail
# TODO: infer a vector of moves between these squares to avoid gaps
# Convert the xy position to a cell rc
# Enqueue transitions across cells in another queue
if len(qEvents) > 0:
tNow = time.time()
if tNow - qEvents[0][0] > 0.1: # wait before trying to draw
# height, width = wid.data.shape[:2]
# writableData = np.copy(self.wid_img.data) # writable copy of image - wid_img.data is somehow readonly
if len(q_events) > 0:
t_now = time.time()
if t_now - q_events[0][0] > 0.1: # wait before trying to draw
# with self.wid_img.hold_sync():
while len(qEvents) > 0:
t, x, y = qEvents.popleft() # get events from our queue
while len(q_events) > 0:
t, x, y = q_events.popleft() # get events from our queue
self.view.drag_path_element(x, y)
# Translate and scale from x,y to integer row,col (note order change)
# rcCell = ((array([y, x]) - self.yxBase) / self.nPixCell).astype(int)
rcCell = self.view.xy_to_rc(x, y)
self.editor.drag_path_element(rcCell)
# Store the row,col location of the click, if we have entered a new cell
# if len(lrcStroke) > 0:
# rcLast = lrcStroke[-1]
# if not np.array_equal(rcLast, rcCell): # only save at transition
# # print(y, x, rcCell)
# lrcStroke.append(rcCell)
# else:
# # This is the first cell in a mouse stroke
# lrcStroke.append(rcCell)
self.view.redisplayImage()
rc_cell = self.view.xy_to_rc(x, y)
self.editor.drag_path_element(rc_cell)
self.view.redisplay_image()
else:
self.model.mod_path(not event["shiftKey"])
......@@ -300,21 +345,39 @@ class Controller(object):
self.model.clear()
def reset(self, event):
self.log("Reset - nAgents:", self.view.wNAgents.value)
self.model.reset(replace_agents=self.view.wReplaceAgents.value,
nAgents=self.view.wNAgents.value)
def restartAgents(self, event):
self.log("Restart Agents - nAgents:", self.view.wNAgents.value)
self.model.restartAgents()
self.log("Reset - nAgents:", self.view.regen_n_agents.value)
self.log("Reset - size:", self.model.regen_size_width)
self.log("Reset - size:", self.model.regen_size_height)
self.model.reset(regenerate_schedule=self.view.replace_agents.value,
nAgents=self.view.regen_n_agents.value)
def rotate_agent(self, event):
self.log("Rotate Agent:", self.model.selected_agent)
if self.model.selected_agent is not None:
for agent_idx, agent in enumerate(self.model.env.agents):
if agent is None:
continue
if agent_idx == self.model.selected_agent:
agent.initial_direction = (agent.initial_direction + 1) % 4
agent.direction = agent.initial_direction
agent.old_direction = agent.direction
self.model.redraw()
def reset_agents(self, event):
self.log("Restart Agents - nAgents:", self.view.regen_n_agents.value)
self.model.env.reset(False, False)
self.refresh(event)
def regenerate(self, event):
method = self.view.wRegenMethod.value
nAgents = self.view.wNAgents.value
self.model.regenerate(method, nAgents)
method = self.view.regen_method.value
n_agents = self.view.regen_n_agents.value
self.model.regenerate(method, n_agents)
def set_regen_width(self, event):
self.model.set_regen_width(event["new"])
def setRegenSize(self, event):
self.model.setRegenSize(event["new"])
def set_regen_height(self, event):
self.model.set_regen_height(event["new"])
def load(self, event):
self.model.load()
......@@ -322,12 +385,12 @@ class Controller(object):
def save(self, event):
self.model.save()
def save_image(self, event):
self.model.save_image()
def step(self, event):
self.model.step()
def start_run(self, event):
self.model.start_run()
def log(self, *args, **kwargs):
if self.view is None:
print(*args, **kwargs)
......@@ -339,46 +402,90 @@ class Controller(object):
class EditorModel(object):
def __init__(self, env):
def __init__(self, env, env_filename="temp.pkl"):
self.view = None
self.env = env
self.regen_size = 10
self.regen_size_width = 10
self.regen_size_height = 10
self.lrcStroke = []
self.iTransLast = -1
self.gRCTrans = array([[-1, 0], [0, 1], [1, 0], [0, -1]]) # NESW in RC
self.bDebug = False
self.bDebug_move = False
self.debug_bool = False
self.debug_move_bool = False
self.wid_output = None
self.drawMode = "Draw"
self.env_filename = "temp.pkl"
self.draw_mode = "Draw"
self.env_filename = env_filename
self.set_env(env)
self.iSelectedAgent = None
self.player = None
self.selected_agent = None
self.thread = None
self.save_image_count = 0
def set_env(self, env):
"""
set a new env for the editor, used by load and regenerate.
"""
self.env = env
# self.yxBase = array([6, 21]) # pixel offset
# self.nPixCell = 700 / self.env.rail.width # 35
# self.oRT = rt.RenderTool(env)
def setDebug(self, bDebug):
self.bDebug = bDebug
self.log("Set Debug:", self.bDebug)
def setDebugMove(self, bDebug):
self.bDebug_move = bDebug
self.log("Set DebugMove:", self.bDebug_move)
def setDrawMode(self, sDrawMode):
self.drawMode = sDrawMode
def drag_path_element(self, rcCell):
def set_debug(self, debug):
self.debug_bool = debug
self.log("Set Debug:", self.debug_bool)
def set_debug_move(self, debug):
self.debug_move_bool = debug
self.log("Set DebugMove:", self.debug_move_bool)
def set_draw_mode(self, draw_mode):
self.draw_mode = draw_mode
def interpolate_pair(self, rcLast, rc_cell):
if np.array_equal(rcLast, rc_cell):
return []
rcLast = array(rcLast)
rc_cell = array(rc_cell)
rcDelta = rc_cell - rcLast
lrcInterp = [] # extra row,col points
if np.any(np.abs(rcDelta) >= 1):
iDim0 = np.argmax(np.abs(rcDelta)) # the dimension with the bigger move
iDim1 = 1 - iDim0 # the dim with the smaller move
rcRatio = rcDelta[iDim1] / rcDelta[iDim0]
delta0 = rcDelta[iDim0]
sgn0 = np.sign(delta0)
iDelta1 = 0
# count integers along the larger dimension
for iDelta0 in range(sgn0, delta0 + sgn0, sgn0):
rDelta1 = iDelta0 * rcRatio
if np.abs(rDelta1 - iDelta1) >= 1:
rcInterp = (iDelta0, iDelta1) # fill in the "corner" for "Manhattan interpolation"
lrcInterp.append(rcInterp)
iDelta1 = int(rDelta1)
rcInterp = (iDelta0, int(rDelta1))
lrcInterp.append(rcInterp)
g2Interp = array(lrcInterp)
if iDim0 == 1: # if necessary, swap c,r to make r,c
g2Interp = g2Interp[:, [1, 0]]
g2Interp += rcLast
# Convert the array to a list of tuples
lrcInterp = list(map(tuple, g2Interp))
return lrcInterp
def interpolate_path(self, lrcPath):
lrcPath2 = [] # interpolated version of the path
rcLast = None
for rcCell in lrcPath:
if rcLast is not None:
lrcPath2.extend(self.interpolate_pair(rcLast, rcCell))
rcLast = rcCell
return lrcPath2
def drag_path_element(self, rc_cell):
"""Mouse motion event handler for drawing.
"""
lrcStroke = self.lrcStroke
......@@ -386,16 +493,20 @@ class EditorModel(object):
# Store the row,col location of the click, if we have entered a new cell
if len(lrcStroke) > 0:
rcLast = lrcStroke[-1]
if not np.array_equal(rcLast, rcCell): # only save at transition
lrcStroke.append(rcCell)
self.debug("lrcStroke ", len(lrcStroke), rcCell)
if not np.array_equal(rcLast, rc_cell): # only save at transition
lrcInterp = self.interpolate_pair(rcLast, rc_cell)
lrcStroke.extend(lrcInterp)
self.debug("lrcStroke ", len(lrcStroke), rc_cell, "interp:", lrcInterp)
else:
# This is the first cell in a mouse stroke
lrcStroke.append(rcCell)
self.debug("lrcStroke ", len(lrcStroke), rcCell)
lrcStroke.append(rc_cell)
self.debug("lrcStroke ", len(lrcStroke), rc_cell)
def mod_path(self, bAddRemove):
# disabled functionality (no longer required)
if bAddRemove is False:
return
# This elif means we wait until all the mouse events have been processed (black square drawn)
# before trying to draw rails. (We could change this behaviour)
# Equivalent to waiting for mouse button to be lifted (and a mouse event is necessary:
......@@ -409,21 +520,26 @@ class EditorModel(object):
# If we have already touched 3 cells
# We have a transition into a cell, and out of it.
#print(lrcStroke)
if len(lrcStroke) >= 2:
# If the first cell in a stroke is empty, add a deadend to cell 0
if self.env.rail.get_transitions(lrcStroke[0]) == 0:
if self.env.rail.get_full_transitions(*lrcStroke[0]) == 0:
self.mod_rail_2cells(lrcStroke, bAddRemove, iCellToMod=0)
# Add transitions for groups of 3 cells
# hence inbound and outbound transitions for middle cell
while len(lrcStroke) >= 3:
#print(lrcStroke)
self.mod_rail_3cells(lrcStroke, bAddRemove=bAddRemove)
# If final cell empty, insert deadend:
if len(lrcStroke) == 2:
if self.env.rail.get_transitions(lrcStroke[1]) == 0:
if self.env.rail.get_full_transitions(*lrcStroke[1]) == 0:
self.mod_rail_2cells(lrcStroke, bAddRemove, iCellToMod=1)
#print("final:", lrcStroke)
# now empty out the final two cells from the queue
lrcStroke.clear()
......@@ -438,14 +554,11 @@ class EditorModel(object):
eg rcCells [(3,4), (2,4), (2,5)] would result in the transitions
N->E and W->S in cell (2,4).
"""
rc3Cells = array(lrcStroke[:3]) # the 3 cells
rcMiddle = rc3Cells[1] # the middle cell which we will update
bDeadend = np.all(lrcStroke[0] == lrcStroke[2]) # deadend means cell 0 == cell 2
# Save the original state of the cell
# oTransrcMiddle = self.env.rail.get_transitions(rcMiddle)
# sTransrcMiddle = self.env.rail.cell_repr(rcMiddle)
# get the 2 row, col deltas between the 3 cells, eg [[-1,0],[0,1]] = North, East
rc2Trans = np.diff(rc3Cells, axis=0)
......@@ -478,12 +591,6 @@ class EditorModel(object):
self.env.rail.set_transition((*rcMiddle, mirror(liTrans[1])),
mirror(liTrans[0]), bAddRemove, remove_deadends=not bDeadend)
# bValid = self.env.rail.is_cell_valid(rcMiddle)
# if not bValid:
# # Reset cell transition values
# self.env.rail.grid[tuple(rcMiddle)] = oTransrcMiddle
# self.log(rcMiddle, "Orig:", sTransrcMiddle, "Mod:", self.env.rail.cell_repr(rcMiddle))
if bPop:
lrcStroke.pop(0) # remove the first cell in the stroke
......@@ -508,6 +615,8 @@ class EditorModel(object):
iTrans = iTrans[0][0]
liTrans.append(iTrans)
#self.log("liTrans:", liTrans)
# check that we have one transition
if len(liTrans) == 1:
# Set the transition as a deadend
......@@ -527,35 +636,40 @@ class EditorModel(object):
def clear(self):
self.env.rail.grid[:, :] = 0
# self.env.number_of_agents = 0
self.env.agents = []
self.env.agents_static = []
# self.env.agents_handles = []
self.player = None
self.redraw()
def reset(self, replace_agents=False, nAgents=0):
# if replace_agents:
# self.env.agents_handles = range(nAgents)
self.env.reset(regen_rail=True, replace_agents=replace_agents)
self.player = Player(self.env)
def clear_cell(self, cell_row_col):
self.debug_cell(cell_row_col)
self.env.rail.grid[cell_row_col[0], cell_row_col[1]] = 0
self.redraw()
def reset(self, regenerate_schedule=False, nAgents=0):
self.regenerate("complex", nAgents=nAgents)
self.redraw()
def restartAgents(self):
self.env.agents = EnvAgent.list_from_static(self.env.agents_static)
self.player = Player(self.env)
def restart_agents(self):
self.env.reset_agents()
self.redraw()
def setFilename(self, filename):
self.log("filename = ", filename, type(filename))
def set_filename(self, filename):
self.env_filename = filename
def load(self):
if os.path.exists(self.env_filename):
self.log("load file: ", self.env_filename)
# self.env.rail.load_transition_map(self.env_filename, override_gridsize=True)
self.env.load(self.env_filename)
#self.env.load(self.env_filename)
RailEnvPersister.load(self.env, self.env_filename)
if not self.regen_size_height == self.env.height or not self.regen_size_width == self.env.width:
self.regen_size_height = self.env.height
self.regen_size_width = self.env.width
self.regenerate(None, 0, self.env)
RailEnvPersister.load(self.env, self.env_filename)
self.env.reset_agents()
self.env.reset(False, False)
self.view.oRT.update_background()
self.fix_env()
self.set_env(self.env)
self.redraw()
......@@ -564,102 +678,108 @@ class EditorModel(object):
def save(self):
self.log("save to ", self.env_filename, " working dir: ", os.getcwd())
# self.env.rail.save_transition_map(self.env_filename)
self.env.save(self.env_filename)
#self.env.save(self.env_filename)
RailEnvPersister.save(self.env, self.env_filename)
def regenerate(self, method=None, nAgents=0):
self.log("Regenerate size", self.regen_size)
def save_image(self):
self.view.oRT.gl.save_image('frame_{:04d}.bmp'.format(self.save_image_count))
self.save_image_count += 1
self.view.redraw()
def regenerate(self, method=None, nAgents=0, env=None):
self.log("Regenerate size",
self.regen_size_width,
self.regen_size_height)
if method is None or method == "Empty":
fnMethod = empty_rail_generator()
else:
fnMethod = sparse_rail_generator(nr_start_goal=nAgents, nr_extra=20, min_dist=12, seed=int(time.time()))
if method is None or method == "Random Cell":
fnMethod = random_rail_generator(cell_type_relative_proportion=[1] * 11)
if env is None:
self.env = RailEnv(width=self.regen_size_width, height=self.regen_size_height, rail_generator=fnMethod,
number_of_agents=nAgents, obs_builder_object=TreeObsForRailEnv(max_depth=2))
else:
fnMethod = complex_rail_generator(nr_start_goal=5, nr_extra=20, min_dist=12)
self.env = RailEnv(width=self.regen_size,
height=self.regen_size,
rail_generator=fnMethod,
# number_of_agents=self.env.get_num_agents(),
number_of_agents=nAgents,
obs_builder_object=TreeObsForRailEnv(max_depth=2))
self.env.reset(regen_rail=True)
self.env = env
self.env.reset(regenerate_rail=True)
self.fix_env()
self.selected_agent = None # clear the selected agent.
self.set_env(self.env)
self.player = Player(self.env)
self.view.new_env()
self.redraw()
def setRegenSize(self, size):
self.regen_size = size
def set_regen_width(self, size):
self.regen_size_width = size
def set_regen_height(self, size):
self.regen_size_height = size
def find_agent_at(self, rcCell):
for iAgent, agent in enumerate(self.env.agents_static):
if tuple(agent.position) == tuple(rcCell):
return iAgent
def find_agent_at(self, cell_row_col):
for agent_idx, agent in enumerate(self.env.agents):
if agent.position is None:
rc_pos = agent.initial_position
else:
rc_pos = agent.position
if tuple(rc_pos) == tuple(cell_row_col):
return agent_idx
return None
def click_agent(self, rcCell):
def click_agent(self, cell_row_col):
""" The user has clicked on a cell -
- If there is an agent, select it
- If that agent was already selected, then deselect it
- If there is no agent selected, and no agent in the cell, create one
- If there is an agent selected, and no agent in the cell, move the selected agent to the cell
* If there is an agent, select it
* If that agent was already selected, then deselect it
* If there is no agent selected, and no agent in the cell, create one
* If there is an agent selected, and no agent in the cell, move the selected agent to the cell
"""
# Has the user clicked on an existing agent?
iAgent = self.find_agent_at(rcCell)
if iAgent is None:
agent_idx = self.find_agent_at(cell_row_col)
# This is in case we still have a selected agent even though the env has been recreated
# with no agents.
if (self.selected_agent is not None) and (self.selected_agent > len(self.env.agents)):
self.selected_agent = None
# Defensive coding below - for cell_row_col to be a tuple, not a numpy array:
# numpy array breaks various things when loading the env.
if agent_idx is None:
# No
if self.iSelectedAgent is None:
if self.selected_agent is None:
# Create a new agent and select it.
agent_static = EnvAgentStatic(rcCell, 0, rcCell)
self.iSelectedAgent = self.env.add_agent_static(agent_static)
self.player = None # will need to start a new player
agent = EnvAgent(initial_position=tuple(cell_row_col),
initial_direction=0,
direction=0,
target=tuple(cell_row_col),
moving=False,
)
self.selected_agent = self.env.add_agent(agent)
# self.env.set_agent_active(agent)
self.view.oRT.update_background()
else:
# Move the selected agent to this cell
agent_static = self.env.agents_static[self.iSelectedAgent]
agent_static.position = rcCell
agent = self.env.agents[self.selected_agent]
agent.initial_position = tuple(cell_row_col)
agent.position = tuple(cell_row_col)
agent.old_position = tuple(cell_row_col)
else:
# Yes
# Have they clicked on the agent already selected?
if self.iSelectedAgent is not None and iAgent == self.iSelectedAgent:
if self.selected_agent is not None and agent_idx == self.selected_agent:
# Yes - deselect the agent
self.iSelectedAgent = None
self.selected_agent = None
else:
# No - select the agent
self.iSelectedAgent = iAgent
self.selected_agent = agent_idx
self.redraw()
def add_target(self, rcCell):
if self.iSelectedAgent is not None:
self.env.agents_static[self.iSelectedAgent].target = rcCell
def add_target(self, rc_cell):
if self.selected_agent is not None:
self.env.agents[self.selected_agent].target = tuple(rc_cell)
self.view.oRT.update_background()
self.redraw()
def step(self):
if self.player is None:
self.player = Player(self.env)
self.env.reset(regen_rail=False, replace_agents=False)
self.player.step()
self.redraw()
def start_run(self):
if self.thread is None:
self.thread = threading.Thread(target=self.bg_updater, args=(self.view.wProg_steps,))
self.thread.start()
else:
self.log("thread already present")
def bg_updater(self, wProg_steps):
try:
for i in range(20):
# self.log("step ", i)
self.step()
time.sleep(0.2)
wProg_steps.value = i+1 # indicate progress on bar
finally:
self.thread = None
def fix_env(self):
self.env.width = self.env.rail.width
self.env.height = self.env.rail.height
......@@ -671,18 +791,15 @@ class EditorModel(object):
self.view.log(*args, **kwargs)
def debug(self, *args, **kwargs):
if self.bDebug:
if self.debug_bool:
self.log(*args, **kwargs)
def debug_cell(self, rcCell):
binTrans = self.env.rail.get_transitions(rcCell)
def debug_cell(self, rc_cell):
binTrans = self.env.rail.get_full_transitions(*rc_cell)
sbinTrans = format(binTrans, "#018b")[2:]
self.debug("cell ",
rcCell,
rc_cell,
"Transitions: ",
binTrans,
sbinTrans,
[sbinTrans[i:(i + 4)] for i in range(0, len(sbinTrans), 4)])
\ No newline at end of file
from numpy.random import RandomState
import flatland.envs.observations as obs
import flatland.envs.rail_generators as rg
from flatland.core.transition_map import GridTransitionMap
from flatland.envs.line_generators import BaseLineGen
from flatland.envs.rail_env import RailEnv
from flatland.envs.timetable_utils import Line
from flatland.utils import editor
# Start and end all agents at the same place
class SchedGen2(BaseLineGen):
def __init__(self, rcStart, rcEnd, iDir):
self.rcStart = rcStart
self.rcEnd = rcEnd
self.iDir = iDir
def generate(self, rail: GridTransitionMap, num_agents: int, hints: dict = None, num_resets: int = None,
np_random: RandomState = None) -> Line:
return Line(agent_positions=[self.rcStart] * num_agents,
agent_directions=[self.iDir] * num_agents,
agent_targets=[self.rcEnd] * num_agents,
agent_speeds=[1.0] * num_agents)
# cycle through lists of start, end and direction
class SchedGen3(BaseLineGen):
def __init__(self, lrcStarts, lrcTargs, liDirs):
self.lrcStarts = lrcStarts
self.lrcTargs = lrcTargs
self.liDirs = liDirs
def generate(self, rail: GridTransitionMap, num_agents: int, hints: dict = None, num_resets: int = None,
np_random: RandomState = None) -> Line:
return Line(agent_positions=[self.lrcStarts[i % len(self.lrcStarts)] for i in range(num_agents)],
agent_directions=[self.liDirs[i % len(self.liDirs)] for i in range(num_agents)],
agent_targets=[self.lrcTargs[i % len(self.lrcTargs)] for i in range(num_agents)],
agent_speeds=[1.0] * num_agents)
def makeEnv(nAg=2, width=20, height=10, oSG=None):
env = RailEnv(width=width, height=height, rail_generator=rg.empty_rail_generator(),
number_of_agents=nAg,
line_generator=oSG,
obs_builder_object=obs.TreeObsForRailEnv(max_depth=1))
envModel = editor.EditorModel(env)
env.reset()
return env, envModel
def makeEnv2(nAg=2, shape=(20, 10), llrcPaths=[], lrcStarts=[], lrcTargs=[], liDirs=[], remove_agents_at_target=True):
oSG = SchedGen3(lrcStarts, lrcTargs, liDirs)
env = RailEnv(width=shape[0], height=shape[1],
rail_generator=rg.empty_rail_generator(),
number_of_agents=nAg,
line_generator=oSG,
obs_builder_object=obs.TreeObsForRailEnv(max_depth=1),
remove_agents_at_target=remove_agents_at_target,
record_steps=True)
envModel = editor.EditorModel(env)
env.reset()
for lrcPath in llrcPaths:
envModel.mod_rail_cell_seq(envModel.interpolate_path(lrcPath))
return env, envModel
ddEnvSpecs = {
# opposing stations with single alternative path
"single_alternative": {
"llrcPaths": [
[(1, 0), (1, 15)], # across the top
[(1, 4), (1, 6), (3, 6), (3, 12), (1, 12), (1, 14)], # alternative loop below
],
"lrcStarts": [(1, 3), (1, 14)],
"lrcTargs": [(1, 14), (1, 3)],
"liDirs": [1, 3]
},
# single spur so one agent needs to wait
"single_spur": {
"llrcPaths": [
[(1, 0), (1, 15)],
[(4, 0), (4, 6), (1, 6), (1, 8)]],
"lrcStarts": [(1, 3), (1, 14)],
"lrcTargs": [(1, 14), (4, 2)],
"liDirs": [1, 3]
},
# single spur so one agent needs to wait
"merging_spurs": {
"llrcPaths": [
[(1, 0), (1, 15), (7, 15), (7, 0)],
[(4, 0), (4, 6), (1, 6), (1, 8)],
# [((1,14), (1,16), (7,16), )]
],
"lrcStarts": [(1, 2), (4, 2)],
"lrcTargs": [(7, 3)],
"liDirs": [1]
},
# Concentric Loops
"concentric_loops": {
"llrcPaths": [
[(1, 1), (1, 5), (8, 5), (8, 1), (1, 1), (1, 3)],
[(1, 3), (1, 10), (8, 10), (8, 3)]
],
"lrcStarts": [(1, 3)],
"lrcTargs": [(2, 1)],
"liDirs": [1]
},
# two loops
"loop_with_loops": {
"llrcPaths": [
# big outer loop Row 1, 8; Col 1, 15
[(1, 1), (1, 15), (8, 15), (8, 1), (1, 1), (1, 3)],
# alternative 1
[(1, 3), (1, 5), (3, 5), (3, 10), (1, 10), (1, 12)],
# alternative 2
[(8, 3), (8, 5), (6, 5), (6, 10), (8, 10), (8, 12)],
],
# list of row,col of agent start cells
"lrcStarts": [(1, 3), (8, 3)],
# list of row,col of targets
"lrcTargs": [(8, 2), (1, 2)],
# list of initial directions
"liDirs": [1, 1],
}
}
def makeTestEnv(sName="single_alternative", nAg=2, remove_agents_at_target=True):
global ddEnvSpecs
dSpec = ddEnvSpecs[sName]
return makeEnv2(nAg=nAg, remove_agents_at_target=remove_agents_at_target, **dSpec)
def getAgentState(env):
dAgState = {}
for iAg, ag in enumerate(env.agents):
dAgState[iAg] = (*ag.position, ag.direction)
return dAgState
import matplotlib.pyplot as plt
from numpy import array
......@@ -7,6 +6,9 @@ class GraphicsLayer(object):
def __init__(self):
pass
def open_window(self):
pass
def plot(self, *args, **kwargs):
pass
......@@ -23,21 +25,31 @@ class GraphicsLayer(object):
pass
def pause(self, seconds=0.00001):
""" deprecated """
pass
def idle(self, seconds=0.00001):
""" process any display events eg redraw, resize.
Return only after the given number of seconds, ie idle / loop until that number.
"""
pass
def clf(self):
pass
def beginFrame(self):
def begin_frame(self):
pass
def endFrame(self):
pass
def getImage(self):
def get_image(self):
pass
def adaptColor(self, color, lighten=False):
def save_image(self, filename):
pass
def adapt_color(self, color, lighten=False):
if type(color) is str:
if color == "red" or color == "r":
color = (255, 0, 0)
......@@ -51,7 +63,7 @@ class GraphicsLayer(object):
color = tuple((gcolor[:3] * 255).astype(int))
else:
color = self.tColGrid
if lighten:
color = tuple([int(255 - (255 - iRGB) / 3) for iRGB in color])
......@@ -59,3 +71,24 @@ class GraphicsLayer(object):
def get_cmap(self, *args, **kwargs):
return plt.get_cmap(*args, **kwargs)
def set_rail_at(self, row, col, binTrans, iTarget=None, isSelected=False, rail_grid=None, num_agents=None):
""" Set the rail at cell (row, col) to have transitions binTrans.
The target argument can contain the index of the agent to indicate
that agent's target is at that cell, so that a station can be
rendered in the static rail layer.
"""
pass
def set_agent_at(self, iAgent, row, col, iDirIn, iDirOut, isSelected=False, rail_grid=None, show_debug=False,
clear_debug_text=True):
pass
def set_cell_occupied(self, iAgent, row, col):
pass
def resize(self, env):
pass
def build_background_map(self, dTargets):
pass
import pyglet as pgl
import time
from PIL import Image
# from numpy import array
# from pkg_resources import resource_string as resource_bytes
# from flatland.utils.graphics_layer import GraphicsLayer
from flatland.utils.graphics_pil import PILSVG
class PGLGL(PILSVG):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.window_open = False # means the window has not yet been opened.
self.close_requested = False # user has clicked
self.closed = False # windows has been closed (currently, we leave the env still running)
def open_window(self):
print("open_window - pyglet")
assert self.window_open is False, "Window is already open!"
self.window = pgl.window.Window(resizable=True, vsync=False, width=1200, height=800)
#self.__class__.window.title("Flatland")
#self.__class__.window.configure(background='grey')
self.window_open = True
@self.window.event
def on_draw():
#print("pyglet draw event")
self.window.clear()
self.show(from_event=True)
#print("pyglet draw event done")
@self.window.event
def on_resize(width, height):
#print(f"The window was resized to {width}, {height}")
self.show(from_event=True)
self.window.dispatch_event("on_draw")
#print("pyglet resize event done")
@self.window.event
def on_close():
self.close_requested = True
def close_window(self):
self.window.close()
self.closed=True
def show(self, block=False, from_event=False):
if not self.window_open:
self.open_window()
if self.close_requested:
if not self.closed:
self.close_window()
return
#tStart = time.time()
self._processEvents()
pil_img = self.alpha_composite_layers()
pil_img_resized = pil_img.resize((self.window.width, self.window.height), resample=Image.NEAREST)
# convert our PIL image to pyglet:
bytes_image = pil_img_resized.tobytes()
pgl_image = pgl.image.ImageData(pil_img_resized.width, pil_img_resized.height,
#self.window.width, self.window.height,
'RGBA',
bytes_image, pitch=-pil_img_resized.width * 4)
pgl_image.blit(0,0)
#tEnd = time.time()
#print("show time: ", tEnd - tStart)
def _processEvents(self):
""" This is the replacement for a custom event loop for Pyglet.
The lines below are typical of Pyglet examples.
Manually resizing the window is still very clunky.
"""
#print("process events...", end="")
pgl.clock.tick()
#for window in pgl.app.windows:
if not self.closed:
self.window.switch_to()
self.window.dispatch_events()
self.window.flip()
#print(" events done")
def idle(self, seconds=0.00001):
tStart = time.time()
tEnd = tStart + seconds
while (time.time() < tEnd):
self._processEvents()
#self.show()
time.sleep(min(seconds, 0.1))
def test_pyglet():
oGL = PGLGL(400,300)
time.sleep(2)
def test_event_loop():
""" Shows how it should work with the standard event loop
Resizing is fairly smooth (ie runs at least 10-20x a second)
"""
window = pgl.window.Window(resizable=True)
pil_img = Image.open("notebooks/simple_example_3.png")
def show():
pil_img_resized = pil_img.resize((window.width, window.height), resample=Image.NEAREST)
bytes_image = pil_img_resized.tobytes()
pgl_image = pgl.image.ImageData(pil_img_resized.width, pil_img_resized.height,
#self.window.width, self.window.height,
'RGBA',
bytes_image, pitch=-pil_img_resized.width * 4)
pgl_image.blit(0,0)
@window.event
def on_draw():
print("pyglet draw event")
window.clear()
show()
print("pyglet draw event done")
@window.event
def on_resize(width, height):
print(f"The window was resized to {width}, {height}")
#show()
print("pyglet resize event done")
@window.event
def on_close():
#self.close_requested = True
print("close")
pgl.app.run()
if __name__=="__main__":
#test_pyglet()
test_event_loop()
\ No newline at end of file
import io
import os
import time
#import tkinter as tk
from flatland.utils.graphics_layer import GraphicsLayer
from PIL import Image, ImageDraw # , ImageFont
from numpy import array
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from numpy import array
from pkg_resources import resource_string as resource_bytes
from flatland.utils.graphics_layer import GraphicsLayer
from flatland.core.grid.rail_env_grid import RailEnvTransitions # noqa: E402
class PILGL(GraphicsLayer):
def __init__(self, width, height, nPixCell=60):
self.nPixCell = 60
# tk.Tk() must be a singleton!
# https://stackoverflow.com/questions/26097811/image-pyimage2-doesnt-exist
# window = tk.Tk()
RAIL_LAYER = 0
PREDICTION_PATH_LAYER = 1
TARGET_LAYER = 2
AGENT_LAYER = 3
SELECTED_AGENT_LAYER = 4
SELECTED_TARGET_LAYER = 5
def __init__(self, width, height, jupyter=False, screen_width=800, screen_height=600):
self.yxBase = (0, 0)
self.linewidth = 4
# self.tile_size = self.nPixCell
self.n_agent_colors = 1 # overridden in loadAgent
self.width = width
self.height = height
self.background_grid = np.zeros(shape=(self.width, self.height))
if jupyter is False:
# NOTE: Currently removed the dependency on
# screeninfo. We have to find an alternate
# way to compute the screen width and height
# In the meantime, we are harcoding the 800x600
# assumption
self.screen_width = screen_width
self.screen_height = screen_height
w = (self.screen_width - self.width - 10) / (self.width + 1 + self.linewidth)
h = (self.screen_height - self.height - 10) / (self.height + 1 + self.linewidth)
self.nPixCell = int(max(1, np.ceil(min(w, h))))
else:
self.nPixCell = 40
# Total grid size at native scale
self.widthPx = self.width * self.nPixCell + self.linewidth
self.heightPx = self.height * self.nPixCell + self.linewidth
self.beginFrame()
self.tColBg = (255, 255, 255) # white background
# self.tColBg = (220, 120, 40) # background color
self.tColRail = (0, 0, 0) # black rails
self.tColGrid = (230,) * 3 # light grey for grid
self.xPx = int((self.screen_width - self.widthPx) / 2.0)
self.yPx = int((self.screen_height - self.heightPx) / 2.0)
self.layers = []
self.draws = []
self.tColBg = (255, 255, 255) # white background
self.tColRail = (0, 0, 0) # black rails
self.tColGrid = (230,) * 3 # light grey for grid
sColors = "d50000#c51162#aa00ff#6200ea#304ffe#2962ff#0091ea#00b8d4#00bfa5#00c853" + \
"#64dd17#aeea00#ffd600#ffab00#ff6d00#ff3d00#5d4037#455a64"
self.agent_colors = [self.rgb_s2i(sColor) for sColor in sColors.split("#")]
self.n_agent_colors = len(self.agent_colors)
self.firstFrame = True
self.old_background_image = (None, None, None)
self.create_layers()
def plot(self, gX, gY, color=None, linewidth=3, **kwargs):
color = self.adaptColor(color)
self.font = ImageFont.load_default()
# print(gX, gY)
def build_background_map(self, dTargets):
x = self.old_background_image
rebuild = False
if x[0] is None:
rebuild = True
else:
if len(x[0]) != len(dTargets):
rebuild = True
else:
if x[0] != dTargets:
rebuild = True
if x[1] != self.width:
rebuild = True
if x[2] != self.height:
rebuild = True
if rebuild:
# rebuild background_grid to control the visualisation of buildings, trees, mountains, lakes and river
self.background_grid = np.zeros(shape=(self.width, self.height))
# build base distance map (distance to targets)
for x in range(self.width):
for y in range(self.height):
distance = int(np.ceil(np.sqrt(self.width ** 2.0 + self.height ** 2.0)))
for rc in dTargets:
r = rc[1]
c = rc[0]
d = int(np.floor(np.sqrt((x - r) ** 2 + (y - c) ** 2)) / 0.5)
distance = min(d, distance)
self.background_grid[x][y] = distance
self.old_background_image = (dTargets, self.width, self.height)
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 get_agent_color(self, iAgent):
return self.agent_colors[iAgent % self.n_agent_colors]
def plot(self, gX, gY, color=None, linewidth=3, layer=RAIL_LAYER, opacity=255, **kwargs):
""" Draw a line joining the points in gX, GY - each an"""
color = self.adapt_color(color)
if len(color) == 3:
color += (opacity,)
elif len(color) == 4:
color = color[:3] + (opacity,)
gPoints = np.stack([array(gX), -array(gY)]).T * self.nPixCell
gPoints = list(gPoints.ravel())
# print(gPoints, color)
self.draw.line(gPoints, fill=color, width=self.linewidth)
# the width here was self.linewidth - not really sure of the implications
self.draws[layer].line(gPoints, fill=color, width=linewidth)
def scatter(self, gX, gY, color=None, marker="o", s=50, *args, **kwargs):
color = self.adaptColor(color)
def scatter(self, gX, gY, color=None, marker="o", s=50, layer=RAIL_LAYER, opacity=255, *args, **kwargs):
color = self.adapt_color(color)
r = np.sqrt(s)
gPoints = np.stack([np.atleast_1d(gX), -np.atleast_1d(gY)]).T * self.nPixCell
for x, y in gPoints:
self.draw.rectangle([(x-r, y-r), (x+r, y+r)], fill=color, outline=color)
self.draws[layer].rectangle([(x - r, y - r), (x + r, y + r)], fill=color, outline=color)
def draw_image_xy(self, pil_img, xyPixLeftTop, layer=RAIL_LAYER, ):
# Resize all PIL images just before drawing them
# to ensure that resizing doesnt affect the
# recolorizing strategies in place
#
# That said : All the code in this file needs
# some serious refactoring -_- to ensure the
# code style and structure is consitent.
# - Mohanty
pil_img = pil_img.resize(
(self.nPixCell, self.nPixCell)
)
if (pil_img.mode == "RGBA"):
pil_mask = pil_img
else:
pil_mask = None
self.layers[layer].paste(pil_img, xyPixLeftTop, pil_mask)
def draw_image_row_col(self, pil_img, rcTopLeft, layer=RAIL_LAYER, ):
xyPixLeftTop = tuple((array(rcTopLeft) * self.nPixCell)[[1, 0]])
self.draw_image_xy(pil_img, xyPixLeftTop, layer=layer)
def open_window(self):
pass
def text(self, *args, **kwargs):
def close_window(self):
pass
def text(self, xPx, yPx, strText, layer=RAIL_LAYER):
xyPixLeftTop = (xPx, yPx)
self.draws[layer].text(xyPixLeftTop, strText, font=self.font, fill=(0, 0, 0, 255))
def text_rowcol(self, rcTopLeft, strText, layer=AGENT_LAYER):
xyPixLeftTop = tuple((array(rcTopLeft) * self.nPixCell)[[1, 0]])
self.text(*xyPixLeftTop, strText, layer)
def prettify(self, *args, **kwargs):
pass
def prettify2(self, width, height, cell_size):
pass
def beginFrame(self):
self.img = Image.new("RGBA", (self.widthPx, self.heightPx), (255, 255, 255, 255))
self.draw = ImageDraw.Draw(self.img)
def begin_frame(self):
# Create a new agent layer
self.create_layer(iLayer=PILGL.AGENT_LAYER, clear=True)
self.create_layer(iLayer=PILGL.PREDICTION_PATH_LAYER, clear=True)
def show(self, block=False):
#print("show() - ", self.__class__)
pass
# plt.show(block=block)
def pause(self, seconds=0.00001):
pass
# plt.pause(seconds)
def getImage(self):
return array(self.img)
def idle(self, seconds=0.00001):
pass
def alpha_composite_layers(self):
img = self.layers[0]
for img2 in self.layers[1:]:
img = Image.alpha_composite(img, img2)
return img
def get_image(self):
""" return a blended / alpha composited image composed of all the layers,
with layer 0 at the "back".
"""
img = self.alpha_composite_layers()
return array(img)
def save_image(self, filename):
"""
Renders the current scene into a image file
:param filename: filename where to store the rendering output_generator
(supported image format *.bmp , .. , *.png)
"""
img = self.alpha_composite_layers()
img.save(filename)
def create_image(self, opacity=255):
img = Image.new("RGBA", (self.widthPx, self.heightPx), (255, 255, 255, opacity))
return img
def clear_layer(self, iLayer=0, opacity=None):
if opacity is None:
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_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:
opacity = 255 # "bottom" layer is opaque (for rails)
else:
opacity = 0 # subsequent layers are transparent
img = self.create_image(opacity)
self.layers.append(img)
self.draws.append(ImageDraw.Draw(img))
else:
# We do already have this iLayer. Clear it if requested.
if clear:
self.clear_layer(iLayer)
def create_layers(self, clear=True):
self.create_layer(PILGL.RAIL_LAYER, clear=clear) # rail / background (scene)
self.create_layer(PILGL.AGENT_LAYER, clear=clear) # agents
self.create_layer(PILGL.TARGET_LAYER, clear=clear) # agents
self.create_layer(PILGL.PREDICTION_PATH_LAYER, clear=clear) # drawing layer for agent's prediction path
self.create_layer(PILGL.SELECTED_AGENT_LAYER, clear=clear) # drawing layer for selected agent
self.create_layer(PILGL.SELECTED_TARGET_LAYER, clear=clear) # drawing layer for selected agent's target
class PILSVG(PILGL):
"""
Note : This class should now ideally be called as PILPNG,
but for backward compatibility, and to not introduce any breaking changes at this point
we are sticking to the legacy name of PILSVG (when in practice we are not using SVG anymore)
"""
def __init__(self, width, height, jupyter=False, screen_width=800, screen_height=600):
oSuper = super()
oSuper.__init__(width, height, jupyter, screen_width, screen_height)
self.lwAgents = []
self.agents_prev = []
self.load_buildings()
self.load_scenery()
self.load_rail()
self.load_agent()
def process_events(self):
time.sleep(0.001)
def clear_rails(self):
self.create_layers()
self.clear_agents()
def clear_agents(self):
for wAgent in self.lwAgents:
self.layout.removeWidget(wAgent)
self.lwAgents = []
self.agents_prev = []
def pil_from_png_file(self, package, resource):
bytestring = resource_bytes(package, resource)
with io.BytesIO(bytestring) as fIn:
pil_img = Image.open(fIn)
pil_img.load()
return pil_img
def load_buildings(self):
lBuildingFiles = [
"Buildings-Bank.png",
"Buildings-Bar.png",
"Buildings-Wohnhaus.png",
"Buildings-Hochhaus.png",
"Buildings-Hotel.png",
"Buildings-Office.png",
"Buildings-Polizei.png",
"Buildings-Post.png",
"Buildings-Supermarkt.png",
"Buildings-Tankstelle.png",
"Buildings-Fabrik_A.png",
"Buildings-Fabrik_B.png",
"Buildings-Fabrik_C.png",
"Buildings-Fabrik_D.png",
"Buildings-Fabrik_E.png",
"Buildings-Fabrik_F.png",
"Buildings-Fabrik_G.png",
"Buildings-Fabrik_H.png",
"Buildings-Fabrik_I.png"
]
imgBg = self.pil_from_png_file('flatland.png', "Background_city.png")
imgBg = imgBg.convert("RGBA")
self.lBuildings = []
for sFile in lBuildingFiles:
img = self.pil_from_png_file('flatland.png', sFile)
img = Image.alpha_composite(imgBg, img)
self.lBuildings.append(img)
def load_scenery(self):
scenery_files = [
"Scenery-Laubbaume_A.png",
"Scenery-Laubbaume_B.png",
"Scenery-Laubbaume_C.png",
"Scenery-Nadelbaume_A.png",
"Scenery-Nadelbaume_B.png",
"Scenery-Bergwelt_B.png"
]
scenery_files_d2 = [
"Scenery-Bergwelt_C_Teil_1_links.png",
"Scenery-Bergwelt_C_Teil_2_rechts.png"
]
scenery_files_d3 = [
"Scenery-Bergwelt_A_Teil_1_links.png",
"Scenery-Bergwelt_A_Teil_2_mitte.png",
"Scenery-Bergwelt_A_Teil_3_rechts.png"
]
scenery_files_water = [
"Scenery_Water.png"
]
img_back_ground = self.pil_from_png_file('flatland.png', "Background_Light_green.png").convert("RGBA")
self.scenery_background_white = self.pil_from_png_file('flatland.png', "Background_white.png").convert("RGBA")
self.scenery = []
for file in scenery_files:
img = self.pil_from_png_file('flatland.png', file)
img = Image.alpha_composite(img_back_ground, img)
self.scenery.append(img)
self.scenery_d2 = []
for file in scenery_files_d2:
img = self.pil_from_png_file('flatland.png', file)
img = Image.alpha_composite(img_back_ground, img)
self.scenery_d2.append(img)
self.scenery_d3 = []
for file in scenery_files_d3:
img = self.pil_from_png_file('flatland.png', file)
img = Image.alpha_composite(img_back_ground, img)
self.scenery_d3.append(img)
self.scenery_water = []
for file in scenery_files_water:
img = self.pil_from_png_file('flatland.png', file)
img = Image.alpha_composite(img_back_ground, img)
self.scenery_water.append(img)
def load_rail(self):
""" Load the rail SVG images, apply rotations, and store as PIL images.
"""
rail_files = {
"": "Background_Light_green.png",
"WE": "Gleis_Deadend.png",
"WW EE NN SS": "Gleis_Diamond_Crossing.png",
"WW EE": "Gleis_horizontal.png",
"EN SW": "Gleis_Kurve_oben_links.png",
"WN SE": "Gleis_Kurve_oben_rechts.png",
"ES NW": "Gleis_Kurve_unten_links.png",
"NE WS": "Gleis_Kurve_unten_rechts.png",
"NN SS": "Gleis_vertikal.png",
"NN SS EE WW ES NW SE WN": "Weiche_Double_Slip.png",
"EE WW EN SW": "Weiche_horizontal_oben_links.png",
"EE WW SE WN": "Weiche_horizontal_oben_rechts.png",
"EE WW ES NW": "Weiche_horizontal_unten_links.png",
"EE WW NE WS": "Weiche_horizontal_unten_rechts.png",
"NN SS EE WW NW ES": "Weiche_Single_Slip.png",
"NE NW ES WS": "Weiche_Symetrical.png",
"NN SS EN SW": "Weiche_vertikal_oben_links.png",
"NN SS SE WN": "Weiche_vertikal_oben_rechts.png",
"NN SS NW ES": "Weiche_vertikal_unten_links.png",
"NN SS NE WS": "Weiche_vertikal_unten_rechts.png",
"NE NW ES WS SS NN": "Weiche_Symetrical_gerade.png",
"NE EN SW WS": "Gleis_Kurve_oben_links_unten_rechts.png"
}
target_files = {
"EW": "Bahnhof_#d50000_Deadend_links.png",
"NS": "Bahnhof_#d50000_Deadend_oben.png",
"WE": "Bahnhof_#d50000_Deadend_rechts.png",
"SN": "Bahnhof_#d50000_Deadend_unten.png",
"EE WW": "Bahnhof_#d50000_Gleis_horizontal.png",
"NN SS": "Bahnhof_#d50000_Gleis_vertikal.png"}
# Dict of rail cell images indexed by binary transitions
pil_rail_files_org = self.load_pngs(rail_files, rotate=True)
pil_rail_files = self.load_pngs(rail_files, rotate=True, background_image="Background_rail.png",
whitefilter="Background_white_filter.png")
# Load the target files (which have rails and transitions of their own)
# They are indexed by (binTrans, iAgent), ie a tuple of the binary transition and the agent index
pil_target_files_org = self.load_pngs(target_files, rotate=False, agent_colors=self.agent_colors)
pil_target_files = self.load_pngs(target_files, rotate=False, agent_colors=self.agent_colors,
background_image="Background_rail.png",
whitefilter="Background_white_filter.png")
# Load station and recolorize them
station = self.pil_from_png_file('flatland.png', "Bahnhof_#d50000_target.png")
self.station_colors = self.recolor_image(station, [0, 0, 0], self.agent_colors, False)
cell_occupied = self.pil_from_png_file('flatland.png', "Cell_occupied.png")
self.cell_occupied = self.recolor_image(cell_occupied, [0, 0, 0], self.agent_colors, False)
# Merge them with the regular rails.
# https://stackoverflow.com/questions/38987/how-to-merge-two-dictionaries-in-a-single-expression
self.pil_rail = {**pil_rail_files, **pil_target_files}
self.pil_rail_org = {**pil_rail_files_org, **pil_target_files_org}
def load_pngs(self, file_directory, rotate=False, agent_colors=False, background_image=None, whitefilter=None):
pil = {}
transitions = RailEnvTransitions()
directions = list("NESW")
for transition, file in file_directory.items():
# Translate the ascii transition description in the format "NE WS" to the
# binary list of transitions as per RailEnv - NESW (in) x NESW (out)
transition_16_bit = ["0"] * 16
for sTran in transition.split(" "):
if len(sTran) == 2:
in_direction = directions.index(sTran[0])
out_direction = directions.index(sTran[1])
transition_idx = 4 * in_direction + out_direction
transition_16_bit[transition_idx] = "1"
transition_16_bit_string = "".join(transition_16_bit)
binary_trans = int(transition_16_bit_string, 2)
pil_rail = self.pil_from_png_file('flatland.png', file).convert("RGBA")
if background_image is not None:
img_bg = self.pil_from_png_file('flatland.png', background_image).convert("RGBA")
pil_rail = Image.alpha_composite(img_bg, pil_rail)
if whitefilter is not None:
img_bg = self.pil_from_png_file('flatland.png', whitefilter).convert("RGBA")
pil_rail = Image.alpha_composite(pil_rail, img_bg)
if rotate:
# For rotations, we also store the base image
pil[binary_trans] = pil_rail
# Rotate both the transition binary and the image and save in the dict
for nRot in [90, 180, 270]:
binary_trans_2 = transitions.rotate_transition(binary_trans, nRot)
# PIL rotates anticlockwise for positive theta
pil_rail_2 = pil_rail.rotate(-nRot)
pil[binary_trans_2] = pil_rail_2
if agent_colors:
# For recoloring, we don't store the base image.
base_color = self.rgb_s2i("d50000")
pils = self.recolor_image(pil_rail, base_color, self.agent_colors)
for color_idx, pil_rail_2 in enumerate(pils):
pil[(binary_trans, color_idx)] = pils[color_idx]
return pil
def set_predicion_path_at(self, row, col, binary_trans, agent_rail_color):
colored_rail = self.recolor_image(self.pil_rail_org[binary_trans],
[61, 61, 61], [agent_rail_color],
False)[0]
self.draw_image_row_col(colored_rail, (row, col), layer=PILGL.PREDICTION_PATH_LAYER)
def set_rail_at(self, row, col, binary_trans, target=None, is_selected=False, rail_grid=None, num_agents=None,
show_debug=True):
if binary_trans in self.pil_rail:
pil_track = self.pil_rail[binary_trans]
if target is not None:
target_img = self.station_colors[target % len(self.station_colors)]
target_img = Image.alpha_composite(pil_track, target_img)
self.draw_image_row_col(target_img, (row, col), layer=PILGL.TARGET_LAYER)
if show_debug:
self.text_rowcol((row + 0.8, col + 0.0), strText=str(target), layer=PILGL.TARGET_LAYER)
city_size = 1
if num_agents is not None:
city_size = max(1, np.log(1 + num_agents) / 2.5)
if binary_trans == 0:
if self.background_grid[col][row] <= 4 + np.ceil(((col * row + col) % 10) / city_size):
a = int(self.background_grid[col][row])
a = a % len(self.lBuildings)
if (col + row + col * row) % 13 > 11:
pil_track = self.scenery[a % len(self.scenery)]
else:
if (col + row + col * row) % 3 == 0:
a = (a + (col + row + col * row)) % len(self.lBuildings)
pil_track = self.lBuildings[a]
elif ((self.background_grid[col][row] > 5 + ((col * row + col) % 3)) or
((col ** 3 + row ** 2 + col * row) % 10 == 0)):
a = int(self.background_grid[col][row]) - 4
a2 = (a + (col + row + col * row + col ** 3 + row ** 4))
if a2 % 64 > 11:
a = a2
a_l = a % len(self.scenery)
if a2 % 50 == 49:
pil_track = self.scenery_water[0]
else:
pil_track = self.scenery[a_l]
if rail_grid is not None:
if a2 % 11 > 3:
if a_l == len(self.scenery) - 1:
# mountain
if col > 1 and row % 7 == 1:
if rail_grid[row, col - 1] == 0:
self.draw_image_row_col(self.scenery_d2[0], (row, col - 1),
layer=PILGL.RAIL_LAYER)
pil_track = self.scenery_d2[1]
else:
if a_l == len(self.scenery) - 1:
# mountain
if col > 2 and not (row % 7 == 1):
if rail_grid[row, col - 2] == 0 and rail_grid[row, col - 1] == 0:
self.draw_image_row_col(self.scenery_d3[0], (row, col - 2),
layer=PILGL.RAIL_LAYER)
self.draw_image_row_col(self.scenery_d3[1], (row, col - 1),
layer=PILGL.RAIL_LAYER)
pil_track = self.scenery_d3[2]
self.draw_image_row_col(pil_track, (row, col), layer=PILGL.RAIL_LAYER)
else:
print("Can't render - illegal rail or SVG element is undefined:", row, col,
format(binary_trans, "#018b")[2:], binary_trans)
if target is not None:
if is_selected:
svgBG = self.pil_from_png_file('flatland.png', "Selected_Target.png")
self.clear_layer(PILGL.SELECTED_TARGET_LAYER, 0)
self.draw_image_row_col(svgBG, (row, col), layer=PILGL.SELECTED_TARGET_LAYER)
def recolor_image(self, pil, a3BaseColor, ltColors, invert=False):
rgbaImg = array(pil)
pils = []
for iColor, tnColor in enumerate(ltColors):
# find the pixels which match the base paint color
if invert:
xy_color_mask = np.all(rgbaImg[:, :, 0:3] - a3BaseColor != 0, axis=2)
else:
xy_color_mask = np.all(rgbaImg[:, :, 0:3] - a3BaseColor == 0, axis=2)
rgbaImg2 = np.copy(rgbaImg)
# Repaint the base color with the new color
rgbaImg2[xy_color_mask, 0:3] = tnColor
pil2 = Image.fromarray(rgbaImg2)
pils.append(pil2)
return pils
def load_agent(self):
# Seed initial train/zug files indexed by tuple(iDirIn, iDirOut):
file_directory = {
(0, 0): "Zug_Gleis_#0091ea.png",
(1, 2): "Zug_1_Weiche_#0091ea.png",
(0, 3): "Zug_2_Weiche_#0091ea.png"
}
# "paint" color of the train images we load - this is the color we will change.
# base_color = self.rgb_s2i("0091ea") \# noqa: E800
# temporary workaround for trains / agents renamed with different colour:
base_color = self.rgb_s2i("d50000")
self.pil_zug = {}
for directions, path_svg in file_directory.items():
in_direction, out_direction = directions
pil_zug = self.pil_from_png_file('flatland.png', path_svg)
# Rotate both the directions and the image and save in the dict
for rot_direction in range(4):
rotation_degree = rot_direction * 90
in_direction_2 = (in_direction + rot_direction) % 4
out_direction_2 = (out_direction + rot_direction) % 4
# PIL rotates anticlockwise for positive theta
pil_zug_2 = pil_zug.rotate(-rotation_degree)
# Save colored versions of each rotation / variant
pils = self.recolor_image(pil_zug_2, base_color, self.agent_colors)
for color_idx, pil_zug_3 in enumerate(pils):
self.pil_zug[(in_direction_2, out_direction_2, color_idx)] = pils[color_idx]
def set_agent_at(self, agent_idx, row, col, in_direction, out_direction, is_selected,
rail_grid=None, show_debug=False, clear_debug_text=True, malfunction=False):
delta_dir = (out_direction - in_direction) % 4
color_idx = agent_idx % self.n_agent_colors
# when flipping direction at a dead end, use the "out_direction" direction.
if delta_dir == 2:
in_direction = out_direction
pil_zug = self.pil_zug[(in_direction % 4, out_direction % 4, color_idx)]
self.draw_image_row_col(pil_zug, (row, col), layer=PILGL.AGENT_LAYER)
if rail_grid is not None:
if rail_grid[row, col] == 0.0:
self.draw_image_row_col(self.scenery_background_white, (row, col), layer=PILGL.RAIL_LAYER)
if is_selected:
bg_svg = self.pil_from_png_file('flatland.png', "Selected_Agent.png")
self.clear_layer(PILGL.SELECTED_AGENT_LAYER, 0)
self.draw_image_row_col(bg_svg, (row, col), layer=PILGL.SELECTED_AGENT_LAYER)
if show_debug:
if not clear_debug_text:
dr = 0.2
dc = 0.2
if in_direction == 0:
dr = 0.8
dc = 0.0
if in_direction == 1:
dr = 0.0
dc = 0.8
if in_direction == 2:
dr = 0.4
dc = 0.8
if in_direction == 3:
dr = 0.8
dc = 0.4
self.text_rowcol((row + dr, col + dc,), str(agent_idx), layer=PILGL.SELECTED_AGENT_LAYER)
else:
self.text_rowcol((row + 0.2, col + 0.2,), str(agent_idx))
if malfunction:
self.draw_malfunction(agent_idx, (row, col))
def set_cell_occupied(self, agent_idx, row, col):
occupied_im = self.cell_occupied[agent_idx % len(self.cell_occupied)]
self.draw_image_row_col(occupied_im, (row, col), 1)
def draw_malfunction(self, agent_idx, rcTopLeft):
# Roughly an "X" shape to indicate malfunction
grcOffsets = np.array([[0, 0], [1, 1], [1, 0], [0, 1]])
grcPoints = np.array(rcTopLeft)[None] + grcOffsets
gxyPoints = grcPoints[:, [1, 0]]
gxPoints, gyPoints = gxyPoints.T
# print(agent_idx, rcTopLeft, gxyPoints, "X:", gxPoints, "Y:", gyPoints)
# plot(self, gX, gY, color=None, linewidth=3, layer=RAIL_LAYER, opacity=255, **kwargs):
self.plot(gxPoints, -gyPoints, color=(0, 0, 0, 255), layer=PILGL.AGENT_LAYER, linewidth=2)
def main2():
gl = PILSVG(10, 10)
for i in range(10):
gl.begin_frame()
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.process_events()
time.sleep(0.1)
time.sleep(1)
if __name__ == "__main__":
main()
import numpy as np
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap, QPainter, QColor, QPolygon
from PyQt5.QtCore import QPoint, QRect # QSize
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QTextEdit
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QFrame
import os
class Window(QMainWindow):
"""
Simple application window to render the environment into
"""
def __init__(self):
super().__init__()
self.setWindowTitle('MiniGrid Gym Environment')
# Image label to display the rendering
self.imgLabel = QLabel()
self.imgLabel.setFrameStyle(QFrame.Panel | QFrame.Sunken)
if False:
# Text box for the mission
self.missionBox = QTextEdit()
self.missionBox.setReadOnly(True)
self.missionBox.setMinimumSize(400, 100)
# Center the image
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.imgLabel)
hbox.addStretch(1)
# Arrange widgets vertically
vbox = QVBoxLayout()
vbox.addLayout(hbox)
# vbox.addWidget(self.missionBox)
# Create a main widget for the window
self.mainWidget = QWidget(self)
self.setCentralWidget(self.mainWidget)
self.mainWidget.setLayout(vbox)
# Show the application window
self.show()
self.setFocus()
self.closed = False
# Callback for keyboard events
self.keyDownCb = None
def closeEvent(self, event):
self.closed = True
def setPixmap(self, pixmap):
self.imgLabel.setPixmap(pixmap)
def setText(self, text):
# self.missionBox.setPlainText(text)
pass
def setKeyDownCb(self, callback):
self.keyDownCb = callback
def keyPressEvent(self, e):
if self.keyDownCb is None:
return
keyName = None
if e.key() == Qt.Key_Left:
keyName = 'LEFT'
elif e.key() == Qt.Key_Right:
keyName = 'RIGHT'
elif e.key() == Qt.Key_Up:
keyName = 'UP'
elif e.key() == Qt.Key_Down:
keyName = 'DOWN'
elif e.key() == Qt.Key_Space:
keyName = 'SPACE'
elif e.key() == Qt.Key_Return:
keyName = 'RETURN'
elif e.key() == Qt.Key_Alt:
keyName = 'ALT'
elif e.key() == Qt.Key_Control:
keyName = 'CTRL'
elif e.key() == Qt.Key_PageUp:
keyName = 'PAGE_UP'
elif e.key() == Qt.Key_PageDown:
keyName = 'PAGE_DOWN'
elif e.key() == Qt.Key_Backspace:
keyName = 'BACKSPACE'
elif e.key() == Qt.Key_Escape:
keyName = 'ESCAPE'
if keyName is None:
return
self.keyDownCb(keyName)
class QtRenderer(object):
def __init__(self, width, height, ownWindow=False):
self.width = width
self.height = height
self.img = QImage(width, height, QImage.Format_RGB888)
self.painter = QPainter()
self.window = None
if ownWindow:
self.app = QApplication([])
self.window = Window()
self.iFrame = 0 # for movie capture
def close(self):
"""
Deallocate resources used
"""
pass
def beginFrame(self):
self.painter.begin(self.img)
# self.painter.setRenderHint(QPainter.Antialiasing, False)
# Clear the background
self.painter.setBrush(QColor(0, 0, 0))
self.painter.drawRect(0, 0, self.width - 1, self.height - 1)
def endFrame(self):
self.painter.end()
if self.window:
if self.window.closed:
self.window = None
else:
self.window.setPixmap(self.getPixmap())
self.app.processEvents()
def getPixmap(self):
return QPixmap.fromImage(self.img)
def getArray(self):
"""
Get a numpy array of RGB pixel values.
The size argument should be (3,w,h)
"""
width = self.width
height = self.height
shape = (width, height, 3)
numBytes = self.width * self.height * 3
buf = self.img.bits().asstring(numBytes)
output = np.frombuffer(buf, dtype='uint8')
output = output.reshape(shape)
return output
def push(self):
self.painter.save()
def pop(self):
self.painter.restore()
def rotate(self, degrees):
self.painter.rotate(degrees)
def translate(self, x, y):
self.painter.translate(x, y)
def scale(self, x, y):
self.painter.scale(x, y)
def setLineColor(self, r, g, b, a=255):
self.painter.setPen(QColor(r, g, b, a))
def setColor(self, r, g, b, a=255):
self.painter.setBrush(QColor(r, g, b, a))
def setLineWidth(self, width):
pen = self.painter.pen()
pen.setWidthF(width)
self.painter.setPen(pen)
def drawLine(self, x0, y0, x1, y1):
self.painter.drawLine(x0, y0, x1, y1)
def drawCircle(self, x, y, r):
center = QPoint(x, y)
self.painter.drawEllipse(center, r, r)
def drawPolygon(self, points):
"""Takes a list of points (tuples) as input"""
points = map(lambda p: QPoint(p[0], p[1]), points)
self.painter.drawPolygon(QPolygon(points))
def drawRect(self, x, y, w, h):
self.painter.drawRect(x, y, w, h)
def drawPolyline(self, points):
"""Takes a list of points (tuples) as input"""
points = map(lambda p: QPoint(p[0], p[1]), points)
self.painter.drawPolyline(QPolygon(points))
def fillRect(self, x, y, width, height, r, g, b, a=255):
self.painter.fillRect(QRect(x, y, width, height), QColor(r, g, b, a))
def drawText(self, x, y, sText):
self.painter.drawText(x, y, sText)
def takeSnapshot(self, sDir="./movie"):
oWidget = self.window.mainWidget
oPixmap = oWidget.grab()
if not os.path.isdir(sDir):
os.mkdir(sDir)
nRunIn = 30
if self.iFrame > nRunIn:
sfImage = "%s/frame%05d.jpg" % (sDir, self.iFrame - nRunIn)
oPixmap.save(sfImage, "jpg")
self.iFrame += 1
from typing import List, NamedTuple
import numpy as np
from IPython import display
from ipycanvas import canvas
from flatland.envs.rail_env import RailEnvActions
from flatland.utils.rendertools import RenderTool
class Behaviour():
def __init__(self, env):
self.env = env
self.nAg = len(env.agents)
def getActions(self):
return {}
class AlwaysForward(Behaviour):
def getActions(self):
return {i: RailEnvActions.MOVE_FORWARD for i in range(self.nAg)}
class DelayedStartForward(AlwaysForward):
def __init__(self, env, nStartDelay=2):
self.nStartDelay = nStartDelay
super().__init__(env)
def getActions(self):
iStep = self.env._elapsed_steps + 1
nAgentsMoving = min(self.nAg, iStep // self.nStartDelay)
return {i: RailEnvActions.MOVE_FORWARD for i in range(nAgentsMoving)}
AgentPause = NamedTuple("AgentPause",
[
("iAg", int),
("iPauseAt", int),
("iPauseFor", int)
])
class ForwardWithPause(Behaviour):
def __init__(self, env, lPauses: List[AgentPause]):
self.env = env
self.nAg = len(env.agents)
self.lPauses = lPauses
self.dAgPaused = {}
def getActions(self):
iStep = self.env._elapsed_steps + 1 # add one because this is called before step()
# new pauses starting this step
lNewPauses = [tPause for tPause in self.lPauses if tPause.iPauseAt == iStep]
# copy across the agent index and pause length
for pause in lNewPauses:
self.dAgPaused[pause.iAg] = pause.iPauseFor
# default action is move forward
dAction = {i: RailEnvActions.MOVE_FORWARD for i in range(self.nAg)}
# overwrite paused agents with stop
for iAg in self.dAgPaused:
dAction[iAg] = RailEnvActions.STOP_MOVING
# decrement the counters for each pause, and remove any expired pauses.
lFinished = []
for iAg in self.dAgPaused:
self.dAgPaused[iAg] -= 1
if self.dAgPaused[iAg] <= 0:
lFinished.append(iAg)
for iAg in lFinished:
self.dAgPaused.pop(iAg, None)
return dAction
class Deterministic(Behaviour):
def __init__(self, env, dAg_lActions):
super().__init__(env)
self.dAg_lActions = dAg_lActions
def getActions(self):
iStep = self.env._elapsed_steps
dAg_Action = {}
for iAg, lActions in self.dAg_lActions.items():
if iStep < len(lActions):
iAct = lActions[iStep]
else:
iAct = RailEnvActions.DO_NOTHING
dAg_Action[iAg] = iAct
# print(iStep, dAg_Action[0])
return dAg_Action
class EnvCanvas():
def __init__(self, env, behaviour: Behaviour = None):
self.env = env
self.iStep = 0
if behaviour is None:
behaviour = AlwaysForward(env)
self.behaviour = behaviour
self.oRT = RenderTool(env, show_debug=True)
self.oCan = canvas.Canvas(size=(600, 300))
self.render()
def render(self):
self.oRT.render_env(show_rowcols=True, show_inactive_agents=False, show_observations=False)
gIm = self.oRT.get_image()
red_channel = gIm[:, :, 0]
blue_channel = gIm[:, :, 1]
green_channel = gIm[:, :, 2]
image_data = np.stack((red_channel, blue_channel, green_channel), axis=2)
self.oCan.put_image_data(image_data)
def step(self):
dAction = self.behaviour.getActions()
self.env.step(dAction)
def show(self):
self.render()
display.display(self.oCan)
# https://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python
def str2bool(v):
return v.lower() in ("yes", "true", "t", "1")
# in order for enumeration to be deterministic for testing purposes
# https://stackoverflow.com/questions/1653970/does-python-have-an-ordered-set
from collections import OrderedDict
from collections.abc import MutableSet
class OrderedSet(OrderedDict, MutableSet):
def update(self, *args, **kwargs):
if kwargs:
raise TypeError("update() takes no keyword arguments")
for s in args:
for e in s:
self.add(e)
def add(self, elem):
self[elem] = None
def discard(self, elem):
self.pop(elem, None)
def __le__(self, other):
return all(e in other for e in self)
def __lt__(self, other):
return self <= other and self != other
def __ge__(self, other):
return all(e in self for e in other)
def __gt__(self, other):
return self >= other and self != other
def __repr__(self):
return 'OrderedSet([%s])' % (', '.join(map(repr, self.keys())))
def __str__(self):
return '{%s}' % (', '.join(map(repr, self.keys())))
difference = property(lambda self: self.__sub__)
difference_update = property(lambda self: self.__isub__)
intersection = property(lambda self: self.__and__)
intersection_update = property(lambda self: self.__iand__)
issubset = property(lambda self: self.__le__)
issuperset = property(lambda self: self.__ge__)
symmetric_difference = property(lambda self: self.__xor__)
symmetric_difference_update = property(lambda self: self.__ixor__)
union = property(lambda self: self.__or__)
from flatland.utils.graphics_qt import QtRenderer
from numpy import array
from flatland.utils.graphics_layer import GraphicsLayer
# from matplotlib import pyplot as plt
import numpy as np
class QTGL(GraphicsLayer):
def __init__(self, width, height):
self.cell_pixels = 60
self.tile_size = self.cell_pixels
self.width = width
self.height = height
# Total grid size at native scale
self.widthPx = self.width * self.cell_pixels
self.heightPx = self.height * self.cell_pixels
self.qtr = QtRenderer(self.widthPx, self.heightPx, ownWindow=True)
self.qtr.beginFrame()
self.qtr.push()
# This comment comes from minigrid. Not sure if it's still true. Jeremy.
# Internally, we draw at the "large" full-grid resolution, but we
# use the renderer to scale back to the desired size
self.qtr.scale(self.tile_size / self.cell_pixels, self.tile_size / self.cell_pixels)
self.tColBg = (255, 255, 255) # white background
# self.tColBg = (220, 120, 40) # background color
self.tColRail = (0, 0, 0) # black rails
self.tColGrid = (230,) * 3 # light grey for grid
# Draw the background of the in-world cells
self.qtr.fillRect(0, 0, self.widthPx, self.heightPx, *self.tColBg)
self.qtr.pop()
self.qtr.endFrame()
def plot(self, gX, gY, color=None, lw=2, **kwargs):
color = self.adaptColor(color)
self.qtr.setLineColor(*color)
lastx = lasty = None
if False:
for x, y in zip(gX, gY):
if lastx is not None:
# print("line", lastx, lasty, x, y)
self.qtr.drawLine(
lastx * self.cell_pixels, -lasty * self.cell_pixels,
x * self.cell_pixels, -y * self.cell_pixels)
lastx = x
lasty = y
else:
gPoints = np.stack([array(gX), -array(gY)]).T * self.cell_pixels
self.qtr.setLineWidth(5)
self.qtr.drawPolyline(gPoints)
def scatter(self, gX, gY, color=None, marker="o", s=50, *args, **kwargs):
color = self.adaptColor(color)
self.qtr.setColor(*color)
self.qtr.setLineColor(*color)
r = np.sqrt(s)
gPoints = np.stack([np.atleast_1d(gX), -np.atleast_1d(gY)]).T * self.cell_pixels
for x, y in gPoints:
self.qtr.drawCircle(x, y, r)
def text(self, x, y, sText):
self.qtr.drawText(x * self.cell_pixels, -y * self.cell_pixels, sText)
def prettify(self, *args, **kwargs):
pass
def prettify2(self, width, height, cell_size):
pass
def show(self, block=False):
pass
def pause(self, seconds=0.00001):
pass
def beginFrame(self):
self.qtr.beginFrame()
self.qtr.push()
self.qtr.fillRect(0, 0, self.widthPx, self.heightPx, *self.tColBg)
def endFrame(self):
self.qtr.pop()
self.qtr.endFrame()
def main():
gl = QTGL(10, 10)
for i in range(10):
gl.beginFrame()
gl.plot([3+i, 4], [-4-i, -5], color="r")
gl.endFrame()
import time
time.sleep(1)
if __name__ == "__main__":
main()
from recordtype import recordtype
import time
import warnings
from collections import deque
from enum import IntEnum
import numpy as np
from numpy import array
# import xarray as xr
import matplotlib.pyplot as plt
import time
from collections import deque
from flatland.utils.render_qt import QTGL
from flatland.utils.graphics_pil import PILGL
from flatland.utils.graphics_layer import GraphicsLayer
from recordtype import recordtype
# TODO: suggested renaming to RailEnvRenderTool, as it will only work with RailEnv!
from flatland.envs.step_utils.states import TrainState
from flatland.utils.graphics_pil import PILGL, PILSVG
from flatland.utils.graphics_pgl import PGLGL
class MPLGL(GraphicsLayer):
def __init__(self, width, height):
self.width = width
self.height = height
self.yxBase = array([6, 21]) # pixel offset
self.nPixCell = 700 / width
self.img = None
def plot(self, *args, **kwargs):
plt.plot(*args, **kwargs)
# TODO: suggested renaming to RailEnvRenderTool, as it will only work with RailEnv!
def scatter(self, *args, **kwargs):
plt.scatter(*args, **kwargs)
class AgentRenderVariant(IntEnum):
BOX_ONLY = 0
ONE_STEP_BEHIND = 1
AGENT_SHOWS_OPTIONS = 2
ONE_STEP_BEHIND_AND_BOX = 3
AGENT_SHOWS_OPTIONS_AND_BOX = 4
def text(self, *args, **kwargs):
plt.text(*args, **kwargs)
def prettify(self, *args, **kwargs):
ax = plt.gca()
plt.xticks(range(int(ax.get_xlim()[1]) + 1))
plt.yticks(range(int(ax.get_ylim()[1]) + 1))
plt.grid()
plt.xlabel("Euclidean distance")
plt.ylabel("Tree / Transition Depth")
class RenderTool(object):
""" RenderTool is a facade to a renderer.
(This was introduced for the Browser / JS renderer which has now been removed.)
"""
def __init__(self, env, gl="PGL", jupyter=False,
agent_render_variant=AgentRenderVariant.ONE_STEP_BEHIND,
show_debug=False, clear_debug_text=True, screen_width=800, screen_height=600,
host="localhost", port=None):
def prettify2(self, width, height, cell_size):
plt.xlim([0, width * cell_size])
plt.ylim([-height * cell_size, 0])
self.env = env
self.frame_nr = 0
self.start_time = time.time()
self.times_list = deque()
gTicks = (np.arange(0, height) + 0.5) * cell_size
gLabels = np.arange(0, height)
plt.xticks(gTicks, gLabels)
self.agent_render_variant = agent_render_variant
gTicks = np.arange(-height * cell_size, 0) + cell_size / 2
gLabels = np.arange(height - 1, -1, -1)
plt.yticks(gTicks, gLabels)
if gl in ["PIL", "PILSVG", "PGL"]:
self.renderer = RenderLocal(env, gl, jupyter,
agent_render_variant,
show_debug, clear_debug_text, screen_width, screen_height)
self.gl = self.renderer.gl
else:
print("[", gl, "] not found, switch to PGL")
def render_env(self,
show=False, # whether to call matplotlib show() or equivalent after completion
show_agents=True, # whether to include agents
show_inactive_agents=False, # whether to show agents before they start
show_observations=True, # whether to include observations
show_predictions=False, # whether to include predictions
show_rowcols=False, # label the rows and columns
frames=False, # frame counter to show (intended since invocation)
episode=None, # int episode number to show
step=None, # int step number to show in image
selected_agent=None, # indicate which agent is "selected" in the editor):
return_image=False): # indicate if image is returned for use in monitor:
return self.renderer.render_env(show, show_agents, show_inactive_agents, show_observations,
show_predictions, show_rowcols, frames, episode, step, selected_agent, return_image)
def close_window(self):
self.renderer.close_window()
def reset(self):
self.renderer.reset()
def set_new_rail(self):
self.renderer.set_new_rail()
self.renderer.env = self.env # bit of a hack - copy our env to the delegate
def update_background(self):
self.renderer.update_background()
def get_endpoint_URL(self):
""" Returns a string URL for the root of the HTTP server
TODO: Need to update this work work on a remote server! May be tricky...
"""
#return "http://localhost:{}".format(self.renderer.get_port())
if hasattr(self.renderer, "get_endpoint_url"):
return self.renderer.get_endpoint_url()
else:
print("Attempt to get_endpoint_url from RenderTool - only supported with BROWSER")
return None
def get_image(self):
"""
"""
if hasattr(self.renderer, "gl"):
return self.renderer.gl.get_image()
else:
print("Attempt to retrieve image from RenderTool - not supported with BROWSER")
return None
plt.xlim([0, width * cell_size])
plt.ylim([-height * cell_size, 0])
def show(self, block=False):
plt.show(block=block)
class RenderBase(object):
def __init__(self, env):
pass
def render_env(self):
pass
def pause(self, seconds=0.00001):
plt.pause(seconds)
def close_window(self):
pass
def clf(self):
plt.clf()
plt.close()
def reset(self):
pass
def get_cmap(self, *args, **kwargs):
return plt.get_cmap(*args, **kwargs)
def set_new_rail(self):
""" Signal to the renderer that the env has changed and will need re-rendering.
"""
pass
def beginFrame(self):
self.img = None
plt.figure(figsize=(10, 10))
def update_background(self):
""" A lesser version of set_new_rail?
TODO: can update_background be pruned for simplicity?
"""
pass
def endFrame(self):
self.img = self.getImage(force=True)
plt.clf()
plt.close()
def getImage(self, force=False):
if self.img is None or force:
ax = plt.gca()
fig = ax.get_figure()
fig.tight_layout(pad=0)
fig.canvas.draw()
data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
self.img = data
return self.img
def adaptColor(self, color, lighten=False):
color = super(self.__class__, self).adaptColor(color, lighten)
# MPL has RGBA in [0,1]^4 not \mathbb{N} \cap [0,255]^4
color = tuple([iRGBA / 255 for iRGBA in color])
return color
class RenderTool(object):
Visit = recordtype("Visit", ["rc", "iDir", "iDepth", "prev"])
class RenderLocal(RenderBase):
""" Class to render the RailEnv and agents.
Uses two layers, layer 0 for rails (mostly static), layer 1 for agents etc (dynamic)
The lower / rail layer 0 is only redrawn after set_new_rail() has been called.
Created with a "GraphicsLayer" or gl - now either PIL or PILSVG
"""
visit = recordtype("visit", ["rc", "iDir", "iDepth", "prev"])
lColors = list("brgcmyk")
color_list = list("brgcmyk")
# \delta RC for NESW
gTransRC = np.array([[-1, 0], [0, 1], [1, 0], [0, -1]])
nPixCell = 1 # misnomer...
nPixHalf = nPixCell / 2
xyHalf = array([nPixHalf, -nPixHalf])
grc2xy = array([[0, -nPixCell], [nPixCell, 0]])
gGrid = array(np.meshgrid(np.arange(10), -np.arange(10))) * \
array([[[nPixCell]], [[nPixCell]]])
# xyPixHalf = xr.DataArray([nPixHalf, -nPixHalf],
# dims="xy",
# coords={"xy": ["x", "y"]})
# gCentres = xr.DataArray(gGrid,
# dims=["xy", "p1", "p2"],
# coords={"xy": ["x", "y"]}) + xyPixHalf
gTheta = np.linspace(0, np.pi / 2, 5)
gArc = array([np.cos(gTheta), np.sin(gTheta)]).T # from [1,0] to [0,1]
def __init__(self, env, gl="MPL"):
transitions_row_col = np.array([[-1, 0], [0, 1], [1, 0], [0, -1]])
pix_per_cell = 1 # misnomer...
half_pix_per_cell = pix_per_cell / 2
x_y_half = array([half_pix_per_cell, -half_pix_per_cell])
row_col_to_xy = array([[0, -pix_per_cell], [pix_per_cell, 0]])
grid = array(np.meshgrid(np.arange(10), -np.arange(10))) * array([[[pix_per_cell]], [[pix_per_cell]]])
theta = np.linspace(0, np.pi / 2, 5)
arc = array([np.cos(theta), np.sin(theta)]).T # from [1,0] to [0,1]
def __init__(self, env, gl="PILSVG", jupyter=False,
agent_render_variant=AgentRenderVariant.ONE_STEP_BEHIND,
show_debug=False, clear_debug_text=True, screen_width=800, screen_height=600):
self.env = env
self.iFrame = 0
self.time1 = time.time()
self.lTimes = deque()
# self.gl = MPLGL()
if gl == "MPL":
self.gl = MPLGL(env.width, env.height)
elif gl == "QT":
self.gl = QTGL(env.width, env.height)
elif gl == "PIL":
self.gl = PILGL(env.width, env.height)
def plotTreeOnRail(self, lVisits, color="r"):
self.frame_nr = 0
self.start_time = time.time()
self.times_list = deque()
self.agent_render_variant = agent_render_variant
self.gl_str = gl
if gl == "PIL":
self.gl = PILGL(env.width, env.height, jupyter, screen_width=screen_width, screen_height=screen_height)
elif gl == "PILSVG":
self.gl = PILSVG(env.width, env.height, jupyter, screen_width=screen_width, screen_height=screen_height)
else:
if gl != "PGL":
print("[", gl, "] not found, switch to PGL, PILSVG")
print("Using PGL")
self.gl = PGLGL(env.width, env.height, jupyter, screen_width=screen_width, screen_height=screen_height)
self.new_rail = True
self.show_debug = show_debug
self.clear_debug_text = clear_debug_text
self.update_background()
def reset(self):
"""
Derives and plots a tree of transitions starting at position rcPos
in direction iDir.
Returns a list of Visits which are the nodes / vertices in the tree.
Resets the environment
:return:
"""
rt = self.__class__
self.set_new_rail()
self.frame_nr = 0
self.start_time = time.time()
self.times_list = deque()
return
def update_background(self):
# create background map
targets = {}
for agent_idx, agent in enumerate(self.env.agents):
if agent is None:
continue
#print(f"updatebg: {agent_idx} {agent.target}")
targets[tuple(agent.target)] = agent_idx
self.gl.build_background_map(targets)
for visit in lVisits:
# transition for next cell
tbTrans = self.env.rail.get_transitions((*visit.rc, visit.iDir))
giTrans = np.where(tbTrans)[0] # RC list of transitions
gTransRCAg = rt.gTransRC[giTrans]
self.plotTrans(visit.rc, gTransRCAg, depth=str(visit.iDepth), color=color)
def resize(self):
self.gl.resize(self.env)
def plotAgents(self, targets=True, iSelectedAgent=None):
cmap = self.gl.get_cmap('hsv',
lut=max(len(self.env.agents), len(self.env.agents_static) + 1))
def set_new_rail(self):
""" Tell the renderer that the rail has changed.
eg when the rail has been regenerated, or updated in the editor.
"""
self.new_rail = True
def plot_agents(self, targets=True, selected_agent=None):
color_map = self.gl.get_cmap('hsv', lut=(len(self.env.agents) + 1))
for iAgent, agent in enumerate(self.env.agents_static):
for agent_idx, agent in enumerate(self.env.agents):
if agent is None:
continue
oColor = cmap(iAgent)
self.plotAgent(agent.position, agent.direction, oColor, target=agent.target if targets else None,
static=True, selected=iAgent == iSelectedAgent)
color = color_map(agent_idx)
self.plot_single_agent(agent.position, agent.direction, color, target=agent.target if targets else None,
static=True, selected=agent_idx == selected_agent)
for iAgent, agent in enumerate(self.env.agents):
for agent_idx, agent in enumerate(self.env.agents):
if agent is None:
continue
oColor = cmap(iAgent)
self.plotAgent(agent.position, agent.direction, oColor, target=agent.target if targets else None)
color = color_map(agent_idx)
self.plot_single_agent(agent.position, agent.direction, color, target=agent.target if targets else None)
def getTransRC(self, rcPos, iDir, bgiTrans=False):
def get_transition_row_col(self, row_col_pos, direction, bgiTrans=False):
"""
Get the available transitions for rcPos in direction iDir,
Get the available transitions for row_col_pos in direction direction,
as row & col deltas.
If bgiTrans is True, return a grid of indices of available transitions.
eg for a cell rcPos = (4,5), in direction iDir = 0 (N),
eg for a cell row_col_pos = (4,5), in direction direction = 0 (N),
where the available transitions are N and E, returns:
[[-1,0], [0,1]] ie N=up one row, and E=right one col.
and if bgiTrans is True, returns a tuple:
......@@ -178,225 +232,78 @@ class RenderTool(object):
)
"""
tbTrans = self.env.rail.get_transitions((*rcPos, iDir))
giTrans = np.where(tbTrans)[0] # RC list of transitions
transitions = self.env.rail.get_transitions(*row_col_pos, direction)
transition_list = np.where(transitions)[0] # RC list of transitions
# HACK: workaround dead-end transitions
if len(giTrans) == 0:
# print("Dead End", rcPos, iDir, tbTrans, giTrans)
iDirReverse = (iDir + 2) % 4
tbTrans = tuple(int(iDir2 == iDirReverse) for iDir2 in range(4))
giTrans = np.where(tbTrans)[0] # RC list of transitions
# print("Dead End2", rcPos, iDirReverse, tbTrans, giTrans)
if len(transition_list) == 0:
reverse_direciton = (direction + 2) % 4
transitions = tuple(int(tmp_dir == reverse_direciton) for tmp_dir in range(4))
transition_list = np.where(transitions)[0] # RC list of transitions
# print("agent", array(list("NESW"))[giTrans], self.gTransRC[giTrans])
gTransRCAg = self.__class__.gTransRC[giTrans]
transition_grid = self.__class__.transitions_row_col[transition_list]
if bgiTrans:
return gTransRCAg, giTrans
return transition_grid, transition_list
else:
return gTransRCAg
return transition_grid
def plotAgent(self, rcPos, iDir, color="r", target=None, static=False, selected=False):
def plot_single_agent(self, position_row_col, direction, color="r", target=None, static=False, selected=False):
"""
Plot a simple agent.
Assumes a working graphics layer context (cf a MPL figure).
"""
if position_row_col is None:
return
rt = self.__class__
rcDir = rt.gTransRC[iDir] # agent direction in RC
xyDir = np.matmul(rcDir, rt.grc2xy) # agent direction in xy
direction_row_col = rt.transitions_row_col[direction] # agent direction in RC
direction_xy = np.matmul(direction_row_col, rt.row_col_to_xy) # agent direction in xy
xyPos = np.matmul(rcPos - rcDir / 2, rt.grc2xy) + rt.xyHalf
xyPos = np.matmul(position_row_col - direction_row_col / 2, rt.row_col_to_xy) + rt.x_y_half
if static:
color = self.gl.adaptColor(color, lighten=True)
color = self.gl.adapt_color(color, lighten=True)
# print("Agent:", rcPos, iDir, rcDir, xyDir, xyPos)
self.gl.scatter(*xyPos, color=color, marker="o", s=100) # agent location
xyDirLine = array([xyPos, xyPos + xyDir / 2]).T # line for agent orient.
self.gl.plot(*xyDirLine, color=color, lw=5, ms=0, alpha=0.6)
color = color
self.gl.scatter(*xyPos, color=color, layer=1, marker="o", s=100) # agent location
xy_dir_line = array([xyPos, xyPos + direction_xy / 2]).T # line for agent orient.
self.gl.plot(*xy_dir_line, color=color, layer=1, lw=5, ms=0, alpha=0.6)
if selected:
self._draw_square(xyPos, 1, color)
if target is not None:
rcTarget = array(target)
xyTarget = np.matmul(rcTarget, rt.grc2xy) + rt.xyHalf
self._draw_square(xyTarget, 1 / 3, color)
target_row_col = array(target)
target_xy = np.matmul(target_row_col, rt.row_col_to_xy) + rt.x_y_half
self._draw_square(target_xy, 1 / 3, color, layer=1)
def plotTrans(self, rcPos, gTransRCAg, color="r", depth=None):
def plot_transition(self, position_row_col, transition_row_col, color="r", depth=None):
"""
plot the transitions in gTransRCAg at position rcPos.
gTransRCAg is a 2d numpy array containing a list of RC transitions,
plot the transitions in transition_row_col at position position_row_col.
transition_row_col is a 2d numpy array containing a list of RC transitions,
eg [[-1,0], [0,1]] means N, E.
"""
rt = self.__class__
xyPos = np.matmul(rcPos, rt.grc2xy) + rt.xyHalf
gxyTrans = xyPos + np.matmul(gTransRCAg, rt.grc2xy / 2.4)
self.gl.scatter(*gxyTrans.T, color=color, marker="o", s=50, alpha=0.2)
position_xy = np.matmul(position_row_col, rt.row_col_to_xy) + rt.x_y_half
transition_xy = position_xy + np.matmul(transition_row_col, rt.row_col_to_xy / 2.4)
self.gl.scatter(*transition_xy.T, color=color, marker="o", s=50, alpha=0.2)
if depth is not None:
for x, y in gxyTrans:
for x, y in transition_xy:
self.gl.text(x, y, depth)
def getTreeFromRail(self, rcPos, iDir, nDepth=10, bBFS=True, bPlot=False):
"""
Generate a tree from the env starting at rcPos, iDir.
"""
rt = self.__class__
print(rcPos, iDir)
iPos = 0 if bBFS else -1 # BF / DF Search
iDepth = 0
visited = set()
lVisits = []
# stack = [ (rcPos,iDir,nDepth) ]
stack = [rt.Visit(rcPos, iDir, iDepth, None)]
while stack:
visit = stack.pop(iPos)
rcd = (visit.rc, visit.iDir)
if visit.iDepth > nDepth:
continue
lVisits.append(visit)
if rcd not in visited:
visited.add(rcd)
# moves = self._get_valid_transitions( node[0], node[1] )
gTransRCAg, giTrans = self.getTransRC(visit.rc,
visit.iDir,
bgiTrans=True)
# nodePos = node[0]
# enqueue the next nodes (ie transitions from this node)
for gTransRC2, iTrans in zip(gTransRCAg, giTrans):
# print("Trans:", gTransRC2)
visitNext = rt.Visit(tuple(visit.rc + gTransRC2),
iTrans,
visit.iDepth + 1,
visit)
# print("node2: ", node2)
stack.append(visitNext)
# plot the available transitions from this node
if bPlot:
self.plotTrans(
visit.rc, gTransRCAg,
depth=str(visit.iDepth))
return lVisits
def plotTree(self, lVisits, xyTarg):
'''
Plot a vertical tree of transitions.
Returns the "visit" to the destination
(ie where euclidean distance is near zero) or None if absent.
'''
dPos = {}
iPos = 0
visitDest = None
for iVisit, visit in enumerate(lVisits):
if visit.rc in dPos:
xLoc = dPos[visit.rc]
else:
xLoc = dPos[visit.rc] = iPos
iPos += 1
rDist = np.linalg.norm(array(visit.rc) - array(xyTarg))
# sDist = "%.1f" % rDist
xLoc = rDist + visit.iDir / 4
# point labelled with distance
self.gl.scatter(xLoc, visit.iDepth, color="k", s=2)
# plt.text(xLoc, visit.iDepth, sDist, color="k", rotation=45)
self.gl.text(xLoc, visit.iDepth, visit.rc, color="k", rotation=45)
# if len(dPos)>1:
if visit.prev:
# print(dPos)
# print(tNodeDepth)
xLocPrev = dPos[visit.prev.rc]
rDistPrev = np.linalg.norm(array(visit.prev.rc) -
array(xyTarg))
# sDist = "%.1f" % rDistPrev
xLocPrev = rDistPrev + visit.prev.iDir / 4
# line from prev node
self.gl.plot([xLocPrev, xLoc],
[visit.iDepth - 1, visit.iDepth],
color="k", alpha=0.5, lw=1)
if rDist < 0.1:
visitDest = visit
# Walk backwards from destination to origin, plotting in red
if visitDest is not None:
visit = visitDest
xLocPrev = None
while visit is not None:
rDist = np.linalg.norm(array(visit.rc) - array(xyTarg))
xLoc = rDist + visit.iDir / 4
if xLocPrev is not None:
self.gl.plot([xLoc, xLocPrev], [visit.iDepth, visit.iDepth + 1],
color="r", alpha=0.5, lw=2)
xLocPrev = xLoc
visit = visit.prev
# prev = prev.prev
# self.gl.xticks(range(7)); self.gl.yticks(range(11))
self.gl.prettify()
return visitDest
def plotPath(self, visitDest):
"""
Given a "final" visit visitDest, plotPath recurses back through the path
using the visit.prev field (previous) to get back to the start of the path.
The path of transitions is plotted with arrows at 3/4 along the line.
The transition is plotted slightly to one side of the rail, so that
transitions in opposite directions are separate.
Currently, no attempt is made to make the transition arrows coincide
at corners, and they are straight only.
"""
rt = self.__class__
# Walk backwards from destination to origin
if visitDest is not None:
visit = visitDest
xyPrev = None
while visit is not None:
xy = np.matmul(visit.rc, rt.grc2xy) + rt.xyHalf
if xyPrev is not None:
dx, dy = (xyPrev - xy) / 20
xyLine = array([xy, xyPrev]) + array([dy, dx])
self.gl.plot(*xyLine.T, color="r", alpha=0.5, lw=1)
xyMid = np.sum(xyLine * [[1 / 4], [3 / 4]], axis=0)
xyArrow = array([
xyMid + [-dx - dy, +dx - dy],
xyMid,
xyMid + [-dx + dy, -dx - dy]])
self.gl.plot(*xyArrow.T, color="r")
visit = visit.prev
xyPrev = xy
def drawTrans2(
self,
xyLine, xyCentre,
rotation, bDeadEnd=False,
sColor="gray",
bArrow=True,
spacing=0.1):
def draw_transition(self,
line,
center,
rotation,
dead_end=False,
curves=False,
color="gray",
arrow=True,
spacing=0.1):
"""
gLine is a numpy 2d array of points,
in the plotting space / coords.
......@@ -405,138 +312,172 @@ class RenderTool(object):
from x=0, y=0.5
to x=1, y=0.2
"""
rt = self.__class__
bStraight = rotation in [0, 2]
dx, dy = np.squeeze(np.diff(xyLine, axis=0)) * spacing / 2
if bStraight:
if sColor == "auto":
if dx > 0 or dy > 0:
sColor = "C1" # N or E
if not curves and not dead_end:
# just a straigt line, no curve nor dead_end included in this basic rail element
self.gl.plot(
[line[0][0], line[1][0]], # x
[line[0][1], line[1][1]], # y
color=color
)
else:
# it was not a simple line to draw: the rail has a curve or dead_end included.
rt = self.__class__
straight = rotation in [0, 2]
dx, dy = np.squeeze(np.diff(line, axis=0)) * spacing / 2
if straight:
if color == "auto":
if dx > 0 or dy > 0:
color = "C1" # N or E
else:
color = "C2" # S or W
if dead_end:
line_xy = array([
line[1] + [dy, dx],
center,
line[1] - [dy, dx],
])
self.gl.plot(*line_xy.T, color=color)
else:
sColor = "C2" # S or W
if bDeadEnd:
xyLine2 = array([
xyLine[1] + [dy, dx],
xyCentre,
xyLine[1] - [dy, dx],
])
self.gl.plot(*xyLine2.T, color=sColor)
else:
xyLine2 = xyLine + [-dy, dx]
self.gl.plot(*xyLine2.T, color=sColor)
line_xy = line + [-dy, dx]
self.gl.plot(*line_xy.T, color=color)
if bArrow:
xyMid = np.sum(xyLine2 * [[1 / 4], [3 / 4]], axis=0)
if arrow:
middle_xy = np.sum(line_xy * [[1 / 4], [3 / 4]], axis=0)
xyArrow = array([
xyMid + [-dx - dy, +dx - dy],
xyMid,
xyMid + [-dx + dy, -dx - dy]])
self.gl.plot(*xyArrow.T, color=sColor)
arrow_xy = array([
middle_xy + [-dx - dy, +dx - dy],
middle_xy,
middle_xy + [-dx + dy, -dx - dy]])
self.gl.plot(*arrow_xy.T, color=color)
else:
xyMid = np.mean(xyLine, axis=0)
dxy = xyMid - xyCentre
xyCorner = xyMid + dxy
if rotation == 1:
rArcFactor = 1 - spacing
sColorAuto = "C1"
else:
rArcFactor = 1 + spacing
sColorAuto = "C2"
dxy2 = (xyCentre - xyCorner) * rArcFactor # for scaling the arc
if sColor == "auto":
sColor = sColorAuto
self.gl.plot(*(rt.gArc * dxy2 + xyCorner).T, color=sColor)
if bArrow:
dx, dy = np.squeeze(np.diff(xyLine, axis=0)) / 20
iArc = int(len(rt.gArc) / 2)
xyMid = xyCorner + rt.gArc[iArc] * dxy2
xyArrow = array([
xyMid + [-dx - dy, +dx - dy],
xyMid,
xyMid + [-dx + dy, -dx - dy]])
self.gl.plot(*xyArrow.T, color=sColor)
def renderEnv(
self, show=False, curves=True, spacing=False,
arrows=False, agents=True, sRailColor="gray",
frames=False, iEpisode=None, iStep=None,
iSelectedAgent=None):
"""
Draw the environment using matplotlib.
Draw into the figure if provided.
Call pyplot.show() if show==True.
(Use show=False from a Jupyter notebook with %matplotlib inline)
middle_xy = np.mean(line, axis=0)
dxy = middle_xy - center
corner = middle_xy + dxy
if rotation == 1:
arc_factor = 1 - spacing
color_auto = "C1"
else:
arc_factor = 1 + spacing
color_auto = "C2"
dxy2 = (center - corner) * arc_factor # for scaling the arc
if color == "auto":
color = color_auto
self.gl.plot(*(rt.arc * dxy2 + corner).T, color=color)
if arrow:
dx, dy = np.squeeze(np.diff(line, axis=0)) / 20
iArc = int(len(rt.arc) / 2)
middle_xy = corner + rt.arc[iArc] * dxy2
arrow_xy = array([
middle_xy + [-dx - dy, +dx - dy],
middle_xy,
middle_xy + [-dx + dy, -dx - dy]])
self.gl.plot(*arrow_xy.T, color=color)
def render_observation(self, agent_handles, observation_dict):
"""
Render the extent of the observation of each agent. All cells that appear in the agent
observation will be highlighted.
:param agent_handles: List of agent indices to adapt color and get correct observation
:param observation_dict: dictionary containing sets of cells of the agent observation
# cell_size is a bit pointless with matplotlib - it does not relate to pixels,
# so for now I've changed it to 1 (from 10)
cell_size = 1
self.gl.beginFrame()
# self.gl.clf()
# if oFigure is None:
# oFigure = self.gl.figure()
"""
rt = self.__class__
def drawTrans(oFrom, oTo, sColor="gray"):
self.gl.plot(
[oFrom[0], oTo[0]], # x
[oFrom[1], oTo[1]], # y
color=sColor
)
# Check if the observation builder provides an observation
if len(observation_dict) < 1:
warnings.warn(
"Predictor did not provide any predicted cells to render. \
Observation builder needs to populate: env.dev_obs_dict")
else:
for agent in agent_handles:
color = self.gl.get_agent_color(agent)
for visited_cell in observation_dict[agent]:
cell_coord = array(visited_cell[:2])
cell_coord_trans = np.matmul(cell_coord, rt.row_col_to_xy) + rt.x_y_half
self._draw_square(cell_coord_trans, 1 / (agent + 1.1), color, layer=1, opacity=100)
def render_prediction(self, agent_handles, prediction_dict):
"""
Render the extent of the observation of each agent. All cells that appear in the agent
observation will be highlighted.
:param agent_handles: List of agent indices to adapt color and get correct observation
:param observation_dict: dictionary containing sets of cells of the agent observation
"""
rt = self.__class__
if len(prediction_dict) < 1:
warnings.warn(
"Predictor did not provide any predicted cells to render. \
Predictors builder needs to populate: env.dev_pred_dict")
else:
for agent in agent_handles:
color = self.gl.get_agent_color(agent)
for visited_cell in prediction_dict[agent]:
cell_coord = array(visited_cell[:2])
if type(self.gl) is PILSVG:
# TODO : Track highlighting (Adrian)
r = cell_coord[0]
c = cell_coord[1]
transitions = self.env.rail.grid[r, c]
self.gl.set_predicion_path_at(r, c, transitions, agent_rail_color=color)
else:
cell_coord_trans = np.matmul(cell_coord, rt.row_col_to_xy) + rt.x_y_half
self._draw_square(cell_coord_trans, 1 / (agent + 1.1), color, layer=1, opacity=100)
def render_rail(self, spacing=False, rail_color="gray", curves=True, arrows=False):
cell_size = 1 # TODO: remove cell_size
env = self.env
# t1 = time.time()
# Draw cells grid
grid_color = [0.95, 0.95, 0.95]
for r in range(env.height + 1):
for row in range(env.height + 1):
self.gl.plot([0, (env.width + 1) * cell_size],
[-r * cell_size, -r * cell_size],
[-row * cell_size, -row * cell_size],
color=grid_color, linewidth=2)
for c in range(env.width + 1):
self.gl.plot([c * cell_size, c * cell_size],
for col in range(env.width + 1):
self.gl.plot([col * cell_size, col * cell_size],
[0, -(env.height + 1) * cell_size],
color=grid_color, linewidth=2)
# Draw each cell independently
for r in range(env.height):
for c in range(env.width):
for row in range(env.height):
for col in range(env.width):
# bounding box of the grid cell
x0 = cell_size * c # left
x1 = cell_size * (c + 1) # right
y0 = cell_size * -r # top
y1 = cell_size * -(r + 1) # bottom
x0 = cell_size * col # left
x1 = cell_size * (col + 1) # right
y0 = cell_size * -row # top
y1 = cell_size * -(row + 1) # bottom
# centres of cell edges
coords = [
((x0 + x1) / 2.0, y0), # N middle top
(x1, (y0 + y1) / 2.0), # E middle right
((x0 + x1) / 2.0, y1), # S middle bottom
(x0, (y0 + y1) / 2.0) # W middle left
(x0, (y0 + y1) / 2.0) # W middle left
]
# cell centre
xyCentre = array([x0, y1]) + cell_size / 2
center_xy = array([x0, y1]) + cell_size / 2
# cell transition values
oCell = env.rail.get_transitions((r, c))
cell = env.rail.get_full_transitions(row, col)
bCellValid = env.rail.cell_neighbours_valid((r, c), check_this_cell=True)
cell_valid = env.rail.cell_neighbours_valid((row, col), check_this_cell=True)
# Special Case 7, with a single bit; terminate at center
nbits = 0
tmp = oCell
tmp = cell
while tmp > 0:
nbits += (tmp & 1)
......@@ -544,132 +485,287 @@ class RenderTool(object):
# as above - move the from coord to the centre
# it's a dead env.
bDeadEnd = nbits == 1
is_dead_end = nbits == 1
if not bCellValid:
# print("invalid:", r, c)
self.gl.scatter(*xyCentre, color="r", s=30)
if not cell_valid:
self.gl.scatter(*center_xy, color="r", s=30)
for orientation in range(4): # ori is where we're heading
from_ori = (orientation + 2) % 4 # 0123=NESW -> 2301=SWNE
from_xy = coords[from_ori]
# renderer.push()
# renderer.translate(c * CELL_PIXELS, r * CELL_PIXELS)
tMoves = env.rail.get_transitions((r, c, orientation))
moves = env.rail.get_transitions(row, col, orientation)
# to_ori = (orientation + 2) % 4
for to_ori in range(4):
to_xy = coords[to_ori]
rotation = (to_ori - from_ori) % 4
if (moves[to_ori]): # if we have this transition
self.draw_transition(
array([from_xy, to_xy]), center_xy,
rotation, dead_end=is_dead_end, curves=curves and not is_dead_end, spacing=spacing,
color=rail_color)
def render_env(self,
show=False, # whether to call matplotlib show() or equivalent after completion
show_agents=True, # whether to include agents
show_inactive_agents=False,
show_observations=True, # whether to include observations
show_predictions=False, # whether to include predictions
show_rowcols=False, # label the rows and columns
frames=False, # frame counter to show (intended since invocation)
episode=None, # int episode number to show
step=None, # int step number to show in image
selected_agent=None, # indicate which agent is "selected" in the editor
return_image=False): # indicate if image is returned for use in monitor:
""" Draw the environment using the GraphicsLayer this RenderTool was created with.
(Use show=False from a Jupyter notebook with %matplotlib inline)
"""
if (tMoves[to_ori]): # if we have this transition
if bDeadEnd:
self.drawTrans2(
array([from_xy, to_xy]), xyCentre,
rotation, bDeadEnd=True, spacing=spacing,
sColor=sRailColor)
else:
if curves:
self.drawTrans2(
array([from_xy, to_xy]), xyCentre,
rotation, spacing=spacing, bArrow=arrows,
sColor=sRailColor)
else:
drawTrans(from_xy, to_xy, sRailColor)
if False:
print(
"r,c,ori: ", r, c, orientation,
"cell:", "{0:b}".format(oCell),
"moves:", tMoves,
"from:", from_ori, from_xy,
"to: ", to_ori, to_xy,
"cen:", *xyCentre,
"rot:", rotation,
# if type(self.gl) is PILSVG:
if self.gl_str in ["PILSVG", "PGL"]:
return self.render_env_svg(show=show,
show_observations=show_observations,
show_predictions=show_predictions,
selected_agent=selected_agent,
show_agents=show_agents,
show_inactive_agents=show_inactive_agents,
show_rowcols=show_rowcols,
return_image=return_image
)
else:
return self.render_env_pil(show=show,
show_agents=show_agents,
show_inactive_agents=show_inactive_agents,
show_observations=show_observations,
show_predictions=show_predictions,
show_rowcols=show_rowcols,
frames=frames,
episode=episode,
step=step,
selected_agent=selected_agent,
return_image=return_image
)
# Draw each agent + its orientation + its target
if agents:
self.plotAgents(targets=True, iSelectedAgent=iSelectedAgent)
def _draw_square(self, center, size, color, opacity=255, layer=0):
x0 = center[0] - size / 2
x1 = center[0] + size / 2
y0 = center[1] - size / 2
y1 = center[1] + size / 2
self.gl.plot([x0, x1, x1, x0, x0], [y0, y0, y1, y1, y0], color=color, layer=layer, opacity=opacity)
def get_image(self):
return self.gl.get_image()
def render_env_pil(self,
show=False, # whether to call matplotlib show() or equivalent after completion
# use false when calling from Jupyter. (and matplotlib no longer supported!)
show_agents=True, # whether to include agents
show_inactive_agents=False,
show_observations=True, # whether to include observations
show_predictions=False, # whether to include predictions
show_rowcols=False, # label the rows and columns
frames=False, # frame counter to show (intended since invocation)
episode=None, # int episode number to show
step=None, # int step number to show in image
selected_agent=None, # indicate which agent is "selected" in the editor
return_image=False # indicate if image is returned for use in monitor:
):
if type(self.gl) is PILGL:
self.gl.begin_frame()
env = self.env
self.render_rail()
# Draw each agent + its orientation + its target
if show_agents:
self.plot_agents(targets=True, selected_agent=selected_agent)
if show_observations:
self.render_observation(range(env.get_num_agents()), env.dev_obs_dict)
if show_predictions and len(env.dev_pred_dict) > 0:
self.render_prediction(range(env.get_num_agents()), env.dev_pred_dict)
# Draw some textual information like fps
yText = [-0.3, -0.6, -0.9]
text_y = [-0.3, -0.6, -0.9]
if frames:
self.gl.text(0.1, yText[2], "Frame:{:}".format(self.iFrame))
self.iFrame += 1
self.gl.text(0.1, text_y[2], "Frame:{:}".format(self.frame_nr))
self.frame_nr += 1
if iEpisode is not None:
self.gl.text(0.1, yText[1], "Ep:{}".format(iEpisode))
if episode is not None:
self.gl.text(0.1, text_y[1], "Ep:{}".format(episode))
if iStep is not None:
self.gl.text(0.1, yText[0], "Step:{}".format(iStep))
if step is not None:
self.gl.text(0.1, text_y[0], "Step:{}".format(step))
tNow = time.time()
self.gl.text(2, yText[2], "elapsed:{:.2f}s".format(tNow - self.time1))
self.lTimes.append(tNow)
if len(self.lTimes) > 20:
self.lTimes.popleft()
if len(self.lTimes) > 1:
rFps = (len(self.lTimes) - 1) / (self.lTimes[-1] - self.lTimes[0])
self.gl.text(2, yText[1], "fps:{:.2f}".format(rFps))
time_now = time.time()
self.gl.text(2, text_y[2], "elapsed:{:.2f}s".format(time_now - self.start_time))
self.times_list.append(time_now)
if len(self.times_list) > 20:
self.times_list.popleft()
if len(self.times_list) > 1:
rFps = (len(self.times_list) - 1) / (self.times_list[-1] - self.times_list[0])
self.gl.text(2, text_y[1], "fps:{:.2f}".format(rFps))
self.gl.prettify2(env.width, env.height, self.nPixCell)
self.gl.prettify2(env.width, env.height, self.pix_per_cell)
# TODO: for MPL, we don't want to call clf (called by endframe)
# for QT, we need to call endFrame()
# if not show:
self.gl.endFrame()
# t2 = time.time()
# print(t2 - t1, "seconds")
if show and type(self.gl) is PILGL:
self.gl.show()
self.gl.pause(0.00001)
if return_image:
return self.get_image()
return
def render_env_svg(
self, show=False, show_observations=True, show_predictions=False, selected_agent=None,
show_agents=True, show_inactive_agents=False, show_rowcols=False, return_image=False
):
"""
Renders the environment with SVG support (nice image)
"""
env = self.env
self.gl.begin_frame()
if self.new_rail:
self.new_rail = False
self.gl.clear_rails()
# store the targets
targets = {}
selected = {}
for agent_idx, agent in enumerate(self.env.agents):
if agent is None:
continue
targets[tuple(agent.target)] = agent_idx
selected[tuple(agent.target)] = (agent_idx == selected_agent)
# Draw each cell independently
for r in range(env.height):
for c in range(env.width):
transitions = env.rail.grid[r, c]
if (r, c) in targets:
target = targets[(r, c)]
is_selected = selected[(r, c)]
else:
target = None
is_selected = False
self.gl.set_rail_at(r, c, transitions, target=target, is_selected=is_selected,
rail_grid=env.rail.grid, num_agents=env.get_num_agents(),
show_debug=self.show_debug)
self.gl.build_background_map(targets)
if show_rowcols:
# label rows, cols
for iRow in range(env.height):
self.gl.text_rowcol((iRow, 0), str(iRow), layer=self.gl.RAIL_LAYER)
for iCol in range(env.width):
self.gl.text_rowcol((0, iCol), str(iCol), layer=self.gl.RAIL_LAYER)
if show_agents:
for agent_idx, agent in enumerate(self.env.agents):
if agent is None:
continue
# Show an agent even if it hasn't already started
if agent.position is None:
if show_inactive_agents:
# print("agent ", agent_idx, agent.position, agent.old_position, agent.initial_position)
self.gl.set_agent_at(agent_idx, *(agent.initial_position),
agent.initial_direction, agent.initial_direction,
is_selected=(selected_agent == agent_idx),
rail_grid=env.rail.grid,
show_debug=self.show_debug, clear_debug_text=self.clear_debug_text,
malfunction=False)
continue
is_malfunction = agent.malfunction_handler.malfunction_down_counter > 0
if self.agent_render_variant == AgentRenderVariant.BOX_ONLY:
self.gl.set_cell_occupied(agent_idx, *(agent.position))
elif self.agent_render_variant == AgentRenderVariant.ONE_STEP_BEHIND or \
self.agent_render_variant == AgentRenderVariant.ONE_STEP_BEHIND_AND_BOX: # noqa: E125
# Most common case - the agent has been running for >1 steps
if agent.old_position is not None:
position = agent.old_position
direction = agent.direction
old_direction = agent.old_direction
# the agent's first step - it doesn't have an old position yet
elif agent.position is not None:
position = agent.position
direction = agent.direction
old_direction = agent.direction
# When the editor has just added an agent
elif agent.initial_position is not None:
position = agent.initial_position
direction = agent.initial_direction
old_direction = agent.initial_direction
# set_agent_at uses the agent index for the color
if self.agent_render_variant == AgentRenderVariant.ONE_STEP_BEHIND_AND_BOX:
self.gl.set_cell_occupied(agent_idx, *(agent.position))
self.gl.set_agent_at(agent_idx, *position, old_direction, direction,
selected_agent == agent_idx, rail_grid=env.rail.grid,
show_debug=self.show_debug, clear_debug_text=self.clear_debug_text,
malfunction=is_malfunction)
else:
position = agent.position
direction = agent.direction
for possible_direction in range(4):
# Is a transition along movement `desired_movement_from_new_cell` to the current cell possible?
isValid = env.rail.get_transition((*agent.position, agent.direction), possible_direction)
if isValid:
direction = possible_direction
# set_agent_at uses the agent index for the color
self.gl.set_agent_at(agent_idx, *position, agent.direction, direction,
selected_agent == agent_idx, rail_grid=env.rail.grid,
show_debug=self.show_debug, clear_debug_text=self.clear_debug_text,
malfunction=is_malfunction)
# set_agent_at uses the agent index for the color
if self.agent_render_variant == AgentRenderVariant.AGENT_SHOWS_OPTIONS_AND_BOX:
self.gl.set_cell_occupied(agent_idx, *(agent.position))
if show_inactive_agents:
show_this_agent = True
else:
show_this_agent = agent.state.is_on_map_state()
if show_this_agent:
self.gl.set_agent_at(agent_idx, *position, agent.direction, direction,
selected_agent == agent_idx,
rail_grid=env.rail.grid, malfunction=is_malfunction)
if show_observations:
self.render_observation(range(env.get_num_agents()), env.dev_obs_dict)
if show_predictions:
self.render_prediction(range(env.get_num_agents()), env.dev_pred_dict)
if show:
self.gl.show(block=False)
self.gl.pause(0.00001)
self.gl.show()
for i in range(3):
self.gl.process_events()
self.frame_nr += 1
if return_image:
return self.get_image()
return
def _draw_square(self, center, size, color):
x0 = center[0] - size / 2
x1 = center[0] + size / 2
y0 = center[1] - size / 2
y1 = center[1] + size / 2
self.gl.plot([x0, x1, x1, x0, x0], [y0, y0, y1, y1, y0], color=color)
def getImage(self):
return self.gl.getImage()
def plotTreeObs(self, gObs):
nBranchFactor = 4
gP0 = array([[0, 0, 0]]).T
nDepth = 2
for i in range(nDepth):
nDepthNodes = nBranchFactor**i
# rScale = nBranchFactor ** (nDepth - i)
rShrinkDepth = 1/(i+1)
# gX1 = np.linspace(-nDepthNodes / 2, nDepthNodes / 2, nDepthNodes) * rShrinkDepth
gX1 = np.linspace(-(nDepthNodes-1), (nDepthNodes-1), nDepthNodes) * rShrinkDepth
gY1 = np.ones((nDepthNodes)) * i
gZ1 = np.zeros((nDepthNodes))
gP1 = array([gX1, gY1, gZ1])
gP01 = np.append(gP0, gP1, axis=1)
if nDepthNodes > 1:
nDepthNodesPrev = nDepthNodes / nBranchFactor
giP0 = np.repeat(np.arange(nDepthNodesPrev), nBranchFactor)
giP1 = np.arange(0, nDepthNodes) + nDepthNodesPrev
giLinePoints = np.stack([giP0, giP1]).ravel("F")
# print(gP01[:,:10])
print(giLinePoints)
self.gl.plot(gP01[0], -gP01[1], lines=giLinePoints, color="gray")
gP0 = array([gX1, gY1, gZ1])
\ No newline at end of file
def close_window(self):
self.gl.close_window()
from typing import Tuple, Dict
import numpy as np
from flatland.core.grid.rail_env_grid import RailEnvTransitions
from flatland.core.transition_map import GridTransitionMap
def make_simple_rail() -> Tuple[GridTransitionMap, np.array]:
# We instantiate a very simple rail network on a 7x10 grid:
# Note that that cells have invalid RailEnvTransitions!
# |
# |
# |
# _ _ _ _\ _ _ _ _ _ _
# /
# |
# |
# |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
simple_switch_north_left = cells[2]
simple_switch_north_right = cells[10]
simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270)
simple_switch_east_west_south = transitions.rotate_transition(simple_switch_north_left, 270)
rail_map = np.array(
[[empty] * 3 + [dead_end_from_south] + [empty] * 6] +
[[empty] * 3 + [vertical_straight] + [empty] * 6] * 2 +
[[dead_end_from_east] + [horizontal_straight] * 2 +
[simple_switch_east_west_north] +
[horizontal_straight] * 2 + [simple_switch_east_west_south] +
[horizontal_straight] * 2 + [dead_end_from_west]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_disconnected_simple_rail() -> Tuple[GridTransitionMap, np.array]:
# We instantiate a very simple rail network on a 7x10 grid:
# Note that that cells have invalid RailEnvTransitions!
# |
# |
# |
# _ _ _ _\ _ _ _ _ _
# /
# |
# |
# |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
simple_switch_north_left = cells[2]
simple_switch_north_right = cells[10]
simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270)
simple_switch_east_west_south = transitions.rotate_transition(simple_switch_north_left, 270)
rail_map = np.array(
[[empty] * 3 + [dead_end_from_south] + [empty] * 6] +
[[empty] * 3 + [vertical_straight] + [empty] * 6] * 2 +
[[dead_end_from_east] + [horizontal_straight] * 2 +
[simple_switch_east_west_north] +
[dead_end_from_west] + [dead_end_from_east] + [simple_switch_east_west_south] +
[horizontal_straight] * 2 + [dead_end_from_west]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_simple_rail2() -> Tuple[GridTransitionMap, np.array]:
# We instantiate a very simple rail network on a 7x10 grid:
# |
# |
# |
# _ _ _ _\ _ _ _ _ _ _
# \
# |
# |
# |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
simple_switch_north_right = cells[10]
simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270)
simple_switch_west_east_south = transitions.rotate_transition(simple_switch_north_right, 90)
rail_map = np.array(
[[empty] * 3 + [dead_end_from_south] + [empty] * 6] +
[[empty] * 3 + [vertical_straight] + [empty] * 6] * 2 +
[[dead_end_from_east] + [horizontal_straight] * 2 +
[simple_switch_east_west_north] +
[horizontal_straight] * 2 + [simple_switch_west_east_south] +
[horizontal_straight] * 2 + [dead_end_from_west]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_simple_rail_unconnected() -> Tuple[GridTransitionMap, np.array]:
# We instantiate a very simple rail network on a 7x10 grid:
# Note that that cells have invalid RailEnvTransitions!
# |
# |
# |
# _ _ _ _ _ _ _ _ _ _
# /
# |
# |
# |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
simple_switch_north_left = cells[2]
# simple_switch_north_right = cells[10]
# simple_switch_east_west_north = transitions.rotate_transition(simple_switch_north_right, 270)
simple_switch_east_west_south = transitions.rotate_transition(simple_switch_north_left, 270)
rail_map = np.array(
[[empty] * 3 + [dead_end_from_south] + [empty] * 6] +
[[empty] * 3 + [vertical_straight] + [empty] * 6] +
[[empty] * 3 + [dead_end_from_north] + [empty] * 6] +
[[dead_end_from_east] + [horizontal_straight] * 5 + [simple_switch_east_west_south] +
[horizontal_straight] * 2 + [dead_end_from_west]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_simple_rail_with_alternatives() -> Tuple[GridTransitionMap, np.array]:
# We instantiate a very simple rail network on a 7x10 grid:
# 0 1 2 3 4 5 6 7 8 9 10
# 0 /-------------\
# 1 | |
# 2 | |
# 3 _ _ _ /_ _ _ |
# 4 \ ___ /
# 5 |/
# 6 |
# 7 |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
right_turn_from_south = cells[8]
right_turn_from_west = transitions.rotate_transition(right_turn_from_south, 90)
right_turn_from_north = transitions.rotate_transition(right_turn_from_south, 180)
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
simple_switch_north_left = cells[2]
simple_switch_north_right = cells[10]
simple_switch_left_east = transitions.rotate_transition(simple_switch_north_left, 90)
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
double_switch_south_horizontal_straight = horizontal_straight + cells[6]
double_switch_north_horizontal_straight = transitions.rotate_transition(
double_switch_south_horizontal_straight, 180)
rail_map = np.array(
[[empty] * 3 + [right_turn_from_south] + [horizontal_straight] * 5 + [right_turn_from_west]] +
[[empty] * 3 + [vertical_straight] + [empty] * 5 + [vertical_straight]] * 2 +
[[dead_end_from_east] + [horizontal_straight] * 2 + [simple_switch_left_east] + [horizontal_straight] * 2 + [
right_turn_from_west] + [empty] * 2 + [vertical_straight]] +
[[empty] * 6 + [simple_switch_north_right] + [horizontal_straight] * 2 + [right_turn_from_north]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_invalid_simple_rail() -> Tuple[GridTransitionMap, np.array, Dict[str, str]]:
# We instantiate a very simple rail network on a 7x10 grid:
# |
# |
# |
# _ _ _ /_\ _ _ _ _ _ _
# \ /
# |
# |
# |
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
dead_end_from_south = cells[7]
dead_end_from_west = transitions.rotate_transition(dead_end_from_south, 90)
dead_end_from_north = transitions.rotate_transition(dead_end_from_south, 180)
dead_end_from_east = transitions.rotate_transition(dead_end_from_south, 270)
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
double_switch_south_horizontal_straight = horizontal_straight + cells[6]
double_switch_north_horizontal_straight = transitions.rotate_transition(
double_switch_south_horizontal_straight, 180)
rail_map = np.array(
[[empty] * 3 + [dead_end_from_south] + [empty] * 6] +
[[empty] * 3 + [vertical_straight] + [empty] * 6] * 2 +
[[dead_end_from_east] + [horizontal_straight] * 2 +
[double_switch_north_horizontal_straight] +
[horizontal_straight] * 2 + [double_switch_south_horizontal_straight] +
[horizontal_straight] * 2 + [dead_end_from_west]] +
[[empty] * 6 + [vertical_straight] + [empty] * 3] * 2 +
[[empty] * 6 + [dead_end_from_north] + [empty] * 3], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(0,3), (6, 6)]
train_stations = [
[( (0, 3), 0 ) ],
[( (6, 6), 0 ) ],
]
city_orientations = [0, 2]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
def make_oval_rail() -> Tuple[GridTransitionMap, np.array]:
transitions = RailEnvTransitions()
cells = transitions.transition_list
empty = cells[0]
vertical_straight = cells[1]
horizontal_straight = transitions.rotate_transition(vertical_straight, 90)
right_turn_from_south = cells[8]
right_turn_from_west = transitions.rotate_transition(right_turn_from_south, 90)
right_turn_from_north = transitions.rotate_transition(right_turn_from_south, 180)
right_turn_from_east = transitions.rotate_transition(right_turn_from_south, 270)
rail_map = np.array(
[[empty] * 9] +
[[empty] + [right_turn_from_south] + [horizontal_straight] * 5 + [right_turn_from_west] + [empty]] +
[[empty] + [vertical_straight] + [empty] * 5 + [vertical_straight] + [empty]]+
[[empty] + [vertical_straight] + [empty] * 5 + [vertical_straight] + [empty]] +
[[empty] + [right_turn_from_east] + [horizontal_straight] * 5 + [right_turn_from_north] + [empty]] +
[[empty] * 9], dtype=np.uint16)
rail = GridTransitionMap(width=rail_map.shape[1],
height=rail_map.shape[0], transitions=transitions)
rail.grid = rail_map
city_positions = [(1, 4), (4, 4)]
train_stations = [
[((1, 4), 0)],
[((4, 4), 0)],
]
city_orientations = [1, 3]
agents_hints = {'city_positions': city_positions,
'train_stations': train_stations,
'city_orientations': city_orientations
}
optionals = {'agents_hints': agents_hints}
return rail, rail_map, optionals
import copy
import re
import svgutils
from flatland.core.grid.rail_env_grid import RailEnvTransitions
class SVG(object):
def __init__(self, sfName=None, svgETree=None):
if sfName is not None:
self.svg = svgutils.transform.fromfile(sfName)
elif svgETree is not None:
self.svg = svgETree
expr = "//*[local-name() = $name]"
self.eStyle = self.svg.root.xpath(expr, name="style")[0]
ltMatch = re.findall(r".st([a-zA-Z0-9]+)[{]([^}]*)}", self.eStyle.text)
self.dStyles = dict(ltMatch)
def copy(self):
new_svg = copy.deepcopy(self.svg)
return SVG(svgETree=new_svg)
def merge(self, svg2):
svg3 = svg2.copy()
svg3.renumber_styles(offset=10)
svg3.eStyle.text = self.eStyle.text + "\n" + svg3.eStyle.text
for child in self.svg.root:
if not child.tag.endswith("style"):
svg3.svg.root.append(child)
return svg3
def renumber_styles(self, offset=10):
sNewStyles = "\n"
for sStyle in self.dStyles.keys():
iStyle = int(sStyle)
sClass = "st{:}".format(iStyle)
lEl = self.svg.root.xpath("//*[@class='{}']".format(sClass))
for el in lEl:
el.attrib["class"] = "st{}".format(iStyle + offset)
sStyle2 = str(iStyle + offset)
sNewStyle = "\t.st" + sStyle2 + "{" + self.dStyles[sStyle] + "}\n"
sNewStyles += sNewStyle
self.eStyle.text = sNewStyles
def set_style_color(self, style_name, color):
sNewStyles = "\n"
for sKey, sValue in self.dStyles.items():
if sKey == style_name:
sValue = "fill:#" + "".join([('{:#04x}'.format(int(255.0 * col))[2:4]) for col in color[0:3]]) + ";"
sNewStyle = "\t.st" + sKey + "{" + sValue + "}\n"
sNewStyles += sNewStyle
self.eStyle.text = sNewStyles
def set_rotate(self, angle):
self.svg.root.attrib["transform"] = "rotate({}, 120, 120)".format(angle)
def to_string(self):
return self.svg.to_str().decode("utf-8")
class Zug(object):
def __init__(self, iDir=0):
self.svg_straight = SVG("svg/Zug_Gleis_#0091ea.svg")
self.svg_curve1 = SVG("svg/Zug_1_Weiche_#0091ea.svg")
self.svg_curve2 = SVG("svg/Zug_2_Weiche_#0091ea.svg")
def getSvg(self, iAgent, iDirIn, iDirOut, color=None):
delta_dir = (iDirOut - iDirIn) % 4
if delta_dir in (0, 2):
svg = self.svg_straight.copy()
svg.set_rotate(iDirIn * 90)
if delta_dir == 1: # bend to right, eg N->E, E->S
svg = self.svg_curve1.copy()
svg.set_rotate((iDirIn - 1) * 90)
elif delta_dir == 3: # bend to left, eg N->W
svg = self.svg_curve2.copy()
svg.set_rotate(iDirIn * 90)
if color is not None:
svg.set_style_color("2", color)
return svg
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",
"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.dSvg = {}
transitions = RailEnvTransitions()
lDirs = list("NESW")
svgBG = SVG("./svg/Background_#9CCB89.svg")
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:
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)
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()
svg2.set_rotate(nRot)
self.dSvg[binTrans2] = svg2
def main():
zug = Zug()
svg = zug.getSvg(0, 0, 0, color=(255, 0, 0))
print(svg.to_string()[:800])
if __name__ == "__main__":
main()
Flatland 2.0 Introduction
=========================
## What's new?
In this version of **Flat**land, we are moving closer to realistic and more complex railway problems.
Earlier versions of **Flat**land introduced you to the concept of restricted transitions, but they were still too simplistic to give us feasible solutions for daily operations.
Thus the following changes are coming in the next version to be closer to real railway network challenges:
- **New Level Generator** provide less connections between different nodes in the network and thus agent densities on rails are much higher.
- **Stochastic Events** cause agents to stop and get stuck for different numbers of time steps.
- **Different Speed Classes** allow agents to move at different speeds and thus enhance complexity in the search for optimal solutions.
We explain these changes in more detail and how you can play with their parametrization in Tutorials 3--5:
* [Tutorials](https://gitlab.aicrowd.com/flatland/flatland/tree/master/docs/tutorials)
We appreciate *your feedback* on the performance and the difficulty on these levels to help us shape the best possible **Flat**land 2.0 environment.
## Example code
To see all the changes in action you can just run the
* [examples/flatland_example_2_0.py](https://gitlab.aicrowd.com/flatland/flatland/blob/master/examples/flatland_2_0_example.py)
example.
@echo on
set FLATLAND_BASEDIR=%~dp0\..
cd %FLATLAND_BASEDIR%
call conda install -y -c conda-forge tox-conda || goto :error
call conda install -y tox || goto :error
call tox -v --recreate || goto :error
call tox -v -e start_jupyter --recreate || goto :error
goto :EOF
:error
echo Failed with error #%errorlevel%.
pause
#!/bin/bash
set -e # stop on error
set -x # echo commands
FLATLAND_BASEDIR=$(dirname "$BASH_SOURCE")/..
cd ${FLATLAND_BASEDIR}
conda install -y -c conda-forge tox-conda
conda install -y tox
tox -v
tox -v -e start_jupyter &