Build a domain¶
A Domain object contains:
a geometry defined by a background image (completely white by default) and Matplotlib shapes that can be added (lines, circles, ellipses, rectangles or polygons). Depending on the color chosen for a shape, its meaning changes. For example, you can decide that a red line corresponds to one door and a green line represents another. To build a computational domain, we therefore use a background image (drawing, map of the site, satellite photo, etc.) or we leave an entirely white background, then we optionally add colored shapes, and finally we define a color code. In general the walls are coded in black. All colors are taken in rgb format: [r, g, b] with r, g and b integers between 0 and 255.
a 2D array of the size of the image representing the distance to the nearest wall (coded by all the colors defined in wall_colors). This means that each pixel in the image has its distance to the nearest wall.
A set of possible destinations attributable to people. A destination is defined by a Destination object which contains all the elements useful for its construction: the color codes representing the destination on the domain (red for a door represented by a red line for example), exclusion zones also defined via colors (black for an impassable wall for example), the names of the next destination and the next domain (we can chain several destinations to go up one floor: one Domain object per floor, one Destination object leading to the stairs, another to climb the stairs, etc …). Each destination will contain a 2D array of the size of the image containing the distance to the objective (the aimed door for example). The opposite of the gradient of this distance represents the desired direction (or named desired velocity) for an individual desiring to reach this outcome. This desired direction is also stored in an array.
Reference : [MF2018] Chapter 8.
All the following examples can be found in the directory
cromosim/examples/domain/
The simplest example¶
python domain_room.py
This domain uses an image as background and two forms: a red line used to define a door and a black disc to add an obstacle a few meters before the door.
|
|
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from cromosim import *
from matplotlib.patches import Circle
from matplotlib.lines import Line2D
## To create a Domain object from a background image
dom = Domain(name = 'room', background = 'room.png', pixel_size = 0.1)
## To define the color for the walls
wall_color = [0,0,0]
## To add an obstacle using a matplotlib shape colored with wall_color :
## Circle( (center_x,center_y), radius )
circle = Circle((20.0,7.0), 1.0)
dom.add_shape(circle, outline_color=wall_color, fill_color=wall_color)
## To define the color for the issue of the room
door_color = [255,0,0]
## To add a door using a matplotlib shape :
## Line2D(xdata, ydata, linewidth)
line = Line2D( [17.0,23.0],[3.1,3.1], 2)
dom.add_shape(line, outline_color=door_color, fill_color=door_color)
## To build the domain :
dom.build_domain()
## To plot the domain : backgroud + added shapes
dom.plot(id=1, title="Domain")
## To create a Destination object towards the door
dest = Destination(name='door', colors=[door_color],
excluded_colors=[wall_color])
dom.add_destination(dest)
## To plot the wall distance and its gradient
dom.plot_wall_dist(id=2, step=20,
title="Distance to walls and its gradient",
savefig=False, filename="room_wall_distance.png")
## To plot the distance to the red door and the correspondant
## desired velocity
dom.plot_desired_velocity('door',id=3, step=20,
title="Distance to the destination and desired velocity",
savefig=False, filename="room_desired_velocity.png")
print("===> Domain: ",dom)
plt.show()
|
A circular domain¶
python domain_stadium.py
This domain contains black walls rgb=[0,0,0], a red line rgb=[255,0,0] and a green line rgb=[0,255,0], all included in the given background.
If we calculate the desired velocity leading to the red line by solving the Eikonal equation using a Fast-Marching method, then we get arrows directed towards this line at any point on the track. For example, a person just to the right of the line will go to the left and a person just to the left will go to the right, so people will not turn around the track.
To correct this problem, we add a fictitious wall (green line) used only during the Fast-Marching method: thanks to it a person located to the left of the red line will go to the left and go around the track.
Finally if we want people to do several laps, we impose a desired velocity equal to (-1.0) on the green and red pixels using the desired_velocity_from_color option of the Destination object.
|
|
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | from cromosim import *
## To create a Domain object from a background image
## By default the black color is for the walls
dom = Domain(name = 'stadium', pixel_size = 0.1, background = "stadium.png")
## To build the domain :
dom.build_domain()
## To plot the domain : backgroud + added shapes (none here, they were added
## directly to the drawing using drawing software)
dom.plot(id=1,title="Domain")
## To create a Destination object towards the red line
## Since the domain is circular here, we want people to go to the red line
## and then cross it and continue to turn. For this, the color green is used
## to block one side of the track when calculating the desired velocity (as a
## temporary wall will do). Once this calculation is done, the desired speed
## is imposed at [-1,0] on the red and green pixels..
dest = Destination(name='running_track',
colors=[[255,0,0]],
excluded_colors = [[0,0,0],[0,255,0]],
desired_velocity_from_color =
[[255,0,0, -1,0],[0,255,0, -1,0]])
dom.add_destination(dest)
## To plot the wall distance and its gradient
dom.plot_wall_dist(id=2,step=30,
title="Distance to walls and its gradient",
savefig=False, filename="stadium_wall_distance.png")
## To plot the distance to the red line and the correspondant desired velocity
dom.plot_desired_velocity('running_track',id=3,step=30,
title="Distance to the destination and desired velocity",
savefig=False, filename="stadium_desired_velocity.png")
print("===> Domain: ",dom)
plt.show()
|
A domain with several destinations¶
python domain_shibuya_crossing.py
Shibuya Crossing is a popular scramble crossing in Shibuya, Tokyo, Japan. Here some walls have been added (magenta walls) to channel pedestrians on the pedestrian crossings and we have defined five destinations leading to five disks of different colors (blue, brown, cyan, green and pink).
|
|
|
|
|
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | from cromosim import *
## The walls are represented here by the black and magenta colors
black = [0,0,0]
magenta = [148,33,146]
wall_colors = [black, magenta]
## To create a Domain object from a background image
dom = Domain(name = 'shibuya_crossing',
pixel_size = 0.033,
background = "shibuya_crossing.png",
wall_colors = wall_colors)
## To build the domain :
dom.build_domain()
## To plot the domain
dom.plot(id=1, title="Domain")
## To define all the destinations
## --> towards the blue disk
blue = [4,51,255]
dest_blue = Destination(name='blue',colors=[blue],excluded_colors=wall_colors)
dom.add_destination(dest_blue)
## --> towards the brown disk
brown = [170,121,66]
dest_brown = Destination(name='brown',colors=[brown],
excluded_colors=wall_colors)
dom.add_destination(dest_brown)
## --> towards the cyan disk
cyan = [0,253,255]
dest_cyan = Destination(name='cyan',colors=[cyan],
excluded_colors=wall_colors)
dom.add_destination(dest_cyan)
## --> towards the green disk
green = [0,249,0]
dest_green = Destination(name='green',colors=[green],
excluded_colors=wall_colors)
dom.add_destination(dest_green)
## --> towards the pink disk
pink = [255,64,255]
dest_pink = Destination(name='pink',colors=[pink],
excluded_colors=wall_colors)
dom.add_destination(dest_pink)
print("===> Domain: ",dom)
## To plot the wall distance and its gradient
dom.plot_wall_dist(id=3,step=40,
title="Distance to walls and its gradient",
savefig=False, filename="shibuya_crossing_wall_distance.png")
## To plot all the desired velocity fields
dom.plot_desired_velocity('blue',id=4,step=40,
title='Distance to the destination "blue" and desired velocity',
savefig=False, filename="shibuya_crossing_blue.png")
dom.plot_desired_velocity('brown',id=5,step=40,
title='Distance to the destination "brown" and desired velocity',
savefig=False, filename="shibuya_crossing_brown.png")
dom.plot_desired_velocity('cyan',id=6,step=40,
title='Distance to the destination "cyan" and desired velocity',
savefig=False, filename="shibuya_crossing_cyan.png")
dom.plot_desired_velocity('green',id=7,step=40,
title='Distance to the destination "green" and desired velocity',
savefig=False, filename="shibuya_crossing_green.png")
dom.plot_desired_velocity('pink',id=8,step=40,
title='Distance to the destination "pink" and desired velocity',
savefig=False, filename="shibuya_crossing_pink.png")
plt.show()
|
A domain build from a json file¶
python domain_from_json.py --json input_room.json
python domain_from_json.py --json input_stadium.json
python domain_from_json.py --json input_shibuya_crossing.json
JSON (JavaScript Object Notation) is a lightweight data-interchange format which can be used to describe one or more domains. This means in our case that with a single python script taking as argument a json file we will be able to build several domains.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | import sys, os
from cromosim import *
from matplotlib.patches import Circle
from matplotlib.lines import Line2D
from optparse import OptionParser
import json
## Parse the arguments of the python command. Then the json file is loaded.
parser = OptionParser(usage="usage: %prog [options] filename",
version="%prog 1.0")
parser.add_option('--json',dest="jsonfilename",
default="input.json",type="string",
action="store",help="Input json filename")
opt, remainder = parser.parse_args()
print("===> JSON filename = ",opt.jsonfilename)
with open(opt.jsonfilename) as json_file:
try:
input = json.load(json_file)
except json.JSONDecodeError as msg:
print(msg)
print("Failed to load json file ",opt.jsonfilename)
print("Check its content :")
print("https://fr.wikipedia.org/wiki/JavaScript_Object_Notation)")
sys.exit()
"""
Get parameters from json file :
For the domain :
| name: string
| Domain name
| background: string
| Image file used as background
| px: float
| Pixel size in meters (also called space step)
| width: integer
| Domain width (equal to the width of the background image)
| height: integer
| Domain height (equal to the height of the background image)
| wall_colors: list
| rgb colors for walls
| [ [r,g,b],[r,g,b],... ]
| shape_lines: list
| Used to define the Matplotlib Polyline shapes,
| [
| {
| "xx": [x0,x1,x2,...],
| "yy": [y0,y1,y2,...],
| "linewidth": float,
| "outline_color": [r,g,b],
| "fill_color": [r,g,b]
| },...
| ]
| shape_circles: list
| Used to define the Matplotlib Circle shapes,
| [
| {
| "center_x": float,
| "center_y": float,
| "radius": float,
| "outline_color": [r,g,b],
| "fill_color": [r,g,b]
| },...
| ]
| shape_ellipses: list
| Used to define the Matplotlib Ellipse shapes,
| [
| {
| "center_x": float,
| "center_y": float,
| "width": float,
| "height": float,
| "angle_in_degrees_anti-clockwise": float (degre),
| "outline_color": [r,g,b],
| "fill_color": [r,g,b]
| },...
| ]
| shape_rectangles: list
| Used to define the Matplotlib Rectangle shapes,
| [
| {
| "bottom_left_x": float,
| "bottom_left_y": float,
| "width": float,
| "height": float,
| "angle_in_degrees_anti-clockwise": float (degre),
| "outline_color": [r,g,b],
| "fill_color": [r,g,b]
| },...
| ]
| shape_polygons: list
| Used to define the Matplotlib Polygon shapes,
| [
| {
| "xy": float,
| "outline_color": [r,g,b],
| "fill_color": [r,g,b]
| },...
| ]
| destinations: list
| Used to define the Destination objects,
| [
| {
| "name": string,
| "colors": [[r,g,b],...],
| "excluded_colors": [[r,g,b],...],
| "desired_velocity_from_color": [] or
| [
| {
| "color": [r,g,b],
| "desired_velocity": [ex,ey]
| },...
| ],
| "velocity_scale": float,
| "next_destination": null or string,
| "next_domain": null or string,
| "next_transit_box": null or [[x0,y0],...,[x3,y3]]
| },...
| ]
|--------------------
"""
jdom = input["domain"]
print("===> JSON data used to build the domain : ",jdom)
"""
Build the Domain object
"""
jname = jdom["name"]
print("===> Build domain ",jname)
jbg = jdom["background"]
jpx = jdom["px"]
jwidth = jdom["width"]
jheight = jdom["height"]
jwall_colors = jdom["wall_colors"]
if (jbg==""):
dom = Domain(name=jname, pixel_size=jpx, width=jwidth, height=jheight,
wall_colors=jwall_colors)
else:
dom = Domain(name=jname, background=jbg, pixel_size=jpx,
wall_colors=jwall_colors)
## To add lines : Line2D(xdata, ydata, linewidth)
for sl in jdom["shape_lines"]:
line = Line2D(sl["xx"],sl["yy"],linewidth=sl["linewidth"])
dom.add_shape(line,outline_color=sl["outline_color"],
fill_color=sl["fill_color"])
## To add circles : Circle( (center_x,center_y), radius )
for sc in jdom["shape_circles"]:
circle = Circle( (sc["center_x"], sc["center_y"]), sc["radius"] )
dom.add_shape(circle,outline_color=sc["outline_color"],
fill_color=sc["fill_color"])
## To add ellipses : Ellipse( (center_x,center_y), width, height,
## angle_in_degrees_anti-clockwise )
for se in jdom["shape_ellipses"]:
ellipse = Ellipse( (se["center_x"], se["center_y"]),
se["width"], se["height"],
se["angle_in_degrees_anti-clockwise"])
dom.add_shape(ellipse,outline_color=se["outline_color"],
fill_color=se["fill_color"])
## To add rectangles : Rectangle( (bottom_left_x,bottom_left_y), width,
## height, angle_in_degrees_anti-clockwise )
for sr in jdom["shape_rectangles"]:
rectangle = Rectangle( (sr["bottom_left_x"],sr["bottom_left_y"]),
sr["width"], sr["height"],
sr["angle_in_degrees_anti-clockwise"])
dom.add_shape(rectangle,outline_color=sr["outline_color"],
fill_color=sr["fill_color"])
## To add polygons : Polygon( [[x0,y0],[x1,y1],...] )
for spo in jdom["shape_polygons"]:
polygon = Polygon(spo["xy"])
dom.add_shape(polygon,outline_color=spo["outline_color"],
fill_color=spo["fill_color"])
## To build the domain : background + shapes
dom.build_domain()
## To add all the available destinations
for j,dd in enumerate(jdom["destinations"]):
desired_velocity_from_color=[]
for gg in dd["desired_velocity_from_color"]:
desired_velocity_from_color.append(
np.concatenate((gg["color"],gg["desired_velocity"])))
dest = Destination(name=dd["name"],colors=dd["colors"],
excluded_colors=dd["excluded_colors"],
desired_velocity_from_color=desired_velocity_from_color,
velocity_scale=dd["velocity_scale"],
next_destination=dd["next_destination"],
next_domain=dd["next_domain"],
next_transit_box=dd["next_transit_box"])
print("===> Destination : ",dest)
dom.add_destination(dest)
## To plot all the desired velocity field
dom.plot_desired_velocity(dd["name"],id=10+j,step=20)
print("===> Domain: ",dom)
## To plot the domain
dom.plot(id=0)
## To plot the wall distance and its gradient
dom.plot_wall_dist(id=1,step=20)
plt.show()
|