Skip to content

Python

pip install fiss-planner               # bindings, smoother and MPC included
pip install "fiss-planner[commonroad]" # CommonRoad adapter and benchmark

A single planning call

import numpy as np
import fiss_planner as fp

x = np.arange(0.0, 300.0, 5.0)
ref = fp.ReferenceLine(x, np.zeros_like(x))
lanes = fp.Corridor.parallel_lanes(0.0, ref.length, 3.5, 1, 0)
obstacles = [fp.predict_constant_velocity(1, 45.0, 0.0, 0.0, 0.0, 4.5, 1.9, horizon=6.0, dt=0.1)]

cfg = fp.make_config("fiss_plus", num_width=5)
cfg.cost.w_lane_change = 3.0
planner = fp.make_planner(cfg, fp.VehicleParams())
res = planner.plan(fp.FrenetState(s=10.0, s_d=10.0), ref, lanes, obstacles, target_speed=12.0)
traj = res.trajectory            # numpy arrays: traj.t, traj.x, traj.y, traj.yaw, traj.v, traj.kappa, traj.s, traj.d …
print(res.stats.rejections)      # why candidates were rejected
print(res.stats.lead.gap, res.stats.lead.ttc, res.stats.blocking_id)  # vehicle ahead; what blocked

Keeping distance is configured on the same object (see Concepts):

cfg.cost.desired_time_headway = 2.0   # comfort: car following and proximity cost
cfg.safety.min_ttc = 2.0              # hard envelope ("virtual bumper")

The full pipeline

lp = fp.LocalPlanner(fp.LocalPlannerConfig(), fp.VehicleParams())
lp.set_map(road_map, route_lane_ids, goal=(gx, gy))  # or lp.set_reference_path(xs, ys)
out = lp.step(t, fp.EgoState(x, y, yaw, v), obstacles)
lp.request_planner_type(fp.PlannerType.FOP)  # takes effect next cycle

road_map is an fp.RoadMap of fp.MapLanes; adapters build it from CommonRoad (fiss_planner.commonroad.road_map_from_lanelet_network) and CARLA (fiss_planner.carla.adapter.road_map_from_carla). The route lists lane ids in driving order and ends in the destination lane; the optional goal, a point in that lane, is where the route's lane changes must be done (see route urgency).

Configuration files and profiles

Every LocalPlannerConfig setting has a YAML key named after its field, the same as the ROS 2 parameter: the scalars (desired_speed, degraded_horizon, ...) at the top level, then the sections planner, sampling, cost, constraints, collision, safety, fiss_plus, route, stitching, path, smoother and vehicle. Missing keys keep their defaults and unknown keys raise an error.

cfg, vehicle = fp.load_local_planner_config("my_config.yaml", vehicle)  # fields not in the file keep their value
print(fp.to_yaml(cfg))                                                   # every setting; reads back exactly

A profile is such a file with every setting pinned, tuned for one benchmark or simulator. The shipped profiles are commonroad and carla_scenarios (see Benchmarks):

cfg, vehicle = fp.profiles.load("carla_scenarios", vehicle)
cfg, vehicle = fp.profiles.override(cfg, vehicle, {"cost.w_lane_change": 3.0, "desired_speed": 12.0})
fp.profiles.config_hash(cfg, vehicle)  # fingerprint the benchmarks record with every result

Prediction

ob = fp.predict_lane_following(road_map, obstacle_id, x, y, yaw, v, length, width, horizon=5.5, dt=0.1)
obstacles = fp.complete_predictions(obstacles, "lane_following", road_map)  # single-state obstacles only

Recording for Foxglove

from fiss_planner.viz import SceneRecorder, obstacle_dict   # pip install foxglove-sdk

rec = SceneRecorder("run.mcap")              # or SceneRecorder(live=True) for ws://localhost:8765
rec.lanes((road_map.lane(i).left, road_map.lane(i).right) for i in road_map.lane_ids())
rec.frame(t, ego, vehicle, [obstacle_dict(o) for o in obstacles], out, "fiss_plus")
rec.close()

See Foxglove.

Control

ctrl = fp.make_controller("mpc", vehicle)       # or "stanley_pid"
car = fp.KinematicBicycle(vehicle, ego)
car.step(ctrl.compute(car.state, out.trajectory, t_since_plan), 0.05)