Lidar now reads from pcap with multiple remotes

This commit is contained in:
Ruben van de Ven 2025-11-05 10:47:15 +01:00
parent f925f1e31a
commit c4ef961a78
2 changed files with 113 additions and 39 deletions

View file

@ -26,10 +26,11 @@ autostart=false
# command=uv run trap_video_source --homography ../DATASETS/hof3/homography.json --video-src ../DATASETS/hof3/hof3-cam-demo-twoperson.mp4 --calibration ../DATASETS/hof3/calibration.json --video-loop # command=uv run trap_video_source --homography ../DATASETS/hof3/homography.json --video-src ../DATASETS/hof3/hof3-cam-demo-twoperson.mp4 --calibration ../DATASETS/hof3/calibration.json --video-loop
command=uv run trap_video_source --homography ../DATASETS/hof3-cam-baumer-cropped/homography.json --video-src gige://../DATASETS/hof3-cam-baumer-cropped/gige_config.json --calibration ../DATASETS/hof3-cam-baumer-cropped/calibration.json command=uv run trap_video_source --homography ../DATASETS/hof3-cam-baumer-cropped/homography.json --video-src gige://../DATASETS/hof3-cam-baumer-cropped/gige_config.json --calibration ../DATASETS/hof3-cam-baumer-cropped/calibration.json
directory=%(here)s directory=%(here)s
directory=%(here)s
[program:tracker] [program:tracker]
command=uv run trap_tracker --smooth-tracks # command=uv run trap_tracker --smooth-tracks
command=uv run trap_lidar --min-box-area 0 --viz --smooth-tracks
environment=DISPLAY=":0"
directory=%(here)s directory=%(here)s
[program:stage] [program:stage]
@ -37,6 +38,12 @@ directory=%(here)s
command=uv run trap_stage --verbose --camera-fps 12 --homography ../DATASETS/hof3/homography.json --calibration ../DATASETS/hof3/calibration.json --cache-path /tmp/history_cache-hof3.pcl --tracker-output-dir EXPERIMENTS/raw/hof3/ command=uv run trap_stage --verbose --camera-fps 12 --homography ../DATASETS/hof3/homography.json --calibration ../DATASETS/hof3/calibration.json --cache-path /tmp/history_cache-hof3.pcl --tracker-output-dir EXPERIMENTS/raw/hof3/
directory=%(here)s directory=%(here)s
[program:settings]
command=uv run trap_settings
autostart=true
environment=DISPLAY=":0"
directory=%(here)s
[program:predictor] [program:predictor]
command=uv run trap_prediction --eval_device cuda:0 --model_dir EXPERIMENTS/models/models_20241229_21_35_13_hof3-m2-ud-split-conv12-f2.0-map-2024-12-29/ --num-samples 1 --map_encoding --eval_data_dict EXPERIMENTS/trajectron-data/hof3-m2-ud-split-nostep-conv12-f2.0-map-2024-12-29_val.pkl --prediction-horizon 120 --gmm-mode True --z-mode command=uv run trap_prediction --eval_device cuda:0 --model_dir EXPERIMENTS/models/models_20241229_21_35_13_hof3-m2-ud-split-conv12-f2.0-map-2024-12-29/ --num-samples 1 --map_encoding --eval_data_dict EXPERIMENTS/trajectron-data/hof3-m2-ud-split-nostep-conv12-f2.0-map-2024-12-29_val.pkl --prediction-horizon 120 --gmm-mode True --z-mode

View file

@ -5,6 +5,7 @@ from pathlib import Path
import select import select
import socket import socket
from typing import DefaultDict, Dict, List from typing import DefaultDict, Dict, List
import dpkt
import numpy as np import numpy as np
import open3d as o3d import open3d as o3d
import shapely import shapely
@ -19,6 +20,8 @@ from trap.node import Node
from bytetracker import BYTETracker from bytetracker import BYTETracker
import velodyne_decoder as vd import velodyne_decoder as vd
from trap.tracker import Smoother
logger = logging.getLogger('lidar') logger = logging.getLogger('lidar')
# import sort # import sort
@ -27,7 +30,82 @@ logger = logging.getLogger('lidar')
# This is what is used by velodyne_decoder internally on read_pcap: # This is what is used by velodyne_decoder internally on read_pcap:
ResultTuple = namedtuple("StampCloudTuple", ("stamp", "points")) ResultTuple = namedtuple("StampCloudTuple", ("stamp", "points"))
# In bag reader this is: Result = namedtuple("ResultTuple", ("stamp", "points", "topic", "frame_id"))
def read_pcap(pcap_file, config, as_pcl_structs=False):
"""
Adapted from velodyne_decoder to handle multiple streams
"""
remotes = []
decoders = defaultdict(lambda: vd.StreamDecoder(config))
with open(pcap_file, 'rb') as f:
pcap = dpkt.pcap.Reader(f)
latest_result = {}
start_t = time.perf_counter()
start_timestamp, _ = next(pcap)
prev_data = bytes()
for timestamp, packet in pcap:
# first we need to figure out when to yield the data. Simulating exhausting the socket
# this should happen when the currently reading timestamp > the wall clock timediff
now_t = time.perf_counter()
time_passed = now_t - start_t
pkg_time_passed = timestamp - start_timestamp
if pkg_time_passed > time_passed:
if len(latest_result):
# return data if available
yield latest_result
# now delay processing the package until it should 'arrive'
# but, time may have passed after returning from the yield:
time_passed = time.perf_counter() - start_t
time.sleep(max(0, pkg_time_passed - time_passed))
latest_result = {}
try:
eth = dpkt.ethernet.Ethernet(packet)
if not isinstance(eth.data, dpkt.ip.IP):
continue
ip = eth.data
if not isinstance(ip.data, dpkt.udp.UDP):
continue
udp = ip.data
src_ip = socket.inet_ntoa(ip.src)
src_port = udp.sport
data = udp.data
if len(data) != vd.PACKET_SIZE:
# other udp data
continue
if src_ip not in remotes:
logger.info(f"New remote {src_ip}")
remotes.append(src_ip)
result = decoders[src_ip].decode(timestamp, data, as_pcl_structs)
if result is not None:
latest_result[src_ip] = ResultTuple(*result)
except Exception as e:
logger.warning(f"Error processing packet: {e}")
# like velodyne_decoder.read_pcap but with live streamed data # like velodyne_decoder.read_pcap but with live streamed data
@ -44,7 +122,6 @@ def read_live_data(ip, port, config, as_pcl_structs=False):
remotes = [] remotes = []
while True: while True:
readable, _, _ = select.select([sock], [], [], 1.0) readable, _, _ = select.select([sock], [], [], 1.0)
latest_result = {} latest_result = {}
@ -121,19 +198,9 @@ def filter_pcd_points_below_z(pcd: o3d.geometry.PointCloud, z_threshold: float)
def filter_pcd_points_outside_bounds(pcd: o3d.geometry.PointCloud, polygon: shapely.Polygon) -> o3d.geometry.PointCloud:
points = np.asarray(pcd.points)
geoms = np.array([shapely.Point(p[0], p[1]) for p in points])
inside_idxs = shapely.contains(polygon, geoms)
return pcd.select_by_index(np.where(inside_idxs)[0])
class Lidar(Node): class Lidar(Node):
def setup(self): def setup(self):
if self.config.smooth_tracks:
self.logger.warning("Smoothing not yet implemented")
self.track_sock = self.pub(self.config.zmq_trajectory_addr) self.track_sock = self.pub(self.config.zmq_trajectory_addr)
self.detection_sock = self.pub(self.config.zmq_detection_addr) self.detection_sock = self.pub(self.config.zmq_detection_addr)
@ -142,8 +209,10 @@ class Lidar(Node):
if self.config.pcap and self.config.pcap.exists(): if self.config.pcap and self.config.pcap.exists():
# self.sequence_generator = vd.read_pcap(self.config.pcap, config, time_range=[1761139039+100,None]) # self.sequence_generator = vd.read_pcap(self.config.pcap, config, time_range=[1761139039+100,None])
self.sequence_generator = vd.read_pcap(self.config.pcap, config, time_range=[1761139039+30,None]) # read_pcap(self.config.pcap)
# self.sequence_generator = vd.read_pcap(self.config.pcap, config) # self.sequence_generator = vd.read_pcap(self.config.pcap, config)
self.sequence_generator = read_pcap(self.config.pcap, config)
else: else:
self.sequence_generator = read_live_data(self.config.ip, self.config.port, config) self.sequence_generator = read_live_data(self.config.ip, self.config.port, config)
@ -175,15 +244,20 @@ class Lidar(Node):
self.map_outline_volume = o3d.visualization.SelectionPolygonVolume() self.map_outline_volume = o3d.visualization.SelectionPolygonVolume()
self.map_outline_volume.orthogonal_axis = "Z" # extrusion direction of polygon self.map_outline_volume.orthogonal_axis = "Z" # extrusion direction of polygon
self.map_outline_volume.axis_min = 0 self.map_outline_volume.axis_min = .3 # filter from slightly above ground
self.map_outline_volume.axis_max = 3.5 self.map_outline_volume.axis_max = 2.2
polygon_points =np.array([[*p.position, 0] for p in lines[0].points]) polygon_points =np.array([[*p.position, 0] for p in lines[0].points])
self.map_outline_volume.bounding_polygon = o3d.utility.Vector3dVector(polygon_points) self.map_outline_volume.bounding_polygon = o3d.utility.Vector3dVector(polygon_points)
if self.config.smooth_tracks:
fps = 12 # TODO)) make configurable, or get from cam
logger.info(f"Smoother enabled, assuming {fps} fps")
self.smoother = Smoother(window_len=fps*5, convolution=False)
else:
logger.info("Smoother Disabled (enable with --smooth-tracks)")
self.remotes = {} self.remotes = {}
@ -289,8 +363,16 @@ class Lidar(Node):
frame_idx += 1 frame_idx += 1
lidar_items = next(self.sequence_generator) lidar_items = next(self.sequence_generator)
# remove some keys. Don't use dict comprehension to not copy the data unnecesarrily
remove_keys = [ip for ip in lidar_items.keys() if not self.get_setting(f"lidar.{self.ip_to_name(ip)}.enabled", True)]
for key in remove_keys:
del lidar_items[key]
current_pcd = self.build_cloud(lidar_items) current_pcd = self.build_cloud(lidar_items)
# current_pcd = filter_pcd_points_below_z(current_pcd, .1) # current_pcd = filter_pcd_points_below_z(current_pcd, .1)
# current_pcd = filter_raw_points_by_distance(current_pcd, 10) # current_pcd = filter_raw_points_by_distance(current_pcd, 10)
@ -344,7 +426,7 @@ class Lidar(Node):
stat_static = len(filtered_pcd.points) stat_static = len(filtered_pcd.points)
counter.set('lidar.unstatic', stat_static) counter.set('lidar.unstatic', stat_static)
if self.room_filter.initialised: if self.room_filter.initialised and self.get_setting('lidar.tracking_enabled',True):
# filtered_pcd, _ = filtered_pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) # filtered_pcd, _ = filtered_pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
denoised_pcd, inlier_indexes = filtered_pcd.remove_radius_outlier(nb_points=5, radius=0.8) denoised_pcd, inlier_indexes = filtered_pcd.remove_radius_outlier(nb_points=5, radius=0.8)
stat_denoise = len(filtered_pcd.points) stat_denoise = len(filtered_pcd.points)
@ -359,7 +441,7 @@ class Lidar(Node):
filtered_pcd = denoised_pcd filtered_pcd = denoised_pcd
# down sample # down sample
filtered_pcd = filtered_pcd.voxel_down_sample(voxel_size=0.01) filtered_pcd = filtered_pcd.voxel_down_sample(voxel_size=0.08)
stat_downsampled = len(filtered_pcd.points) stat_downsampled = len(filtered_pcd.points)
@ -369,19 +451,11 @@ class Lidar(Node):
counter.set('lidar.downsampled', stat_downsampled) counter.set('lidar.downsampled', stat_downsampled)
# print(f"in: {stat_orig} / crop: {stat_crop} / static object removal: {stat_static} / outlier removal: {stat_denoise} / downsample: {stat_downsampled}")
# pcd.points = filtered_pcd.points
points_2d = project_to_xy(filtered_pcd) points_2d = project_to_xy(filtered_pcd)
# points_2d = project_to_plane(filtered_pcd, ground_normal) labels = cluster_2d(points_2d, .3, min_samples=8)
# non_ground = remove_ground(pcd)
# points_2d = project_to_plane(pro)
# points_2d = non_ground[:, :2]
labels = cluster_2d(points_2d)
# boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= 0.3*0.3) # boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= 0.3*0.3)
boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= self.config.min_box_area) boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= self.config.min_box_area)
@ -390,10 +464,6 @@ class Lidar(Node):
detections = np.ones((boxes.shape[0],boxes.shape[1]+2)); detections = np.ones((boxes.shape[0],boxes.shape[1]+2));
detections[:,:-2] = boxes detections[:,:-2] = boxes
# detections = np.array([
# # coords in xyxy
# box.tolist() + [1,1] for box in boxes
# ])
online_tracks = self.tracker.update(detections) online_tracks = self.tracker.update(detections)
self.logger.debug(f"online tracks: {[t[4] for t in online_tracks]}") self.logger.debug(f"online tracks: {[t[4] for t in online_tracks]}")
@ -476,15 +546,12 @@ class Lidar(Node):
if self.config.viz: if self.config.viz:
if len(dropped_pcds): if len(dropped_pcds):
print(dropped_pcds)
dropped_pcd = sum(dropped_pcds, o3d.geometry.PointCloud()) dropped_pcd = sum(dropped_pcds, o3d.geometry.PointCloud())
else: else:
dropped_pcd = o3d.geometry.PointCloud() dropped_pcd = o3d.geometry.PointCloud()
live_pcd.points = dropped_pcd.points live_pcd.points = dropped_pcd.points
live_pcd.colors = dropped_pcd.colors live_pcd.colors = dropped_pcd.colors
print(live_pcd)
color = [0.5, 0.5, 0.7] if self.room_filter.initialised else [0.7, 0.7, 0.7] color = [0.5, 0.5, 0.7] if self.room_filter.initialised else [0.7, 0.7, 0.7]
# live_pcd.paint_uniform_color(color) # live_pcd.paint_uniform_color(color)
live_filtered_pcd.paint_uniform_color([0.2, 0.7, 0.2]) live_filtered_pcd.paint_uniform_color([0.2, 0.7, 0.2])
@ -562,7 +629,7 @@ class Lidar(Node):
argparser.add_argument('--map-outlines', argparser.add_argument('--map-outlines',
help='specify a map (svg-file) from which to load boundary of the map. Any points outside will be filtered (removing floor and walls)', help='specify a map (svg-file) from which to load boundary of the map. Any points outside will be filtered (removing floor and walls)',
type=Path, type=Path,
default="../DATASETS/hof3/map_hof_boundaries.svg") default="../DATASETS/hof3/map_hof_lidar_boundaries.svg")
return argparser return argparser