feat: add NAV2 perception3D module
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
build/
|
||||||
|
install/
|
||||||
|
log/
|
||||||
|
.colcon/
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODULE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
RUNTIME_ROOT="${AUTONOMY_STUDIO_RUNTIME_ROOT:-$HOME/autonomy-studio/runtime/nav2}"
|
||||||
|
exec bash "$RUNTIME_ROOT/docker/docker_build_nav2.sh" "$(dirname "$MODULE_PATH")" "$(basename "$MODULE_PATH")" "${NAV2_DOCKER_IMAGE:-future-nav2-gazebo:humble}" "${ROS_DOMAIN_ID:-0}"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODULE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
RUNTIME_ROOT="${AUTONOMY_STUDIO_RUNTIME_ROOT:-$HOME/autonomy-studio/runtime/nav2}"
|
||||||
|
|
||||||
|
exec bash "$RUNTIME_ROOT/docker/docker_run_nav2.sh" \
|
||||||
|
"lms-nav2-perception3d" \
|
||||||
|
"$(dirname "$MODULE_PATH")" \
|
||||||
|
"$(basename "$MODULE_PATH")" \
|
||||||
|
"${NAV2_DOCKER_IMAGE:-future-nav2:humble}" \
|
||||||
|
"${ROS_DOMAIN_ID:-0}" \
|
||||||
|
sim_mode:=false robot:=mkmini
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
|
odometry:
|
||||||
|
ros__parameters:
|
||||||
|
odom_topic: "/vehicle_odom"
|
||||||
|
odom_frame: "odom"
|
||||||
|
base_frame: "base_link"
|
||||||
|
|
||||||
|
pcd_crop:
|
||||||
|
ros__parameters:
|
||||||
|
input_topic: "/raw/lidar"
|
||||||
|
output_topic: "/lidar_filtered"
|
||||||
|
min_z: -5.0
|
||||||
|
max_z: 0.5
|
||||||
|
# min_z: -10.0
|
||||||
|
# max_z: 10.0
|
||||||
|
min_radius: 1.0
|
||||||
|
max_radius: 50.0
|
||||||
|
# min_angle: -1.570796
|
||||||
|
# max_angle: 2.356194
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
odometry:
|
||||||
|
ros__parameters:
|
||||||
|
odom_topic: "/vehicle_odom"
|
||||||
|
odom_frame: "odom"
|
||||||
|
base_frame: "base_link"
|
||||||
|
|
||||||
|
pcd_crop:
|
||||||
|
ros__parameters:
|
||||||
|
input_topic: "/raw/lidar"
|
||||||
|
output_topic: "/lidar_filtered"
|
||||||
|
min_z: -2.5
|
||||||
|
max_z: 1.5
|
||||||
|
min_radius: 2.0
|
||||||
|
max_radius: 30.0
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
lidar:
|
||||||
|
x: 0.456
|
||||||
|
y: 0.0
|
||||||
|
z: 1.135
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_lidar
|
||||||
|
|
||||||
|
imu:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.5
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_imu
|
||||||
|
|
||||||
|
rgb_front:
|
||||||
|
x: 0.56331
|
||||||
|
y: 0.56343
|
||||||
|
z: 0.80703
|
||||||
|
roll: -0.025
|
||||||
|
pitch: 0.315
|
||||||
|
yaw: 1.035
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: hero/rgb_front
|
||||||
|
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
lidar:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.03618
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 2.356194
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_lidar
|
||||||
|
|
||||||
|
imu:
|
||||||
|
x: 0.006253
|
||||||
|
y: -0.011775
|
||||||
|
z: 0.007645
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_imu
|
||||||
|
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
lidar:
|
||||||
|
x: 0.456
|
||||||
|
y: 0.0
|
||||||
|
z: 1.135
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_lidar
|
||||||
|
|
||||||
|
imu:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.5
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_imu
|
||||||
|
|
||||||
|
rgb_front:
|
||||||
|
x: 0.56331
|
||||||
|
y: 0.56343
|
||||||
|
z: 0.80703
|
||||||
|
roll: -0.025
|
||||||
|
pitch: 0.315
|
||||||
|
yaw: 1.035
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: hero/rgb_front
|
||||||
|
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
lidar:
|
||||||
|
x: 0.1
|
||||||
|
y: 0.0
|
||||||
|
z: 0.6
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_lidar
|
||||||
|
|
||||||
|
imu:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.5
|
||||||
|
roll: 0.0
|
||||||
|
pitch: 0.0
|
||||||
|
yaw: 0.0
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: os_imu
|
||||||
|
|
||||||
|
rgb_front:
|
||||||
|
x: 0.10731
|
||||||
|
y: 0.56343
|
||||||
|
z: -0.27697
|
||||||
|
roll: -0.025
|
||||||
|
pitch: 0.315
|
||||||
|
yaw: 1.035
|
||||||
|
frame_id: base_link
|
||||||
|
child_frame_id: hero/rgb_front
|
||||||
|
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
from ament_index_python.packages import get_package_share_directory
|
||||||
|
|
||||||
|
from launch import LaunchDescription
|
||||||
|
from launch.actions import DeclareLaunchArgument
|
||||||
|
from launch.actions import GroupAction
|
||||||
|
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
|
||||||
|
from launch_ros.actions import Node
|
||||||
|
from launch_ros.descriptions import ParameterFile
|
||||||
|
from nav2_common.launch import RewrittenYaml
|
||||||
|
from launch.actions import OpaqueFunction
|
||||||
|
from launch.substitutions import PythonExpression
|
||||||
|
from launch.conditions import IfCondition
|
||||||
|
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
ARGUMENTS = [
|
||||||
|
DeclareLaunchArgument('use_sim_time', default_value='true'),
|
||||||
|
DeclareLaunchArgument('sim_mode', default_value='true'),
|
||||||
|
DeclareLaunchArgument('robot', default_value='fr09', choices=['mkmini', 'fr09']),
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate_launch_description():
|
||||||
|
use_sim_time = LaunchConfiguration('use_sim_time')
|
||||||
|
robot = LaunchConfiguration('robot')
|
||||||
|
|
||||||
|
remappings = [('/tf', 'tf'),
|
||||||
|
('/tf_static', 'tf_static')]
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
pkg_perception = get_package_share_directory(
|
||||||
|
'perception3d_pkg')
|
||||||
|
|
||||||
|
params_file = PathJoinSubstitution([
|
||||||
|
pkg_perception,
|
||||||
|
'config',
|
||||||
|
PythonExpression(["'3d_' + '", robot, "' + '.yaml'"])])
|
||||||
|
param_substitutions = {
|
||||||
|
'use_sim_time': use_sim_time,
|
||||||
|
}
|
||||||
|
configured_params = ParameterFile(
|
||||||
|
RewrittenYaml(
|
||||||
|
source_file=params_file,
|
||||||
|
param_rewrites=param_substitutions,
|
||||||
|
convert_types=True),
|
||||||
|
allow_substs=True)
|
||||||
|
|
||||||
|
load_nodes = GroupAction(
|
||||||
|
actions=[
|
||||||
|
Node(
|
||||||
|
package='perception3d_pkg',
|
||||||
|
executable='pcd_crop',
|
||||||
|
name='pcd_crop',
|
||||||
|
output='screen',
|
||||||
|
parameters=[configured_params],
|
||||||
|
remappings=remappings
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create launch description and add actions
|
||||||
|
ld = LaunchDescription(ARGUMENTS)
|
||||||
|
ld.add_action(load_nodes)
|
||||||
|
|
||||||
|
return ld
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<package format="3">
|
||||||
|
<name>perception3d_pkg</name>
|
||||||
|
<version>0.0.0</version>
|
||||||
|
<description>TODO: Package description</description>
|
||||||
|
<maintainer email="jang1future@gmail.com">dgist</maintainer>
|
||||||
|
<license>TODO: License declaration</license>
|
||||||
|
|
||||||
|
<test_depend>ament_copyright</test_depend>
|
||||||
|
<test_depend>ament_flake8</test_depend>
|
||||||
|
<test_depend>ament_pep257</test_depend>
|
||||||
|
<test_depend>python3-pytest</test_depend>
|
||||||
|
|
||||||
|
<export>
|
||||||
|
<build_type>ament_python</build_type>
|
||||||
|
</export>
|
||||||
|
</package>
|
||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import math
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
|
||||||
|
from std_msgs.msg import Float32
|
||||||
|
from sensor_msgs.msg import Imu
|
||||||
|
from rosgraph_msgs.msg import Clock
|
||||||
|
from nav_msgs.msg import Odometry
|
||||||
|
from geometry_msgs.msg import Quaternion, TransformStamped
|
||||||
|
from tf2_ros import TransformBroadcaster
|
||||||
|
|
||||||
|
|
||||||
|
def quat_to_yaw(x: float, y: float, z: float, w: float) -> float:
|
||||||
|
siny_cosp = 2.0 * (w * z + x * y)
|
||||||
|
cosy_cosp = 1.0 - 2.0 * (y * y + z * z)
|
||||||
|
return math.atan2(siny_cosp, cosy_cosp)
|
||||||
|
|
||||||
|
|
||||||
|
def yaw_to_quaternion(yaw: float) -> Quaternion:
|
||||||
|
q = Quaternion()
|
||||||
|
q.x = 0.0
|
||||||
|
q.y = 0.0
|
||||||
|
q.z = math.sin(yaw * 0.5)
|
||||||
|
q.w = math.cos(yaw * 0.5)
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_angle(angle: float) -> float:
|
||||||
|
while angle > math.pi:
|
||||||
|
angle -= 2.0 * math.pi
|
||||||
|
while angle < -math.pi:
|
||||||
|
angle += 2.0 * math.pi
|
||||||
|
return angle
|
||||||
|
|
||||||
|
|
||||||
|
def stamp_to_sec(stamp) -> float:
|
||||||
|
return float(stamp.sec) + float(stamp.nanosec) * 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
class SpeedImuOdomNode(Node):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('speed_imu_odom_node')
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
self.declare_parameter('odom_topic', '/vehicle_odom')
|
||||||
|
self.declare_parameter('odom_frame', 'odom')
|
||||||
|
self.declare_parameter('base_frame', 'base_link')
|
||||||
|
|
||||||
|
self.odom_topic = self.get_parameter('odom_topic').value
|
||||||
|
self.odom_frame = self.get_parameter('odom_frame').value
|
||||||
|
self.base_frame = self.get_parameter('base_frame').value
|
||||||
|
|
||||||
|
self.declare_parameter('pose_cov_x', 0.5)
|
||||||
|
self.declare_parameter('pose_cov_y', 0.5)
|
||||||
|
self.declare_parameter('pose_cov_yaw', 0.2)
|
||||||
|
self.declare_parameter('twist_cov_vx', 0.2)
|
||||||
|
self.declare_parameter('twist_cov_vy', 0.2)
|
||||||
|
self.declare_parameter('twist_cov_wz', 0.1)
|
||||||
|
|
||||||
|
self.pose_cov_x = self.get_parameter('pose_cov_x').get_parameter_value().double_value
|
||||||
|
self.pose_cov_y = self.get_parameter('pose_cov_y').get_parameter_value().double_value
|
||||||
|
self.pose_cov_yaw = self.get_parameter('pose_cov_yaw').get_parameter_value().double_value
|
||||||
|
self.twist_cov_vx = self.get_parameter('twist_cov_vx').get_parameter_value().double_value
|
||||||
|
self.twist_cov_vy = self.get_parameter('twist_cov_vy').get_parameter_value().double_value
|
||||||
|
self.twist_cov_wz = self.get_parameter('twist_cov_wz').get_parameter_value().double_value
|
||||||
|
|
||||||
|
self.x = 0.0
|
||||||
|
self.y = 0.0
|
||||||
|
self.yaw = 0.0
|
||||||
|
|
||||||
|
self.max_dt = 0.1
|
||||||
|
|
||||||
|
# State
|
||||||
|
self.speed_mps: float = 0.0
|
||||||
|
self.current_yaw: Optional[float] = None
|
||||||
|
self.last_yaw: Optional[float] = None
|
||||||
|
self.last_pub_time: Optional[float] = None
|
||||||
|
|
||||||
|
self.x = 0.0
|
||||||
|
self.y = 0.0
|
||||||
|
|
||||||
|
self.has_speed = False
|
||||||
|
self.has_imu = False
|
||||||
|
|
||||||
|
self.ground_truth_odom: Optional[Odometry] = None
|
||||||
|
|
||||||
|
# Pub/Sub
|
||||||
|
self.odom_pub = self.create_publisher(Odometry, self.odom_topic, 50)
|
||||||
|
self.tf_broadcaster = TransformBroadcaster(self)
|
||||||
|
|
||||||
|
self.create_subscription(Float32, '/raw/speed', self.speed_cb, 50)
|
||||||
|
self.create_subscription(Imu, '/raw/imu', self.imu_cb, 50)
|
||||||
|
|
||||||
|
self.get_logger().info('SpeedImuOdomNode started')
|
||||||
|
self.get_logger().info(f'odom_topic: {self.odom_topic}')
|
||||||
|
|
||||||
|
def speed_cb(self, msg: Float32):
|
||||||
|
self.speed_mps = float(msg.data)
|
||||||
|
self.has_speed = True
|
||||||
|
|
||||||
|
def imu_cb(self, msg: Imu):
|
||||||
|
self.has_imu = True
|
||||||
|
|
||||||
|
# speed가 아직 없으면 선택적으로 skip
|
||||||
|
if not self.has_speed:
|
||||||
|
return
|
||||||
|
|
||||||
|
# IMU timestamp 우선 사용
|
||||||
|
stamp = msg.header.stamp
|
||||||
|
now_sec = stamp_to_sec(stamp)
|
||||||
|
|
||||||
|
omega = float(msg.angular_velocity.z)
|
||||||
|
|
||||||
|
if self.last_pub_time is None:
|
||||||
|
self.last_pub_time = now_sec
|
||||||
|
self.current_yaw = self.yaw
|
||||||
|
self.last_yaw = self.yaw
|
||||||
|
# 첫 샘플은 dt가 없으므로 현재 yaw로 publish
|
||||||
|
self.publish_odom(stamp, self.yaw, self.speed_mps, omega)
|
||||||
|
return
|
||||||
|
|
||||||
|
dt = now_sec - self.last_pub_time
|
||||||
|
if dt <= 0.0:
|
||||||
|
# 시간 역전/동일시간이면 적분하지 않고 현재 yaw만 publish
|
||||||
|
self.publish_odom(stamp, self.yaw, self.speed_mps, omega)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 긴 공백은 과적분 방지
|
||||||
|
if dt > self.max_dt:
|
||||||
|
dt = self.max_dt
|
||||||
|
|
||||||
|
# angular velocity integration
|
||||||
|
self.yaw += omega * dt
|
||||||
|
self.yaw = normalize_angle(self.yaw)
|
||||||
|
|
||||||
|
# position integration
|
||||||
|
self.x += self.speed_mps * math.cos(self.yaw) * dt
|
||||||
|
self.y += self.speed_mps * math.sin(self.yaw) * dt
|
||||||
|
|
||||||
|
self.current_yaw = self.yaw
|
||||||
|
self.last_yaw = self.yaw
|
||||||
|
self.last_pub_time = now_sec
|
||||||
|
|
||||||
|
self.publish_odom(stamp, self.yaw, self.speed_mps, omega)
|
||||||
|
|
||||||
|
def publish_odom(self, stamp, yaw: float, v: float, omega: float):
|
||||||
|
odom = Odometry()
|
||||||
|
odom.header.stamp = stamp
|
||||||
|
odom.header.frame_id = self.odom_frame
|
||||||
|
odom.child_frame_id = self.base_frame
|
||||||
|
|
||||||
|
odom.pose.pose.position.x = self.x
|
||||||
|
odom.pose.pose.position.y = self.y
|
||||||
|
odom.pose.pose.position.z = 0.0
|
||||||
|
odom.pose.pose.orientation = yaw_to_quaternion(yaw)
|
||||||
|
|
||||||
|
odom.twist.twist.linear.x = v
|
||||||
|
odom.twist.twist.linear.y = 0.0
|
||||||
|
odom.twist.twist.linear.z = 0.0
|
||||||
|
odom.twist.twist.angular.x = 0.0
|
||||||
|
odom.twist.twist.angular.y = 0.0
|
||||||
|
odom.twist.twist.angular.z = omega
|
||||||
|
|
||||||
|
odom.pose.covariance = [
|
||||||
|
self.pose_cov_x, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, self.pose_cov_y, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 999.0,0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 999.0,0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 999.0,0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0, self.pose_cov_yaw
|
||||||
|
]
|
||||||
|
|
||||||
|
odom.twist.covariance = [
|
||||||
|
self.twist_cov_vx, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, self.twist_cov_vy, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 999.0,0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 999.0,0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 999.0,0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0, self.twist_cov_wz
|
||||||
|
]
|
||||||
|
|
||||||
|
self.odom_pub.publish(odom)
|
||||||
|
|
||||||
|
tf_msg = TransformStamped()
|
||||||
|
tf_msg.header.stamp = stamp
|
||||||
|
tf_msg.header.frame_id = self.odom_frame
|
||||||
|
tf_msg.child_frame_id = self.base_frame
|
||||||
|
tf_msg.transform.translation.x = self.x
|
||||||
|
tf_msg.transform.translation.y = self.y
|
||||||
|
tf_msg.transform.translation.z = 0.0
|
||||||
|
tf_msg.transform.rotation = yaw_to_quaternion(yaw)
|
||||||
|
self.tf_broadcaster.sendTransform(tf_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
rclpy.init(args=args)
|
||||||
|
node = SpeedImuOdomNode()
|
||||||
|
try:
|
||||||
|
rclpy.spin(node)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
|
||||||
|
from sensor_msgs.msg import PointCloud2
|
||||||
|
import sensor_msgs_py.point_cloud2 as pc2
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class PointCloudCrop(Node):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('pcd_crop')
|
||||||
|
|
||||||
|
self.declare_parameter('min_z', -2.5)
|
||||||
|
self.declare_parameter('max_z', -0.5)
|
||||||
|
self.declare_parameter('min_radius', 5.0) # 너무 가까운 점 제거
|
||||||
|
self.declare_parameter('max_radius', 30.0) # 너무 먼 점 제거
|
||||||
|
self.declare_parameter('input_topic', '/raw/lidar')
|
||||||
|
self.declare_parameter('output_topic', '/lidar_filtered')
|
||||||
|
|
||||||
|
self.min_z = self.get_parameter('min_z').value
|
||||||
|
self.max_z = self.get_parameter('max_z').value
|
||||||
|
self.min_radius = self.get_parameter('min_radius').value
|
||||||
|
self.max_radius = self.get_parameter('max_radius').value
|
||||||
|
|
||||||
|
input_topic = self.get_parameter('input_topic').value
|
||||||
|
output_topic = self.get_parameter('output_topic').value
|
||||||
|
|
||||||
|
self.declare_parameter('min_angle', (-np.pi)) # rad
|
||||||
|
self.declare_parameter('max_angle', (np.pi)) # rad
|
||||||
|
|
||||||
|
self.min_angle = self.get_parameter('min_angle').value
|
||||||
|
self.max_angle = self.get_parameter('max_angle').value
|
||||||
|
|
||||||
|
self.sub = self.create_subscription(
|
||||||
|
PointCloud2,
|
||||||
|
input_topic,
|
||||||
|
self.callback,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pub = self.create_publisher(
|
||||||
|
PointCloud2,
|
||||||
|
output_topic,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_logger().info(
|
||||||
|
f"Crop started: Z[{self.min_z}, {self.max_z}] "
|
||||||
|
f"Radius[{self.min_radius}, {self.max_radius}]"
|
||||||
|
f"Angle[{self.min_angle}, {self.max_angle}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def callback(self, msg: PointCloud2):
|
||||||
|
pts = pc2.read_points(
|
||||||
|
msg,
|
||||||
|
field_names=("x", "y", "z"),
|
||||||
|
skip_nans=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if pts.shape[0] == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# structured array -> 일반 (N,3) float32 배열
|
||||||
|
points = np.stack(
|
||||||
|
(pts["x"], pts["y"], pts["z"]), axis=-1).astype(np.float32, copy=False)
|
||||||
|
|
||||||
|
x = points[:, 0]
|
||||||
|
y = points[:, 1]
|
||||||
|
z = points[:, 2]
|
||||||
|
|
||||||
|
min_r2 = self.min_radius * self.min_radius
|
||||||
|
max_r2 = self.max_radius * self.max_radius
|
||||||
|
r2 = x * x + y * y
|
||||||
|
|
||||||
|
angles = np.arctan2(-y, -x)
|
||||||
|
|
||||||
|
if self.min_angle < self.max_angle:
|
||||||
|
angle_mask = (angles >= self.min_angle) & (angles <= self.max_angle)
|
||||||
|
else:
|
||||||
|
angle_mask = (angles >= self.min_angle) | (angles <= self.max_angle)
|
||||||
|
|
||||||
|
mask = (
|
||||||
|
(z >= self.min_z) &
|
||||||
|
(z <= self.max_z) &
|
||||||
|
(r2 >= min_r2) &
|
||||||
|
(r2 <= max_r2) &
|
||||||
|
angle_mask
|
||||||
|
)
|
||||||
|
|
||||||
|
filtered = points[mask]
|
||||||
|
|
||||||
|
if filtered.shape[0] == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
filtered_msg = pc2.create_cloud_xyz32(msg.header, filtered.tolist())
|
||||||
|
self.pub.publish(filtered_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
rclpy.init(args=args)
|
||||||
|
node = PointCloudCrop()
|
||||||
|
rclpy.spin(node)
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print('Hi from perception3d_pkg.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
[develop]
|
||||||
|
script_dir=$base/lib/perception3d_pkg
|
||||||
|
[install]
|
||||||
|
install_scripts=$base/lib/perception3d_pkg
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
from glob import glob
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
package_name = 'perception3d_pkg'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=package_name,
|
||||||
|
version='0.0.0',
|
||||||
|
packages=find_packages(exclude=['test']),
|
||||||
|
data_files=[
|
||||||
|
('share/ament_index/resource_index/packages',
|
||||||
|
['resource/' + package_name]),
|
||||||
|
('share/' + package_name, ['package.xml']),
|
||||||
|
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
|
||||||
|
(os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
|
||||||
|
],
|
||||||
|
install_requires=['setuptools'],
|
||||||
|
zip_safe=True,
|
||||||
|
maintainer='dgist',
|
||||||
|
maintainer_email='jang1future@gmail.com',
|
||||||
|
description='TODO: Package description',
|
||||||
|
license='TODO: License declaration',
|
||||||
|
extras_require={
|
||||||
|
'test': [
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'test = perception3d_pkg.test:main',
|
||||||
|
'pcd_crop = perception3d_pkg.pcd_crop:main'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2015 Open Source Robotics Foundation, 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 ament_copyright.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
# Remove the `skip` decorator once the source file(s) have a copyright header
|
||||||
|
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
|
||||||
|
@pytest.mark.copyright
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_copyright():
|
||||||
|
rc = main(argv=['.', 'test'])
|
||||||
|
assert rc == 0, 'Found errors'
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2017 Open Source Robotics Foundation, 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 ament_flake8.main import main_with_errors
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.flake8
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_flake8():
|
||||||
|
rc, errors = main_with_errors(argv=[])
|
||||||
|
assert rc == 0, \
|
||||||
|
'Found %d code style errors / warnings:\n' % len(errors) + \
|
||||||
|
'\n'.join(errors)
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2015 Open Source Robotics Foundation, 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 ament_pep257.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.linter
|
||||||
|
@pytest.mark.pep257
|
||||||
|
def test_pep257():
|
||||||
|
rc = main(argv=['.', 'test'])
|
||||||
|
assert rc == 0, 'Found code style errors / warnings'
|
||||||
Reference in New Issue
Block a user