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.


The image that serves as the background The image that serves as the background
The domain: background, black disk and red line added The domain : background + a shape (black circle)
Distance to walls and its gradient Distance to walls and its gradient
Distance to destination (red door) and desired velocity Distance to destination (red door) and the desired velocity

domain_room.py
 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.


The image that serves as the background The image that serves as the background
The domain, identical to the background image The domain, identical to the background image
Distance to walls and its gradient Distance to walls and its gradient
Distance to destination (red door) and desired velocity Distance to destination (red door) and the desired velocity

domain_stadium.py
 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).


The domain, identical to the background image The domain, identical to the background image
Distance to walls and its gradient Distance to walls and its gradient
Distance to destination (blue disk) and desired velocity Distance to destination (blue disk) and the desired velocity
Distance to destination (brown disk) and desired velocity Distance to destination (brown disk) and the desired velocity
Distance to destination (cyan disk) and desired velocity Distance to destination (cyan disk) and the desired velocity
Distance to destination (green disk) and desired velocity Distance to destination (green disk) and the desired velocity
Distance to destination (pink disk) and desired velocity Distance to destination (pink disk) and the desired velocity

domain_shibuya_crossing.py
 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.

domain_from_json.py
  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()