cromosim.domain

Source code for cromosim.domain

# Authors:
#     Sylvain Faure <sylvain.faure@math.u-psud.fr>
#     Bertrand Maury <bertrand.maury@math.u-psud.fr>
# License: GPL

import scipy as sp
from scipy.misc import imread
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, Circle, Rectangle, Polygon
from matplotlib.lines import Line2D
import PIL
from PIL import Image
from PIL import ImageDraw
import skfmm

[docs]class Domain(): """ To define the computational domain : - a background : empty (white) or a PNG image which only \ contains the colors white, red (for the doors) and black \ (for the walls) - supplementary doors represented by matplotlib shapes : \ line2D - supplementary walls represented by matplotlib shapes : \ line2D, circle, ellipse, rectangle or polygon To compute the obstacle distances and the desired velocities Attributes ---------- pixel_size : float size of a pixel in meters width : int width of the background image (number of pixels) height : int height of the background image (number of pixels) xmin : float x coordinate of the origin (bottom left corner) xmax : float xmax = xmin + width*pixel_size ymin : float y coordinate of the origin (bottom left corner) ymax : float ymax = ymin + height*pixel_size X : numpy array x coordinates (meshgrid) Y : numpy array y coordinates (meshgrid) image : numpy array pixel array (r,g,b,a) The Pillow image is converted to a numpy arrays, then \ using flipud the origin of the image is put it down left instead the \ top left image_red : numpy array red values of the image (r,g,b,a) image_green : numpy array green values of the image (r,g,b,a) image_blue : numpy array blue values of the image (r,g,b,a) mask : numpy array boolean array : true for black pixels mask_id : numpy array black pixel indices wall_distance : numpy array distance (m) to the wall wall_grad_X : numpy array gradient of the distance to the wall (first component) wall_grad_Y : numpy array gradient of the distance to the wall (second component) door_distance : numpy array distance (m) to the door desired_velocity_X : numpy array opposite of the gradient of the distance to the door : desired velocity \ (first component) desired_velocity_Y : numpy array opposite of the gradient of the distance to the door : desired velocity \ (second component) Examples -------- - An example with a domain built using only shape elements : cromosim/examples/domain_manually_computed.py - An example with a domain built using an image and shape elements : cromosim/examples/domain_auto_computed.py """ def __init__(self, name = 'Domain', background = 'White', pixel_size = 1.0, xmin = 0.0, width = 100, ymin = 0.0, height = 100): """ Constructor of a Domain object Parameters ---------- name : string domain name (default : 'Domain') background : string name of the background image (default : 'White', no image) pixel_size : float size of a pixel in meters (default : 1.0) xmin : float x coordinate of the origin, bottom left corner (default : 0.0) ymin : float y coordinate of the origin, bottom left corner (default : 0.0) width : int width of the background image (default : 100 pixels) height : int height of the background image (default : 100 pixels) """ self.__walls = [] self.__doors = [] self.__image_filename = '' self.__name = name self.__background = background self.pixel_size = pixel_size self.xmin, self.ymin = [xmin, ymin] if (self.__background != 'White'): image = imread(self.__background) self.width = image.shape[1] ## width = number of columns self.height = image.shape[0] ## height = number of rows else: self.width, self.height = [width, height] self.xmax = self.xmin + self.width*pixel_size self.ymax = self.ymin + self.height*pixel_size self.X, self.Y = sp.meshgrid(sp.arange(self.width), sp.arange(self.height)) self.X = 0.5*self.pixel_size + self.xmin + self.X*self.pixel_size self.Y = 0.5*self.pixel_size + self.ymin + self.Y*self.pixel_size self.wall_distance = None self.wall_grad_X = None self.wall_grad_Y = None self.desired_velocity_X = None self.desired_velocity_Y = None self.door_distance = None
[docs] def add_wall(self, shape): """ To add a wall represented by matplotlib shapes : \ line2D, circle, ellipse, rectangle or polygon Parameters ---------- shape : matplotlib shape line2D, circle, ellipse, rectangle or polygon """ self.__walls.append(shape)
[docs] def add_door(self, shape): """ To add a door represented by matplotlib shapes : \ line2D (only) Parameters ---------- shape : matplotlib shape line2D """ self.__doors.append(shape)
[docs] def build_domain(self): """ To build the domain : reads the background image (if supplied) \ and initializes all the color arrrays """ if (self.__background != 'White'): image = Image.open(self.__background) else: image = Image.new("RGB", (self.width, self.height), "white") draw = ImageDraw.Draw(image) for iw in self.__walls: if ( isinstance(iw, Circle) or isinstance(iw, Ellipse) or isinstance(iw, Rectangle) or isinstance(iw, Polygon)): xy = iw.get_verts()/self.pixel_size xy[:,1] = self.height - xy[:,1] draw.polygon(sp.around(xy.flatten()).tolist(), outline="rgb(0, 0, 0)", fill="rgb(0, 0, 0)") elif isinstance(iw, Line2D): linewidth = iw.get_linewidth() xy = iw.get_xydata()/self.pixel_size xy[:,1] = self.height - xy[:,1] draw.line(sp.around(xy.flatten()).tolist(), width=int(linewidth), fill="rgb(0, 0, 0)") for id in self.__doors: linewidth = id.get_linewidth() xy = id.get_xydata()/self.pixel_size xy[:,1] = self.height - xy[:,1] draw.line(sp.around(xy.flatten()).tolist(), width=int(linewidth), fill="rgb(255, 0, 0)") self.__image_filename = self.__name+'_domain.png' image.save(self.__image_filename) ## Easy way to convert a Pillow image to numpy arrays... ## The origin of img is at the top (left) and flipud allows to put it down... self.image = sp.flipud(imread(self.__image_filename)) self.image_red = self.image[:,:,0] self.image_green = self.image[:,:,1] self.image_blue = self.image[:,:,2] self.mask = (self.image_red == 0) \ *(self.image_green == 0) \ *(self.image_blue == 0) self.mask_id = sp.where( self.mask )
[docs] def compute_wall_distance(self): """ To compute the geodesic distance to the walls in using \ a fast-marching method """ phi = sp.ones(self.image_red.shape) if (len(self.mask_id[0])>0): phi[self.mask_id] = 0 self.wall_distance = skfmm.distance(phi, dx=self.pixel_size) grad = sp.gradient(self.wall_distance,edge_order=2) grad_X = grad[1]/self.pixel_size grad_Y = grad[0]/self.pixel_size norm = sp.sqrt(grad_X**2+grad_Y**2) norm = (norm>0)*norm+(norm==0)*0.001 self.wall_grad_X = grad_X/norm self.wall_grad_Y = grad_Y/norm else: self.wall_distance = 1.0e99*sp.ones(self.image_red.shape)
[docs] def compute_desired_velocity(self): """ To compute the geodesic distance to the doors in using \ a fast-marching method. The opposite of the gradient of this distance corresponds to the desired velocity which permits to reach the closest door Returns ------- door_distance : numpy array distance to the closest door desired_velocity_X : numpy array opposite of the gradient of the door distance, x component desired_velocity_Y : numpy array opposite of the gradient of the door distance, y component """ mask_red = (self.image_red == 255) \ *(self.image_green == 0) \ *(self.image_blue == 0) ind_red = sp.where( mask_red ) phi = sp.ones(self.image_red.shape) phi[ind_red] = 0 phi = sp.ma.MaskedArray(phi, mask=self.mask) self.door_distance = skfmm.distance(phi, dx=self.pixel_size) tmp_dist = self.door_distance.filled(9999) grad = sp.gradient(tmp_dist,edge_order=2) grad_X = -grad[1]/self.pixel_size grad_Y = -grad[0]/self.pixel_size norm = sp.sqrt(grad_X**2+grad_Y**2) norm = (norm>0)*norm+(norm==0)*0.001 self.desired_velocity_X = grad_X/norm self.desired_velocity_Y = grad_Y/norm return self.door_distance, self.desired_velocity_X, self.desired_velocity_Y
[docs] def plot(self,id=1,dpi=150): """ To plot the computational domain Parameters ---------- id : integer Figure id (number) dpi : integer Figure resolution """ fig = plt.figure(id) ax1 = fig.add_subplot(111) ax1.imshow(self.image,interpolation='nearest',extent=[self.xmin,self.xmax, self.ymin,self.ymax], origin='lower') #plt.savefig('.png',dpi=dpi) plt.draw()
[docs] def plot_wall_dist(self,id=1,dpi=150): """ To plot the wall distances Parameters ---------- id : integer Figure id (number) dpi : integer Figure resolution """ fig = plt.figure(id) ax1 = fig.add_subplot(111) ax1.imshow(self.image,interpolation='nearest', extent=[self.xmin,self.xmax,self.ymin,self.ymax], origin='lower') ax1.imshow(self.wall_distance,interpolation='nearest', extent=[self.xmin,self.xmax,self.ymin,self.ymax],alpha=0.7, origin='lower') step = 10 ax1.quiver(self.X[::step, ::step],self.Y[::step, ::step], self.wall_grad_X[::step, ::step], self.wall_grad_Y[::step, ::step]) #plt.savefig('.png',dpi=dpi) plt.draw()
[docs] def plot_desired_velocity(self,id=1,dpi=150): """ To plot the desired velocity Parameters ---------- id : integer Figure id (number) dpi : integer Figure resolution """ fig = plt.figure(id) ax1 = fig.add_subplot(111) ax1.imshow(self.image,interpolation='nearest', extent=[self.xmin,self.xmax,self.ymin,self.ymax], origin='lower') ax1.imshow(self.door_distance,interpolation='nearest', extent=[self.xmin,self.xmax,self.ymin,self.ymax],alpha=0.7, origin='lower') step = 10 ax1.quiver(self.X[::step, ::step],self.Y[::step, ::step], self.desired_velocity_X[::step, ::step], self.desired_velocity_Y[::step, ::step]) #plt.savefig('.png',dpi=dpi) plt.draw()
def __str__(self): """ To print the main caracteristics of a Domain object """ return "--> "+self.__name+ \ " :\n dimensions : ["+str(self.xmin)+","+str(self.xmax)+"]x["+ \ str(self.ymin)+","+str(self.ymax)+"]"+ \ "\n width : "+str(self.width)+" height : "+str(self.height)+ \ "\n background image : "+str(self.__background)+ \ "\n image of the domain : "+str(self.__image_filename)+ \ "\n walls : "+str(self.__walls)+ \ "\n doors : "+str(self.__doors)