mirror of
https://github.com/pim-n/road-gen.git
synced 2026-02-03 09:23:09 +01:00
Merge pull request #3 from pim-n/dev
Release 0.2.0 adding segmented road feature to cli
This commit is contained in:
12
README.md
12
README.md
@ -54,10 +54,20 @@ road-gen random -l 1000 -s 10 -v 10 --save
|
|||||||
|
|
||||||
which will produce 3 files starting with the unique seed number used to generate the road.
|
which will produce 3 files starting with the unique seed number used to generate the road.
|
||||||
|
|
||||||
|
## Example - segmented road
|
||||||
|
|
||||||
|
A minimal segmented road needs, besides length $L$, step size $\Delta s$ and velocity $v$, a list of segments.
|
||||||
|
|
||||||
|
```
|
||||||
|
road-gen segments --segments straight turn_left straight turn_right --length 1000 --ds 10 --velocity 10
|
||||||
|
```
|
||||||
|
|
||||||
|
The parameter $\alpha$ represents the concentration factor for the [Dirichlet distribution](https://numpy.org/doc/2.0/reference/random/generated/numpy.random.dirichlet.html).
|
||||||
|
|
||||||
## Reproducability
|
## Reproducability
|
||||||
|
|
||||||
You can reproduce results by adding a seed with the `--seed` flag.
|
You can reproduce results by adding a seed with the `--seed` flag.
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
For more info, just see `road-gen --help` or `road-gen random --help`.
|
For more info, see `road-gen --help` or `road-gen random --help`.
|
||||||
@ -33,27 +33,25 @@ class SegmentedRoadGenerator(BaseRoadGenerator):
|
|||||||
def generate(
|
def generate(
|
||||||
self,
|
self,
|
||||||
segments: list[str],
|
segments: list[str],
|
||||||
alpha: float = 1.0
|
alpha: float = 100
|
||||||
) -> Tuple[np.ndarray, np.ndarray]:
|
) -> Tuple[np.ndarray, np.ndarray]:
|
||||||
"""Generate a curvature profile from a list of segments.
|
"""Generate a curvature profile from a list of segments.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
segments (list[str]): List of segments.
|
segments (list[str]): List of segments.
|
||||||
length (int): Total length of the path in meters.
|
|
||||||
velocity (float): Velocity of the car in km/h.
|
|
||||||
ds (float, optional): Step size. Defaults to 1.0.
|
|
||||||
alpha (float, optional): Dirichlet concentration parameter. A higher value leads to more uniform apportionment of the length amongst the segments, while a lower value allows more random apportionment. Defaults to 1.0.
|
alpha (float, optional): Dirichlet concentration parameter. A higher value leads to more uniform apportionment of the length amongst the segments, while a lower value allows more random apportionment. Defaults to 1.0.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: _description_
|
ValueError: "No valid radius for this turn segment" means a turn is too tight given its segment length and the velocity. To fix this, you can try to reduce the amount of segments or increase length. Increasing alpha (Dirichlet concentration parameter) can also help because this reduces the odds of very small lengths being assigned to turn segments.
|
||||||
ValueError: _description_
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray: _description_
|
Tuple[np.ndarray, np.ndarray]: x and y coordinates of the waypoints describing the random road.
|
||||||
"""
|
"""
|
||||||
if not all(segment in prefabs.PREFABS.keys() for segment in segments):
|
if not all(segment in prefabs.PREFABS.keys() for segment in segments):
|
||||||
raise ValueError(f"Invalid segment type provided. Available choices: {prefabs.SEGMENTS.keys()}")
|
raise ValueError(f"Invalid segment type provided. Available choices: {prefabs.SEGMENTS.keys()}")
|
||||||
|
|
||||||
|
self.segments = segments
|
||||||
|
self.alpha = alpha
|
||||||
num_points = int(self.length / self.ds)
|
num_points = int(self.length / self.ds)
|
||||||
|
|
||||||
# divide num_points into len(segments) randomly sized parts.
|
# divide num_points into len(segments) randomly sized parts.
|
||||||
@ -84,7 +82,7 @@ class SegmentedRoadGenerator(BaseRoadGenerator):
|
|||||||
if R_min > R_max_angle:
|
if R_min > R_max_angle:
|
||||||
raise ValueError("No valid radius for this turn segment")
|
raise ValueError("No valid radius for this turn segment")
|
||||||
|
|
||||||
rand_radius = np.random.uniform(R_min, R_max_angle)
|
rand_radius = self._rng.uniform(R_min, R_max_angle)
|
||||||
|
|
||||||
if seg_name.startswith("u_turn"):
|
if seg_name.startswith("u_turn"):
|
||||||
curvature_s = seg_function(rand_radius)
|
curvature_s = seg_function(rand_radius)
|
||||||
|
|||||||
@ -4,9 +4,8 @@ from matplotlib import pyplot as plt
|
|||||||
|
|
||||||
from .generators.random_road_generator import RandomRoadGenerator
|
from .generators.random_road_generator import RandomRoadGenerator
|
||||||
from .generators.segmented_road_generator import SegmentedRoadGenerator
|
from .generators.segmented_road_generator import SegmentedRoadGenerator
|
||||||
|
|
||||||
from .plotting.plot_road import plot_road
|
from .plotting.plot_road import plot_road
|
||||||
|
from .prefabs import prefabs
|
||||||
from .utils import export
|
from .utils import export
|
||||||
|
|
||||||
def add_common_args(parser):
|
def add_common_args(parser):
|
||||||
@ -30,48 +29,60 @@ def main():
|
|||||||
random_parser.add_argument("--straight_section_max_rel_size", type=float, required=False, help="The maximum size that straight section(s) can have relative to the total length of the path. Defaults to 0.1.")
|
random_parser.add_argument("--straight_section_max_rel_size", type=float, required=False, help="The maximum size that straight section(s) can have relative to the total length of the path. Defaults to 0.1.")
|
||||||
add_common_args(random_parser)
|
add_common_args(random_parser)
|
||||||
|
|
||||||
|
segment_parser = subparsers.add_parser("segments", help="Generate a road according to a list of segments.")
|
||||||
|
segment_parser.add_argument("--segments", nargs="+", type=str, required=True, help=f"List of segments. Choose from {str(prefabs.PREFABS.keys())}")
|
||||||
|
segment_parser.add_argument("--alpha", type=float, required=False, help="Dirichlet distribution concentration parameter. A high alpha distributes total length more evenly across segments.")
|
||||||
|
add_common_args(segment_parser)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.method == "random":
|
try:
|
||||||
try:
|
if not all(v > 0 for v in (args.length, args.ds, args.velocity)):
|
||||||
if not all(v > 0 for v in (args.length, args.ds, args.velocity)):
|
raise ValueError("Length, step size, and velocity must be positive values.")
|
||||||
raise ValueError("Length, step size, and velocity must be positive values.")
|
|
||||||
|
|
||||||
init_args = {
|
init_args = {
|
||||||
"length": args.length,
|
"length": args.length,
|
||||||
"ds": args.ds,
|
"ds": args.ds,
|
||||||
"velocity": args.velocity,
|
"velocity": args.velocity,
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.mu:
|
if args.mu:
|
||||||
init_args["mu"] = args.mu
|
init_args["mu"] = args.mu
|
||||||
if args.g:
|
if args.g:
|
||||||
init_args["g"] = args.g
|
init_args["g"] = args.g
|
||||||
if args.seed:
|
if args.seed:
|
||||||
init_args["seed"] = args.seed
|
init_args["seed"] = args.seed
|
||||||
|
|
||||||
|
generate_args = {}
|
||||||
|
if args.method == "random":
|
||||||
generator = RandomRoadGenerator(**init_args)
|
generator = RandomRoadGenerator(**init_args)
|
||||||
|
|
||||||
generate_args = {}
|
|
||||||
|
|
||||||
if args.straight_section_prob:
|
if args.straight_section_prob:
|
||||||
generate_args["straight_section_prob"] = float(args.straight_section_prob)
|
generate_args["straight_section_prob"] = float(args.straight_section_prob)
|
||||||
if args.straight_section_max_rel_size:
|
if args.straight_section_max_rel_size:
|
||||||
generate_args["straight_section_max_rel_size"] = float(args.straight_section_max_rel_size)
|
generate_args["straight_section_max_rel_size"] = float(args.straight_section_max_rel_size)
|
||||||
|
|
||||||
x, y = generator.generate(**generate_args)
|
elif args.method == "segments":
|
||||||
|
generator = SegmentedRoadGenerator(**init_args)
|
||||||
|
|
||||||
if args.save:
|
if args.alpha:
|
||||||
basename = str(generator.seed)
|
generate_args["alpha"] = args.alpha
|
||||||
plot_road(x, y, generator, save = True, filename = basename+".jpg")
|
|
||||||
export.coords_to_json(x, y, filename = basename+".json")
|
|
||||||
export.params_to_json(generator, filename = basename+".params.json")
|
|
||||||
else:
|
|
||||||
plot_road(x, y, generator)
|
|
||||||
|
|
||||||
except ValueError as e:
|
generate_args["segments"] = list(args.segments)
|
||||||
print(f"Error: {e}")
|
|
||||||
exit(1)
|
x, y = generator.generate(**generate_args)
|
||||||
|
|
||||||
|
if args.save:
|
||||||
|
basename = str(generator.seed)
|
||||||
|
plot_road(x, y, generator, save = True, filename = basename+".jpg")
|
||||||
|
export.coords_to_json(x, y, filename = basename+".json")
|
||||||
|
export.params_to_json(generator, filename = basename+".params.json")
|
||||||
|
else:
|
||||||
|
plot_road(x, y, generator)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@ -3,24 +3,42 @@ import numpy as np
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from road_gen.generators.random_road_generator import RandomRoadGenerator
|
from road_gen.generators.random_road_generator import RandomRoadGenerator
|
||||||
|
from road_gen.generators.segmented_road_generator import SegmentedRoadGenerator
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_params():
|
def base_params():
|
||||||
length = 1_000
|
length = 1_000
|
||||||
ds = 10
|
ds = 10
|
||||||
velocity = 10
|
velocity = 10
|
||||||
|
|
||||||
return length, ds, velocity
|
return length, ds, velocity
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def seg_params():
|
||||||
|
segments = ["straight", "turn_left", "straight", "turn_right"]
|
||||||
|
alpha = 100
|
||||||
|
|
||||||
def test_random_road_generator(test_params):
|
return segments, alpha
|
||||||
|
|
||||||
|
|
||||||
|
def test_random_road_generator(base_params):
|
||||||
"""Test whether fixing the seed for RandomRoadGenerator produces identical output."""
|
"""Test whether fixing the seed for RandomRoadGenerator produces identical output."""
|
||||||
generator_1 = RandomRoadGenerator(*test_params)
|
generator_1 = RandomRoadGenerator(*base_params)
|
||||||
x1, y1 = generator_1.generate()
|
x1, y1 = generator_1.generate()
|
||||||
|
|
||||||
generator_2 = RandomRoadGenerator(seed = generator_1.seed, *test_params)
|
generator_2 = RandomRoadGenerator(seed = generator_1.seed, *base_params)
|
||||||
x2, y2 = generator_2.generate()
|
x2, y2 = generator_2.generate()
|
||||||
|
|
||||||
assert np.array_equal(x1, x2)
|
assert np.array_equal(x1, x2)
|
||||||
assert np.array_equal(y1, y2)
|
assert np.array_equal(y1, y2)
|
||||||
|
|
||||||
|
def test_segmented_road_generator(base_params, seg_params):
|
||||||
|
"""Test whether fixing the seed for SegmentedRoadGenerator produces identical output."""
|
||||||
|
generator_1 = SegmentedRoadGenerator(*base_params)
|
||||||
|
x1, y1 = generator_1.generate(*seg_params)
|
||||||
|
|
||||||
|
generator_2 = SegmentedRoadGenerator(seed = generator_1.seed, *base_params)
|
||||||
|
x2, y2 = generator_2.generate(*seg_params)
|
||||||
|
|
||||||
|
assert np.array_equal(x1, x2)
|
||||||
|
assert np.array_equal(y1, y2)
|
||||||
Reference in New Issue
Block a user