diff --git a/sensor_calibration_manager/launch/x2_gen2/ground_plane_calibrator.launch.xml b/sensor_calibration_manager/launch/x2_gen2/ground_plane_calibrator.launch.xml
new file mode 100644
index 00000000..b4a6dda2
--- /dev/null
+++ b/sensor_calibration_manager/launch/x2_gen2/ground_plane_calibrator.launch.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensor_calibration_manager/launch/x2_gen2/mapping_based_lidar_lidar_calibrator.launch.xml b/sensor_calibration_manager/launch/x2_gen2/mapping_based_lidar_lidar_calibrator.launch.xml
new file mode 100644
index 00000000..1e458e46
--- /dev/null
+++ b/sensor_calibration_manager/launch/x2_gen2/mapping_based_lidar_lidar_calibrator.launch.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensor_calibration_manager/launch/x2_gen2/marker_radar_lidar_calibrator.launch.xml b/sensor_calibration_manager/launch/x2_gen2/marker_radar_lidar_calibrator.launch.xml
new file mode 100644
index 00000000..204e506c
--- /dev/null
+++ b/sensor_calibration_manager/launch/x2_gen2/marker_radar_lidar_calibrator.launch.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensor_calibration_manager/launch/x2_gen2/tag_based_pnp_calibrator.launch.xml b/sensor_calibration_manager/launch/x2_gen2/tag_based_pnp_calibrator.launch.xml
new file mode 100644
index 00000000..ee1bca44
--- /dev/null
+++ b/sensor_calibration_manager/launch/x2_gen2/tag_based_pnp_calibrator.launch.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensor_calibration_manager/launch/x2_gen2/tag_based_sfm_base_lidar_calibrator.launch.xml b/sensor_calibration_manager/launch/x2_gen2/tag_based_sfm_base_lidar_calibrator.launch.xml
new file mode 100644
index 00000000..1e59083d
--- /dev/null
+++ b/sensor_calibration_manager/launch/x2_gen2/tag_based_sfm_base_lidar_calibrator.launch.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/__init__.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/__init__.py
index 0d7af762..3c8926d7 100644
--- a/sensor_calibration_manager/sensor_calibration_manager/calibrators/__init__.py
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/__init__.py
@@ -2,5 +2,6 @@
from .rdv import * # noqa: F401, F403
from .x1 import * # noqa: F401, F403
from .x2 import * # noqa: F401, F403
+from .x2_gen2 import * # noqa: F401, F403
from .xx1 import * # noqa: F401, F403
from .xx1_15 import * # noqa: F401, F403
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/__init__.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/__init__.py
new file mode 100644
index 00000000..8548af73
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/__init__.py
@@ -0,0 +1,13 @@
+from .ground_plane_calibrator import GroundPlaneCalibrator
+from .mapping_based_lidar_lidar_calibrator import MappingBasedLidarLidarCalibrator
+from .marker_radar_lidar_calibrator import MarkerRadarLidarCalibrator
+from .tag_based_pnp_calibrator import TagBasedPNPCalibrator
+from .tag_based_sfm_base_lidar_calibrator import TagBasedSfmBaseLidarCalibrator
+
+__all__ = [
+ "TagBasedSfmBaseLidarCalibrator",
+ "GroundPlaneCalibrator",
+ "MappingBasedLidarLidarCalibrator",
+ "MarkerRadarLidarCalibrator",
+ "TagBasedPNPCalibrator",
+]
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/ground_plane_calibrator.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/ground_plane_calibrator.py
new file mode 100644
index 00000000..8834113d
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/ground_plane_calibrator.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 Tier IV, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict
+
+import numpy as np
+
+from sensor_calibration_manager.calibrator_base import CalibratorBase
+from sensor_calibration_manager.calibrator_registry import CalibratorRegistry
+from sensor_calibration_manager.ros_interface import RosInterface
+from sensor_calibration_manager.types import FramePair
+
+
+@CalibratorRegistry.register_calibrator(
+ project_name="x2_gen2", calibrator_name="ground_plane_calibrator"
+)
+class GroundPlaneCalibrator(CalibratorBase):
+ required_frames = []
+
+ def __init__(self, ros_interface: RosInterface, **kwargs):
+ super().__init__(ros_interface)
+
+ self.base_frame = "base_link"
+ self.sensor_kit_frame = "sensor_kit_base_link"
+ self.lidar_frame = "left_upper/lidar"
+
+ self.required_frames.extend([self.base_frame, self.sensor_kit_frame, self.lidar_frame])
+
+ self.add_calibrator(
+ service_name="calibrate_base_lidar",
+ expected_calibration_frames=[
+ FramePair(parent=self.base_frame, child=self.lidar_frame),
+ ],
+ )
+
+ def post_process(self, calibration_transforms: Dict[str, Dict[str, np.array]]):
+ base_to_lidar_transform = calibration_transforms[self.base_frame][self.lidar_frame]
+
+ sensor_kit_to_lidar_transform = self.get_transform_matrix(
+ self.sensor_kit_frame, self.lidar_frame
+ )
+
+ base_to_sensor_kit_transform = base_to_lidar_transform @ np.linalg.inv(
+ sensor_kit_to_lidar_transform
+ )
+
+ result = {self.base_frame: {self.sensor_kit_frame: base_to_sensor_kit_transform}}
+
+ return result
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/mapping_based_lidar_lidar_calibrator.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/mapping_based_lidar_lidar_calibrator.py
new file mode 100644
index 00000000..9f83bf73
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/mapping_based_lidar_lidar_calibrator.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 Tier IV, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict
+
+import numpy as np
+
+from sensor_calibration_manager.calibrator_base import CalibratorBase
+from sensor_calibration_manager.calibrator_registry import CalibratorRegistry
+from sensor_calibration_manager.ros_interface import RosInterface
+from sensor_calibration_manager.types import FramePair
+
+
+@CalibratorRegistry.register_calibrator(
+ project_name="x2_gen2", calibrator_name="mapping_based_lidar_lidar_calibrator"
+)
+class MappingBasedLidarLidarCalibrator(CalibratorBase):
+ required_frames = []
+
+ def __init__(self, ros_interface: RosInterface, **kwargs):
+ super().__init__(ros_interface)
+
+ self.sensor_kit_frame = "sensor_kit_base_link"
+
+ self.mapping_lidar_frame = "left_upper/lidar"
+ self.calibration_lidar_frames = [
+ "left_lower/lidar",
+ "right_upper/lidar",
+ "right_lower/lidar",
+ "front_upper/lidar",
+ "front_lower/lidar",
+ "rear_upper/lidar",
+ "rear_lower/lidar",
+ ]
+
+ self.calibration_base_lidar_frames = [
+ "left_lower/lidar_base_link",
+ "right_upper/lidar_base_link",
+ "right_lower/lidar_base_link",
+ "front_upper/lidar_base_link",
+ "front_lower/lidar_base_link",
+ "rear_upper/lidar_base_link",
+ "rear_lower/lidar_base_link",
+ ]
+
+ self.required_frames.extend(
+ [
+ self.sensor_kit_frame,
+ self.mapping_lidar_frame,
+ *self.calibration_lidar_frames,
+ *self.calibration_base_lidar_frames,
+ ]
+ )
+
+ self.add_calibrator(
+ service_name="calibrate_lidar_lidar",
+ expected_calibration_frames=[
+ FramePair(parent=self.mapping_lidar_frame, child=calibration_lidar_frame)
+ for calibration_lidar_frame in self.calibration_lidar_frames
+ ],
+ )
+
+ def post_process(self, calibration_transforms: Dict[str, Dict[str, np.array]]):
+ sensor_kit_to_lidar_transform = self.get_transform_matrix(
+ self.sensor_kit_frame, self.mapping_lidar_frame
+ )
+
+ calibration_lidar_to_base_lidar_transforms = [
+ self.get_transform_matrix(calibration_lidar_frame, calibration_base_lidar_frame)
+ for calibration_lidar_frame, calibration_base_lidar_frame in zip(
+ self.calibration_lidar_frames, self.calibration_base_lidar_frames
+ )
+ ]
+
+ sensor_kit_to_calibration_lidar_transforms = [
+ sensor_kit_to_lidar_transform
+ @ calibration_transforms[self.mapping_lidar_frame][calibration_lidar_frame]
+ @ calibration_lidar_to_base_lidar_transform
+ for calibration_lidar_frame, calibration_lidar_to_base_lidar_transform in zip(
+ self.calibration_lidar_frames, calibration_lidar_to_base_lidar_transforms
+ )
+ ]
+
+ result = {
+ self.sensor_kit_frame: {
+ calibration_base_lidar_frame: transform
+ for calibration_base_lidar_frame, transform in zip(
+ self.calibration_base_lidar_frames, sensor_kit_to_calibration_lidar_transforms
+ )
+ }
+ }
+
+ return result
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/marker_radar_lidar_calibrator.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/marker_radar_lidar_calibrator.py
new file mode 100644
index 00000000..21bfe419
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/marker_radar_lidar_calibrator.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 TIER IV, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict
+
+import numpy as np
+
+from sensor_calibration_manager.calibrator_base import CalibratorBase
+from sensor_calibration_manager.calibrator_registry import CalibratorRegistry
+from sensor_calibration_manager.ros_interface import RosInterface
+from sensor_calibration_manager.types import FramePair
+
+
+@CalibratorRegistry.register_calibrator(
+ project_name="x2_gen2", calibrator_name="marker_radar_lidar_calibrator"
+)
+class MarkerRadarLidarCalibrator(CalibratorBase):
+ required_frames = []
+
+ def __init__(self, ros_interface: RosInterface, **kwargs):
+ super().__init__(ros_interface)
+
+ self.radar_optimization_frame = kwargs["radar_optimization_frame"]
+ self.radar_parent_frame = kwargs["radar_parent_frame"]
+ self.radar_frame = kwargs["radar_frame"]
+ self.lidar_frame = kwargs["lidar_frame"]
+
+ if self.radar_optimization_frame == self.radar_parent_frame:
+ self.required_frames.extend(
+ [self.radar_optimization_frame, self.radar_frame, self.lidar_frame]
+ )
+ else:
+ self.required_frames.extend(
+ [
+ self.radar_optimization_frame,
+ self.radar_parent_frame,
+ self.radar_frame,
+ self.lidar_frame,
+ ]
+ )
+
+ self.add_calibrator(
+ service_name="calibrate_radar_lidar",
+ expected_calibration_frames=[
+ FramePair(parent=self.radar_frame, child=self.lidar_frame)
+ ],
+ )
+
+ def post_process(self, calibration_transforms: Dict[str, Dict[str, np.array]]):
+ lidar_to_radar_optimization_transform = self.get_transform_matrix(
+ self.lidar_frame, self.radar_optimization_frame
+ )
+
+ radar_optimization_to_radar_transform = np.linalg.inv(
+ calibration_transforms[self.radar_frame][self.lidar_frame]
+ @ lidar_to_radar_optimization_transform
+ )
+
+ if self.radar_optimization_frame == self.radar_parent_frame:
+ results = {
+ self.radar_optimization_frame: {
+ self.radar_frame: radar_optimization_to_radar_transform
+ }
+ }
+ else:
+ radar_parent_to_radar_optimization_transform = self.get_transform_matrix(
+ self.radar_parent_frame, self.radar_optimization_frame
+ )
+
+ radar_parent_to_radar_transform = (
+ radar_parent_to_radar_optimization_transform @ radar_optimization_to_radar_transform
+ )
+ results = {self.radar_parent_frame: {self.radar_frame: radar_parent_to_radar_transform}}
+
+ return results
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_pnp_calibrator.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_pnp_calibrator.py
new file mode 100644
index 00000000..60076c36
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_pnp_calibrator.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 Tier IV, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict
+
+import numpy as np
+
+from sensor_calibration_manager.calibrator_base import CalibratorBase
+from sensor_calibration_manager.calibrator_registry import CalibratorRegistry
+from sensor_calibration_manager.ros_interface import RosInterface
+from sensor_calibration_manager.types import FramePair
+
+
+@CalibratorRegistry.register_calibrator(
+ project_name="x2_gen2", calibrator_name="tag_based_pnp_calibrator"
+)
+class TagBasedPNPCalibrator(CalibratorBase):
+ required_frames = []
+
+ def __init__(self, ros_interface: RosInterface, **kwargs):
+ super().__init__(ros_interface)
+
+ self.camera_name = kwargs["camera_name"]
+ self.lidar_frame = kwargs["lidar_frame"]
+
+ camera_name_to_sensor_kit_frame = {
+ "camera0": "sensor_kit_base_link",
+ "camera1": "sensor_kit_base_link",
+ "camera2": "sensor_kit_base_link",
+ "camera3": "sensor_kit_base_link",
+ "camera4": "sensor_kit_base_link",
+ "camera5": "sensor_kit_base_link",
+ "camera6": "sensor_kit_base_link",
+ "camera7": "sensor_kit_base_link",
+ }
+
+ camera_name_to_frame = {
+ "camera0": "top_rear_center",
+ "camera1": "left_center",
+ "camera2": "right_center",
+ "camera3": "top_front_right",
+ "camera4": "top_front_center_right",
+ "camera5": "top_front_left",
+ "camera6": "left_front",
+ "camera7": "right_front",
+ }
+
+ self.sensor_kit_frame = camera_name_to_sensor_kit_frame[self.camera_name]
+ self.camera_frame = camera_name_to_frame[self.camera_name]
+
+ self.required_frames.extend(
+ [
+ self.lidar_frame,
+ self.sensor_kit_frame,
+ f"{self.camera_frame}/camera_link",
+ f"{self.camera_frame}/camera_optical_link",
+ ]
+ )
+
+ self.add_calibrator(
+ service_name="calibrate_camera_lidar",
+ expected_calibration_frames=[
+ FramePair(
+ parent=f"{self.camera_frame}/camera_optical_link", child=self.lidar_frame
+ ),
+ ],
+ )
+
+ def post_process(self, calibration_transforms: Dict[str, Dict[str, np.array]]):
+ optical_link_to_lidar_transform = calibration_transforms[
+ f"{self.camera_frame}/camera_optical_link"
+ ][self.lidar_frame]
+
+ sensor_kit_to_lidar_transform = self.get_transform_matrix(
+ self.sensor_kit_frame, self.lidar_frame
+ )
+
+ camera_to_optical_link_transform = self.get_transform_matrix(
+ f"{self.camera_frame}/camera_link", f"{self.camera_frame}/camera_optical_link"
+ )
+
+ sensor_kit_camera_link_transform = np.linalg.inv(
+ camera_to_optical_link_transform
+ @ optical_link_to_lidar_transform
+ @ np.linalg.inv(sensor_kit_to_lidar_transform)
+ )
+
+ result = {
+ self.sensor_kit_frame: {
+ f"{self.camera_frame}/camera_link": sensor_kit_camera_link_transform
+ }
+ }
+ return result
diff --git a/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_sfm_base_lidar_calibrator.py b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_sfm_base_lidar_calibrator.py
new file mode 100644
index 00000000..1b502c42
--- /dev/null
+++ b/sensor_calibration_manager/sensor_calibration_manager/calibrators/x2_gen2/tag_based_sfm_base_lidar_calibrator.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 TIER IV, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict
+
+import numpy as np
+
+from sensor_calibration_manager.calibrator_base import CalibratorBase
+from sensor_calibration_manager.calibrator_registry import CalibratorRegistry
+from sensor_calibration_manager.ros_interface import RosInterface
+from sensor_calibration_manager.types import FramePair
+
+
+@CalibratorRegistry.register_calibrator(
+ project_name="x2_gen2", calibrator_name="tag_based_sfm_base_lidar_calibrator"
+)
+class TagBasedSfmBaseLidarCalibrator(CalibratorBase):
+ required_frames = []
+
+ def __init__(self, ros_interface: RosInterface, **kwargs):
+ super().__init__(ros_interface)
+
+ self.base_frame = kwargs["base_frame"]
+ self.top_unit_frame = "sensor_kit_base_link"
+
+ self.main_sensor_frame = kwargs["main_calibration_sensor_frame"]
+
+ self.required_frames.extend([self.base_frame, self.top_unit_frame, self.main_sensor_frame])
+
+ self.add_calibrator(
+ service_name="calibrate_base_lidar",
+ expected_calibration_frames=[
+ FramePair(parent=self.main_sensor_frame, child=self.base_frame)
+ ],
+ )
+
+ def post_process(self, calibration_transforms: Dict[str, Dict[str, np.array]]):
+ sensor_kit_to_mapping_lidar_transform = self.get_transform_matrix(
+ self.top_unit_frame, self.main_sensor_frame
+ )
+
+ base_to_top_sensor_kit_transform = np.linalg.inv(
+ sensor_kit_to_mapping_lidar_transform
+ @ calibration_transforms[self.main_sensor_frame][self.base_frame]
+ )
+ results = {self.base_frame: {self.top_unit_frame: base_to_top_sensor_kit_transform}}
+
+ return results