Skip to content

I fount this error #26

@cisrobot

Description

@cisrobot

[online_node-1] [/home/marin/marine/build/rko_lio/_deps/sophus-src/sophus/so3.hpp:747] SOPHUS_ENSURE failed:
[online_node-1] SO3::exp failed! omega: nan nan nan, real: nan, img: nan
[ERROR] [online_node-1]: process has died [pid 22875, exit code -6, cmd
'/home/marin/marine/install/rko_lio/lib/rko_lio/online_node --ros-args --log-level info --ros-args --params-file /tmp/launch_params_kzqrman1'].

I'm using velodyne vlp-16 and PX4 GPS. Also, I have to do project to ROS2 Humble. And topic name is /mavros/imu/data(it's px4 gps imu) and /velodyne_points.

but, when I run my robot, I saw this error.

And, this is my launch file setting. What can I do?

from pathlib import Path

import launch_ros.actions
import yaml
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, OpaqueFunction
from launch.substitutions import LaunchConfiguration

#import logging

logging.root.setLevel(logging.DEBUG)

offline_only_parameters = [
{
"name": "bag_path",
"default": "/home/marin/marine/src/rko_lio/bag",
"description": "[Offline node only] ROS bag path to process (required)",
"required": True,
},
{
"name": "skip_to_time",
"default": "0.0",
"type": "float",
"description": "[Offline node only] Skip to timestamp in the bag (seconds)",
},
]

configurable_parameters = [
{
"name": "imu_topic",
"default": "/mavros/imu/data",
"description": "IMU input topic (required)",
"required": True,
},
{
"name": "lidar_topic",
"default": "/velodyne_points",
"description": "LiDAR pointcloud topic (required)",
"required": True,
},
{
"name": "imu_frame",
"default": "base_link",
"description": "IMU frame, can be left empty if message frame id is to be used",
},
{
"name": "lidar_frame",
"default": "velodyne",
"description": "LiDAR frame, can be left empty if message frame id is to be used",
},
{
"name": "base_frame",
"default": "base_link",
"description": "Robot base frame, or the frame of estimation for odometry (required)",
"required": True,
},
{
"name": "odom_frame",
"default": "odom",
"description": "Odom frame id, used for publishing the tf from base frame to odom frame",
},
{
"name": "odom_topic",
"default": "/rko_lio/odometry",
"description": "Odometry topic name",
},
{
"name": "invert_odom_tf",
"default": "False",
"type": "bool",
"description": "Invert odometry transform if required so that the base frame is the parent and odom frame is the child in the TF tree",
},
{
"name": "publish_local_map",
"default": "False",
"type": "bool",
"description": "Publish local map",
},
{
"name": "map_topic",
"default": "/rko_lio/local_map",
"description": "Local map topic. Published if publish_local_map is true",
},
{
"name": "publish_map_after",
"default": "1.0",
"type": "float",
"description": "Seconds between each local map publishing",
},
{
"name": "publish_lidar_acceleration",
"default": "False",
"type": "bool",
"description": "Publish the linear acceleration of the base_frame expressed in base_frame coordinates. Note that this acceleration can be quite noisy, as it is essentially a double time derivative of the pose update from the lidar scan registration (similar to the twist/velocity in the odometry topic). The topic name is /rko_lio/lidar_acceleration.",
},
# lio parameters
{
"name": "deskew",
"default": "True", #False
"type": "bool",
"description": "Deskew the point cloud before registration, requires timestamps in the Lidar messages",
},
{
"name": "voxel_size",
"default": "1.0", #1.0
"type": "float",
"description": "Voxel size for the local map (meters) used for odometry",
},
{
"name": "max_points_per_voxel",
"default": "20", #20
"type": "int",
"description": "Max points per voxel",
},
{
"name": "max_range",
"default": "100", #100
"type": "float",
"description": "Max valid LiDAR range (meters)",
},
{
"name": "min_range",
"default": "1.0", #1,0
"type": "float",
"description": "Min valid LiDAR range (meters)",
},
{
"name": "max_correspondance_distance",
"default": "0.5",
"type": "float",
"description": "ICP correspondence distance threshold (meters)",
},
{
"name": "convergence_criterion",
"default": "0.00001",
"type": "float",
"description": "Optimization stopping condition for ICP",
},
{
"name": "max_num_threads",
"default": "0",
"type": "int",
"description": "Number of threads used for data association in ICP",
},
{
"name": "publish_deskewed_scan",
"default": "false",
"type": "bool",
"description": "Publish deskewed scan for visualization",
},
{
"name": "initialization_phase",
"default": "False", #False
"type": "bool",
"description": "Use the IMU data between the first two frames to initialize IMU bias and system orientation. Assumes the system is at rest between these two frames. WARNING: If this is enabled, but the odometry starts while the system is in motion, the odometry will likely not work as expected. But I highly recommended enabling this while ensuring the system starts from rest.",
},
{
"name": "max_iterations",
"default": "100",
"type": "int",
"description": "Max ICP iterations",
},
{
"name": "max_expected_jerk",
"default": "3.0",
"type": "float",
"description": "Max expected jerk (m/s^3)",
},
{
"name": "double_downsample",
"default": "False", #True
"type": "bool",
"description": "Double downsample the input cloud before registration. Useful for dense Lidars. Can be disabled for sparse lidars.",
},
{
"name": "min_beta",
"default": "200",
"type": "float",
"description": "Scale parameter that decides the minimum amount of orientation regularisation applied during ICP registration.",
},
# ros params
{
"name": "mode",
"default": "online",
"description": "Launch mode: 'offline' or 'online'",
},
{
"name": "config_file",
"default": "/home/marin/marine/src/rko_lio/ros/config/default.yaml",
"description": "YAML config file to load parameters from",
},
{
"name": "run_name",
"default": "rko_lio_odometry_run",
"description": "Run name, used when saving results to results_dir",
},
{
"name": "results_dir",
"default": "results",
"description": "When the odometry node exists, it dumps a trajectory file to this directory under a 'run_name' subfolder along with the LIO configuration parameters used for that run.",
},
{
"name": "rviz",
"default": "False",
"type": "bool",
"description": "Launch RViz simultaneously",
},
{
"name": "rviz_config_file",
"default": "config/default.rviz",
"description": "RViz config file path. If it's not the default value, note that it will be passed to rviz as is.",
},
{
"name": "log_level",
"default": "info",
"description": "ROS Log level [DEBUG|INFO|WARN|ERROR|FATAL]",
},
] + offline_only_parameters

def declare_configurable_parameters(parameters):
return [
DeclareLaunchArgument(
param["name"],
default_value=param["default"],
description=param.get("description", ""),
)
for param in parameters
]

def auto_cast_params(params, param_defs):
"""
Because for some reason, I cannot use a raw bool in configurable parameters dict
because of how DeclareLaunchArgument seems to work, so i have to write it as a string.
But then my node expects a bool so I need to explicitly cast it the string to bool again...
There must be a simpler way to do all this...
"""
out = {}
for p in param_defs:
name = p["name"]
v = params.get(name)
tp = p.get("type", None)
if tp == "bool":
out[name] = str(v).lower() == "true"
elif tp == "int":
out[name] = int(v)
elif tp == "float":
out[name] = float(v)
else:
out[name] = v
return out

def get_configurable_parameters(configurable_parameters, context):
return auto_cast_params(
dict(
[
(param["name"], LaunchConfiguration(param["name"]).perform(context))
for param in configurable_parameters
]
),
configurable_parameters,
)

def yaml_to_dict(path_to_yaml):
with open(path_to_yaml, "r") as f:
return yaml.safe_load(f)

def merge_and_validate_parameters(
cli_params: dict,
file_params: dict,
mode: str,
) -> dict:
"""
Merge CLI and file parameters:
- CLI overrides file values only if non-empty
- Validate required params are provided (depending on mode)
"""
merged = {}

merged.update(file_params)

# override with CLI only if non-empty
for k, v in cli_params.items():
    if v not in ("", None):
        merged[k] = v

# Validating required params
missing = []
for param in configurable_parameters:
    name = param["name"]

    # Skip offline-only params in online mode
    if mode == "online" and param in offline_only_parameters:
        continue

    if param.get("required", False):
        if name not in merged or not merged.get(name):
            missing.append(name)

if missing:
    print("\n\n" + "=" * 40)
    print("[ERROR] Missing required parameter(s):")
    print(", ".join(missing))
    print("Please provide them via CLI (param:=value) or a config file.")
    print("=" * 40 + "\n\n")
    import sys

    sys.exit(1)

return merged

def prepare_rviz_config(rviz_config_file: Path, parameters: dict) -> Path:
"""
Decide which RViz config to use.
If rviz_config_file is not the default, just return it.
Otherwise, patch the default config with base_frame.
"""
default_config = Path("config/default.rviz")

# If user provided a custom config, just return it unchanged
if rviz_config_file != default_config:
    return rviz_config_file

rviz_config_file = Path(get_package_share_directory("rko_lio")) / rviz_config_file

base_frame = parameters.get("base_frame", "")
if not base_frame:
    return rviz_config_file  # no override needed

# Load default config
with open(rviz_config_file, "r") as f:
    rviz_cfg = yaml.safe_load(f)

try:
    rviz_cfg["Visualization Manager"]["Views"]["Current"][
        "Target Frame"
    ] = base_frame
except Exception as e:
    raise RuntimeError(
        f"Could not patch RViz config with base_frame ({base_frame}): {e}"
    )

# Write to a temp file
import tempfile

tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".rviz", delete=False)
yaml.safe_dump(rviz_cfg, tmp)
tmp.flush()
# since its the default config file, we also want to viz the deskewed scan and local map
parameters["publish_deskewed_scan"] = True
parameters["publish_local_map"] = True

return Path(tmp.name)

def launch_setup(context, *args, **kwargs):
mode = LaunchConfiguration("mode").perform(context).lower()

# Prepare parameters
config_file = LaunchConfiguration("config_file").perform(context)
params_from_file = {} if config_file == "" else yaml_to_dict(config_file)
cli_params = get_configurable_parameters(configurable_parameters, context=context)
final_params = merge_and_validate_parameters(
    cli_params=cli_params,
    file_params=params_from_file,
    mode=mode,
)

print("\n" + "=" * 40 + "\n")
print("Using Launch configuration:\n")
print(yaml.dump(final_params, sort_keys=False, default_flow_style=False, indent=4))
print("=" * 40 + "\n")

rviz_enabled = LaunchConfiguration("rviz").perform(context).lower() == "true"
if rviz_enabled:
    rviz_config_file = prepare_rviz_config(
        Path(LaunchConfiguration("rviz_config_file").perform(context)),
        final_params,
    )

node_executable = "online_node" if mode == "online" else "offline_node"

nodes = [
    launch_ros.actions.Node(
        package="rko_lio",
        executable=node_executable,
        parameters=[final_params],
        output="screen",
        arguments=[
            "--ros-args",
            "--log-level",
            LaunchConfiguration("log_level"),
        ],
        emulate_tty=True,
    )
]

if rviz_enabled:
    nodes.append(
        launch_ros.actions.Node(
            package="rviz2",
            executable="rviz2",
            name="rviz2",
            arguments=["-d", rviz_config_file.as_posix()],
            output="screen",
        )
    )

return nodes

def generate_launch_description():
return LaunchDescription(
declare_configurable_parameters(configurable_parameters)
+ [OpaqueFunction(function=launch_setup)]
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions