Source code for flatland.utils.graphics_pil

import io
import os
import time
#import tkinter as tk

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


[docs] class PILGL(GraphicsLayer): # 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.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.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() self.font = ImageFont.load_default()
[docs] 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)
[docs] 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])
[docs] def get_agent_color(self, iAgent): return self.agent_colors[iAgent % self.n_agent_colors]
[docs] 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()) # the width here was self.linewidth - not really sure of the implications self.draws[layer].line(gPoints, fill=color, width=linewidth)
[docs] 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.draws[layer].rectangle([(x - r, y - r), (x + r, y + r)], fill=color, outline=color)
[docs] 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)
[docs] 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)
[docs] def open_window(self): pass
[docs] def close_window(self): pass
[docs] 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))
[docs] def text_rowcol(self, rcTopLeft, strText, layer=AGENT_LAYER): xyPixLeftTop = tuple((array(rcTopLeft) * self.nPixCell)[[1, 0]]) self.text(*xyPixLeftTop, strText, layer)
[docs] def prettify(self, *args, **kwargs): pass
[docs] def prettify2(self, width, height, cell_size): pass
[docs] 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)
[docs] def show(self, block=False): #print("show() - ", self.__class__) pass
[docs] def pause(self, seconds=0.00001): pass
[docs] def idle(self, seconds=0.00001): pass
[docs] def alpha_composite_layers(self): img = self.layers[0] for img2 in self.layers[1:]: img = Image.alpha_composite(img, img2) return img
[docs] 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)
[docs] 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)
[docs] def create_image(self, opacity=255): img = Image.new("RGBA", (self.widthPx, self.heightPx), (255, 255, 255, opacity)) return img
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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()
[docs] def process_events(self): time.sleep(0.001)
[docs] def clear_rails(self): self.create_layers() self.clear_agents()
[docs] def clear_agents(self): for wAgent in self.lwAgents: self.layout.removeWidget(wAgent) self.lwAgents = [] self.agents_prev = []
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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}
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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]
[docs] 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))
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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()