Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/createScreenshotSequence.py
169659 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file createScreenshotSequence.py
15
# @author Mirko Barthauer
16
# @date 2023-05-25
17
18
from __future__ import print_function
19
import os
20
import sys
21
from datetime import datetime
22
23
if 'SUMO_HOME' in os.environ:
24
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
25
import sumolib # noqa
26
import traci # noqa
27
28
29
def getOptions(args=None):
30
argParser = sumolib.options.ArgumentParser(
31
description="Run SUMO simulation and take screenshots of chosen time intervals")
32
argParser.add_argument("--sumocfg", category="input", dest="sumocfg",
33
help="define the sumocfg to be loaded")
34
argParser.add_argument("-o", "--output-dir", category="output", dest="outdir",
35
default="screenshots", help="define the output directory to write the screenshots to")
36
argParser.add_argument("--begin", category="input", type=float,
37
help="simulation time the recording begins")
38
argParser.add_argument("--end", category="input", type=float,
39
help="simulation time the recording ends")
40
argParser.add_argument("--view", category="input", type=str, default="View #0",
41
help="View ID to use for the screenshots")
42
argParser.add_argument("-p", "--prefix", category="input", dest="prefix",
43
default="", help="define a prefix of the screenshot filename")
44
argParser.add_argument("--zoom",
45
help="linear interpolation of zoom values given the key frames syntax t1:v1[;t2:v2 ...]")
46
argParser.add_argument("--rotate",
47
help="linear interpolation to rotation values in degrees (around Z axis) "
48
"given the key frames syntax t1:v1[;t2:v2 ...]")
49
argParser.add_argument("--translate",
50
help="linear interpolation to the given view center points "
51
"with the key frames syntax t1:p1[;t2:p2 ...] where p1 is \"x,y\"")
52
argParser.add_argument("--include-time", dest="includeTime", category="processing", action="store_true",
53
default=False,
54
help="whether to include the system time at simulation begin in the file name")
55
argParser.add_argument("--image-format", category="processing", dest="imageFormat",
56
default="png", help="image format to use")
57
58
options = argParser.parse_args(args=args)
59
if not options.sumocfg:
60
argParser.print_help()
61
sys.exit(1)
62
return options
63
64
65
def main(options):
66
# create directory if it does not exist
67
outputPath = os.path.abspath(options.outdir)
68
69
if not os.path.exists(outputPath):
70
os.mkdir(outputPath)
71
72
# remember system time before the simulation begins
73
formattedTime = datetime.now().strftime("%Y%m%d-%H_%M_%S")
74
traci.start(["sumo-gui", "-c", options.sumocfg])
75
recordAll = options.begin is None and options.end is None
76
listener = ScreenshotHelper(outputPath, "%s%s" % (options.prefix, formattedTime if options.includeTime else ""),
77
options.imageFormat, 1, view=options.view, recordAll=recordAll)
78
if not recordAll:
79
listener.addTimeInterval(begin=options.begin, end=options.end)
80
if options.zoom is not None:
81
zoomTargets = [(float(pair[:pair.index(":")]), float(pair[pair.index(":")+1:]))
82
for pair in options.zoom.split(";")]
83
for t, value in zoomTargets:
84
listener.addTransformTarget(t, value, transform="zoom")
85
if options.rotate is not None:
86
rotateTargets = [(float(pair[:pair.index(":")]), float(pair[pair.index(":")+1:]))
87
for pair in options.rotate.split(";")]
88
for t, value in rotateTargets:
89
listener.addTransformTarget(t, value, transform="rotate")
90
if options.translate is not None:
91
translateTargets = [(float(pair[:pair.index(":")]), [float(c) for c in pair[pair.index(":")+1:].split(",")])
92
for pair in options.translate.split(";")]
93
for t, value in translateTargets:
94
listener.addTransformTarget(t, value, transform="translate")
95
traci.addStepListener(listener)
96
sumoEndTime = listener.getEndTime()
97
t = float(traci.simulation.getOption("begin"))
98
simStep = float(traci.simulation.getOption("step-length"))
99
while t < sumoEndTime:
100
traci.simulationStep()
101
t += simStep
102
traci.close()
103
104
105
class KeyFramedNumericAttribute(object):
106
107
InterpolationModes = ("linear",)
108
109
def __init__(self, initT, initValue):
110
self._interpolation = self.InterpolationModes[0]
111
self._dimensions = 1
112
self._targets = []
113
self.addTarget(initT, initValue)
114
print("initialised KeyFramedNumericAttribute with (%.2f, %s)" % (initT, str(initValue)))
115
116
def addTarget(self, t, value):
117
if isinstance(value, float):
118
value = [value]
119
self._targets.append((t, tuple(value)))
120
self._targets.sort(key=lambda x: x[0])
121
if len(value) > self._dimensions:
122
self._dimensions = len(value)
123
# TODO: recreate the targets tuples with additional value dimension
124
125
def hasKeyFrames(self):
126
return len(self._targets) > 1
127
128
def update(self, t):
129
if self.hasKeyFrames() and self._targets[1][0] < t:
130
self._targets.pop(0)
131
132
def get(self, t):
133
if self._interpolation == "linear":
134
if self._targets[1][0] - self._targets[0][0] == 0:
135
result = [self._targets[0][1][dim] for dim in range(self._dimensions)]
136
else:
137
reached = (t - self._targets[0][0]) / (self._targets[1][0] - self._targets[0][0])
138
deltaValue = [self._targets[1][1][dim] - self._targets[0][1][dim] for dim in range(self._dimensions)]
139
result = [self._targets[0][1][dim] + reached * deltaValue[dim] for dim in range(self._dimensions)]
140
if self._dimensions == 1:
141
return result[0]
142
else:
143
return tuple(result)
144
return 0
145
146
147
class ScreenshotHelper(traci.StepListener):
148
149
def __init__(self, outputDir, prefix, imageFormat, frequency, view="View #0", recordAll=True):
150
traci.StepListener()
151
# init member variables
152
self._outputDir = outputDir
153
self._prefix = prefix
154
self._imageFormat = imageFormat
155
self._frequency = frequency
156
self._view = view
157
self.__counter = 0
158
self._recordIntervals = []
159
self._startTime = float(traci.simulation.getOption("begin"))
160
self._endTime = float(traci.simulation.getOption("end"))
161
self._simTime = self._startTime
162
self._simStep = float(traci.simulation.getOption("step-length"))
163
self._zoom = KeyFramedNumericAttribute(self._startTime, traci.gui.getZoom(viewID=view))
164
self._rotate = KeyFramedNumericAttribute(self._startTime, traci.gui.getAngle(viewID=view))
165
self._translate = KeyFramedNumericAttribute(self._startTime, traci.gui.getOffset(viewID=view))
166
if recordAll:
167
self.addTimeInterval()
168
self._imageCount = int((self._endTime - self._startTime) /
169
(float(traci.simulation.getOption("step-length")) * frequency))
170
self._fileNameTemplate = "%s%0" + str(len(str(self._imageCount))) + "d.%s"
171
172
def addTransformTarget(self, timeKey, value, transform="zoom"):
173
if transform == "zoom":
174
self._zoom.addTarget(timeKey, value)
175
elif transform == "rotate":
176
self._rotate.addTarget(timeKey, value)
177
elif transform == "translate":
178
self._translate.addTarget(timeKey, value)
179
180
def addTimeInterval(self, begin=None, end=None):
181
if begin is None:
182
begin = self._startTime
183
else:
184
begin = max(0, begin)
185
if end is None:
186
if self._endTime > 0:
187
end = self._endTime
188
else:
189
end = sys.maxsize
190
else:
191
end = max(begin + self._simStep, min(end, sys.maxsize))
192
self._recordIntervals.append((begin, end))
193
self._recordIntervals.sort(key=lambda x: x[0])
194
195
def getBeginTime(self):
196
return self._recordIntervals[0][0]
197
198
def getEndTime(self):
199
return self._recordIntervals[-1][1]
200
201
def _updateTime(self):
202
if self._recordIntervals[0][1] < self._simTime:
203
self._recordIntervals.pop(0)
204
self._zoom.update(self._simTime)
205
self._rotate.update(self._simTime)
206
self._translate.update(self._simTime)
207
208
def step(self, t):
209
self._updateTime()
210
if len(self._recordIntervals) == 0:
211
return False
212
if self._zoom.hasKeyFrames():
213
traci.gui.setZoom(self._view, self._zoom.get(self._simTime))
214
if self._rotate.hasKeyFrames():
215
traci.gui.setAngle(self._view, self._rotate.get(self._simTime))
216
if self._translate.hasKeyFrames():
217
traci.gui.setOffset(self._view, *(self._translate.get(self._simTime)))
218
if (self._simTime > self._recordIntervals[0][0] and self._simTime <= self._recordIntervals[0][1]
219
and self.__counter % self._frequency == 0):
220
traci.gui.screenshot(self._view, os.path.join(self._outputDir, self._fileNameTemplate %
221
(self._prefix, self.__counter, self._imageFormat)))
222
self.__counter += 1
223
self._simTime += self._simStep
224
return True
225
226
227
if __name__ == "__main__":
228
if not main(getOptions()):
229
sys.exit(1)
230
231