|
11 | 11 | from datetime import datetime |
12 | 12 | import time |
13 | 13 | import rospy |
14 | | -import std_msgs.msg |
| 14 | +import std_msgs.msg |
15 | 15 | import argparse |
16 | 16 |
|
17 | | -def get_matching_files(dir): |
18 | | - """Get files that have corresponding data in all three formats""" |
19 | | - # Get sorted lists of files |
20 | | - png_files = sorted(glob.glob(os.path.join(dir, 'color*.png'))) |
21 | | - tif_files = sorted(glob.glob(os.path.join(dir, 'depth*.tif'))) |
22 | | - npz_files = sorted(glob.glob(os.path.join(dir, 'lidar*.npz'))) |
23 | | - |
24 | | - print(f"Found {len(png_files)} PNG files") |
25 | | - print(f"Found {len(tif_files)} TIF files") |
26 | | - print(f"Found {len(npz_files)} NPZ files") |
27 | | - |
28 | | - # Extract numbers from filenames |
| 17 | +def get_matching_files(data_dir): |
| 18 | + """ |
| 19 | + Collect and return files that have matching (common) index numbers: |
| 20 | + - camera_fl[index].png |
| 21 | + - camera_fr[index].png |
| 22 | + - camera_rr[index].png |
| 23 | + - camera_rl[index].png |
| 24 | + - front_cam[index].png |
| 25 | + - lidar_front[index].npz |
| 26 | + - lidar_top[index].npz |
| 27 | + """ |
| 28 | + |
| 29 | + # 1) Gather each pattern of files |
| 30 | + camera_fl_files = sorted(glob.glob(os.path.join(data_dir, 'camera_fl*.png'))) |
| 31 | + camera_fr_files = sorted(glob.glob(os.path.join(data_dir, 'camera_fr*.png'))) |
| 32 | + camera_rr_files = sorted(glob.glob(os.path.join(data_dir, 'camera_rr*.png'))) |
| 33 | + camera_rl_files = sorted(glob.glob(os.path.join(data_dir, 'camera_rl*.png'))) |
| 34 | + front_cam_files = sorted(glob.glob(os.path.join(data_dir, 'front_cam*.png'))) |
| 35 | + lidar_front_files = sorted(glob.glob(os.path.join(data_dir, 'lidar_front*.npz'))) |
| 36 | + lidar_top_files = sorted(glob.glob(os.path.join(data_dir, 'lidar_top*.npz'))) |
| 37 | + |
| 38 | + print(f"Found {len(camera_fl_files)} FL camera PNG files") |
| 39 | + print(f"Found {len(camera_fr_files)} FR camera PNG files") |
| 40 | + print(f"Found {len(camera_rr_files)} RR camera PNG files") |
| 41 | + print(f"Found {len(camera_rl_files)} RL camera PNG files") |
| 42 | + print(f"Found {len(front_cam_files)} front_cam PNG files") |
| 43 | + print(f"Found {len(lidar_front_files)} lidar_front NPZ files") |
| 44 | + print(f"Found {len(lidar_top_files)} lidar_top NPZ files") |
| 45 | + |
| 46 | + # 2) Helper function to extract index from filename |
29 | 47 | def get_number(filename): |
30 | | - return int(''.join(filter(str.isdigit, os.path.basename(filename)))) |
31 | | - |
32 | | - # Create dictionaries with numbers as keys |
33 | | - png_dict = {get_number(f): f for f in png_files} |
34 | | - tif_dict = {get_number(f): f for f in tif_files} |
35 | | - npz_dict = {get_number(f): f for f in npz_files} |
36 | | - |
37 | | - # Find common numbers |
38 | | - common_numbers = set(png_dict.keys()) & set(tif_dict.keys()) & set(npz_dict.keys()) |
39 | | - print(f"\nFound {len(common_numbers)} matching frame numbers") |
40 | | - |
41 | | - # Get matching files in sorted order |
42 | | - common_numbers = sorted(common_numbers) |
43 | | - matching_pngs = [png_dict[n] for n in common_numbers] |
44 | | - matching_tifs = [tif_dict[n] for n in common_numbers] |
45 | | - matching_npzs = [npz_dict[n] for n in common_numbers] |
46 | | - |
47 | | - return matching_pngs, matching_tifs, matching_npzs |
48 | | - |
49 | | -def create_rosbag_from_data( |
50 | | - dir, |
51 | | - output_bag_path, |
52 | | - frame_interval |
53 | | -): |
54 | | - # Initialize CV bridge |
| 48 | + # Example: "camera_fl123.png" => 123 |
| 49 | + base = os.path.basename(filename) |
| 50 | + # Filter out digits in the basename, then convert to int |
| 51 | + return int(''.join(filter(str.isdigit, base))) |
| 52 | + |
| 53 | + # 3) Convert to dictionaries keyed by index number |
| 54 | + fl_dict = {get_number(f): f for f in camera_fl_files} |
| 55 | + fr_dict = {get_number(f): f for f in camera_fr_files} |
| 56 | + rr_dict = {get_number(f): f for f in camera_rr_files} |
| 57 | + rl_dict = {get_number(f): f for f in camera_rl_files} |
| 58 | + front_dict = {get_number(f): f for f in front_cam_files} |
| 59 | + |
| 60 | + lidar_front_dict = {get_number(f): f for f in lidar_front_files} |
| 61 | + lidar_top_dict = {get_number(f): f for f in lidar_top_files} |
| 62 | + |
| 63 | + # 4) Find common index numbers across all 7 dictionaries |
| 64 | + common_indices = ( |
| 65 | + set(fl_dict.keys()) |
| 66 | + & set(fr_dict.keys()) |
| 67 | + & set(rr_dict.keys()) |
| 68 | + & set(rl_dict.keys()) |
| 69 | + & set(front_dict.keys()) |
| 70 | + & set(lidar_front_dict.keys()) |
| 71 | + & set(lidar_top_dict.keys()) |
| 72 | + ) |
| 73 | + |
| 74 | + print(f"\nFound {len(common_indices)} matching indices among all file types") |
| 75 | + |
| 76 | + # 5) For convenience, sort them and pull out matching file paths in the same order |
| 77 | + common_indices = sorted(common_indices) |
| 78 | + matching_fl = [fl_dict[idx] for idx in common_indices] |
| 79 | + matching_fr = [fr_dict[idx] for idx in common_indices] |
| 80 | + matching_rr = [rr_dict[idx] for idx in common_indices] |
| 81 | + matching_rl = [rl_dict[idx] for idx in common_indices] |
| 82 | + matching_front = [front_dict[idx] for idx in common_indices] |
| 83 | + |
| 84 | + matching_lidar_front = [lidar_front_dict[idx] for idx in common_indices] |
| 85 | + matching_lidar_top = [lidar_top_dict[idx] for idx in common_indices] |
| 86 | + |
| 87 | + return ( |
| 88 | + matching_fl, |
| 89 | + matching_fr, |
| 90 | + matching_rr, |
| 91 | + matching_rl, |
| 92 | + matching_front, |
| 93 | + matching_lidar_front, |
| 94 | + matching_lidar_top |
| 95 | + ) |
| 96 | + |
| 97 | +def create_rosbag_from_data(data_dir, output_bag_path, frame_rate): |
| 98 | + """Create a ROS bag with camera (PNG) and LiDAR (NPZ) data at a given frame rate.""" |
| 99 | + |
| 100 | + # Initialize CV bridge and ROS node |
55 | 101 | bridge = CvBridge() |
56 | | - |
57 | | - # Initialize ROS node |
58 | 102 | rospy.init_node('bag_creator', anonymous=True) |
59 | | - |
60 | | - # Get matching files |
61 | | - png_files, tif_files, npz_files = get_matching_files(dir) |
62 | | - |
63 | | - print(f"Found {len(png_files)} matching files in all three formats") |
64 | | - |
65 | | - if len(png_files) == 0: |
| 103 | + |
| 104 | + # Gather matching files |
| 105 | + ( |
| 106 | + fl_files, |
| 107 | + fr_files, |
| 108 | + rr_files, |
| 109 | + rl_files, |
| 110 | + front_files, |
| 111 | + lidar_front_files, |
| 112 | + lidar_top_files |
| 113 | + ) = get_matching_files(data_dir) |
| 114 | + |
| 115 | + num_frames = len(fl_files) |
| 116 | + print(f"Found {num_frames} total matching sets of files") |
| 117 | + |
| 118 | + if num_frames == 0: |
66 | 119 | raise ValueError("No matching files found!") |
67 | | - |
68 | | - # Create a new bag file |
| 120 | + |
| 121 | + # Create a new bag |
69 | 122 | with rosbag.Bag(output_bag_path, 'w') as bag: |
70 | 123 | # Start time for the messages |
71 | 124 | start_time = rospy.Time.from_sec(time.time()) |
72 | 125 | message_count = 0 |
73 | | - |
74 | | - for idx, (png_file, tif_file, npz_file) in enumerate(zip(png_files, tif_files, npz_files)): |
75 | | - # Calculate timestamp for this frame |
76 | | - timestamp = rospy.Time.from_sec(start_time.to_sec() + idx * (1/frame_interval)) |
77 | | - |
| 126 | + |
| 127 | + # Publish each frame |
| 128 | + for idx in range(num_frames): |
| 129 | + # Prepare a timestamp for this frame |
| 130 | + timestamp = rospy.Time.from_sec(start_time.to_sec() + idx * (1.0 / frame_rate)) |
| 131 | + |
78 | 132 | try: |
79 | | - # Process PNG image |
80 | | - png_img = cv2.imread(png_file) |
81 | | - if png_img is not None: |
82 | | - png_msg = bridge.cv2_to_imgmsg(png_img, encoding="bgr8") |
83 | | - png_msg.header.stamp = timestamp |
84 | | - png_msg.header.frame_id = "camera_rgb" |
85 | | - bag.write('/camera/rgb/image_raw', png_msg, timestamp) |
| 133 | + # ------------------------------------------------- |
| 134 | + # 1) CAMERA: FL, FR, RR, RL, FRONT |
| 135 | + # ------------------------------------------------- |
| 136 | + # Read each .png file |
| 137 | + fl_img = cv2.imread(fl_files[idx]) |
| 138 | + fr_img = cv2.imread(fr_files[idx]) |
| 139 | + rr_img = cv2.imread(rr_files[idx]) |
| 140 | + rl_img = cv2.imread(rl_files[idx]) |
| 141 | + front_img = cv2.imread(front_files[idx]) |
| 142 | + |
| 143 | + # Convert to ROS Image messages |
| 144 | + if fl_img is not None: |
| 145 | + fl_msg = bridge.cv2_to_imgmsg(fl_img, encoding="bgr8") |
| 146 | + fl_msg.header.stamp = timestamp |
| 147 | + fl_msg.header.frame_id = "camera_fl" |
| 148 | + bag.write('/camera/fl/image_raw', fl_msg, timestamp) |
86 | 149 | message_count += 1 |
87 | | - else: |
88 | | - print(f"Warning: Could not read PNG file: {png_file}") |
89 | | - |
90 | | - # Process TIF image |
91 | | - tif_img = cv2.imread(tif_file, -1) # -1 to preserve original depth |
92 | | - if tif_img is not None: |
93 | | - tif_msg = bridge.cv2_to_imgmsg(tif_img, encoding="passthrough") |
94 | | - tif_msg.header.stamp = timestamp |
95 | | - tif_msg.header.frame_id = "camera_depth" |
96 | | - bag.write('/camera/depth/image_raw', tif_msg, timestamp) |
| 150 | + |
| 151 | + if fr_img is not None: |
| 152 | + fr_msg = bridge.cv2_to_imgmsg(fr_img, encoding="bgr8") |
| 153 | + fr_msg.header.stamp = timestamp |
| 154 | + fr_msg.header.frame_id = "camera_fr" |
| 155 | + bag.write('/camera/fr/image_raw', fr_msg, timestamp) |
97 | 156 | message_count += 1 |
98 | | - else: |
99 | | - print(f"Warning: Could not read TIF file: {tif_file}") |
100 | | - |
101 | | - # Process pointcloud NPZ |
102 | | - pc_data = np.load(npz_file) |
103 | | - points = pc_data['arr_0'] # Using 'arr_0' based on the provided files' |
104 | | - |
105 | | - # Create pointcloud message |
106 | | - header = std_msgs.msg.Header() |
107 | | - header.stamp = timestamp |
108 | | - header.frame_id = "velodyne" |
109 | | - |
| 157 | + |
| 158 | + if rr_img is not None: |
| 159 | + rr_msg = bridge.cv2_to_imgmsg(rr_img, encoding="bgr8") |
| 160 | + rr_msg.header.stamp = timestamp |
| 161 | + rr_msg.header.frame_id = "camera_rr" |
| 162 | + bag.write('/camera/rr/image_raw', rr_msg, timestamp) |
| 163 | + message_count += 1 |
| 164 | + |
| 165 | + if rl_img is not None: |
| 166 | + rl_msg = bridge.cv2_to_imgmsg(rl_img, encoding="bgr8") |
| 167 | + rl_msg.header.stamp = timestamp |
| 168 | + rl_msg.header.frame_id = "camera_rl" |
| 169 | + bag.write('/camera/rl/image_raw', rl_msg, timestamp) |
| 170 | + message_count += 1 |
| 171 | + |
| 172 | + if front_img is not None: |
| 173 | + front_msg = bridge.cv2_to_imgmsg(front_img, encoding="bgr8") |
| 174 | + front_msg.header.stamp = timestamp |
| 175 | + front_msg.header.frame_id = "camera_front" |
| 176 | + bag.write('/camera/front/image_raw', front_msg, timestamp) |
| 177 | + message_count += 1 |
| 178 | + |
| 179 | + # ------------------------------------------------- |
| 180 | + # 2) LIDAR: FRONT, TOP |
| 181 | + # ------------------------------------------------- |
| 182 | + # For .npz, assume data is in 'arr_0' (x, y, z, or however your data is structured) |
| 183 | + front_data = np.load(lidar_front_files[idx]) |
| 184 | + front_points = front_data['arr_0'] # e.g., Nx3 array |
| 185 | + |
| 186 | + top_data = np.load(lidar_top_files[idx]) |
| 187 | + top_points = top_data['arr_0'] # e.g., Nx3 array |
| 188 | + |
| 189 | + # A) Lidar FRONT |
| 190 | + front_header = std_msgs.msg.Header() |
| 191 | + front_header.stamp = timestamp |
| 192 | + front_header.frame_id = "lidar_front" |
| 193 | + |
| 194 | + # Example: 3 float32 fields for x,y,z |
110 | 195 | fields = [ |
111 | 196 | pc2.PointField('x', 0, pc2.PointField.FLOAT32, 1), |
112 | 197 | pc2.PointField('y', 4, pc2.PointField.FLOAT32, 1), |
113 | | - pc2.PointField('z', 8, pc2.PointField.FLOAT32, 1) |
| 198 | + pc2.PointField('z', 8, pc2.PointField.FLOAT32, 1), |
114 | 199 | ] |
115 | | - |
116 | | - pc_msg = pc2.create_cloud(header, fields, points) |
117 | | - bag.write('/velodyne_points', pc_msg, timestamp) |
| 200 | + front_pc_msg = pc2.create_cloud(front_header, fields, front_points) |
| 201 | + bag.write('/lidar/front/points', front_pc_msg, timestamp) |
118 | 202 | message_count += 1 |
119 | | - |
| 203 | + |
| 204 | + # B) Lidar TOP |
| 205 | + top_header = std_msgs.msg.Header() |
| 206 | + top_header.stamp = timestamp |
| 207 | + top_header.frame_id = "lidar_top" |
| 208 | + |
| 209 | + top_pc_msg = pc2.create_cloud(top_header, fields, top_points) |
| 210 | + bag.write('/lidar/top/points', top_pc_msg, timestamp) |
| 211 | + message_count += 1 |
| 212 | + |
120 | 213 | except Exception as e: |
121 | | - print(f"Error processing frame {idx}: {str(e)}") |
| 214 | + print(f"Error processing frame {idx}: {e}") |
122 | 215 | continue |
123 | | - |
| 216 | + |
| 217 | + # Log progress every so often |
124 | 218 | if idx % 10 == 0: |
125 | | - print(f"Processed {idx} frames") |
126 | | - |
127 | | - print(f"Total messages written to bag: {message_count}") |
| 219 | + print(f"Processed {idx} frames...") |
| 220 | + |
| 221 | + print(f"Finished writing {message_count} total messages to {output_bag_path}.") |
128 | 222 |
|
129 | 223 | if __name__ == "__main__": |
130 | | - #This initializes the parser and sets the parameters that the user will be asked to provide in the terminal |
131 | 224 | parser = argparse.ArgumentParser( |
132 | | - description='A script to convert data gathered by cameras and lidar sensors to ros .bag messages' |
| 225 | + description='Convert multiple camera (PNG) and LiDAR (NPZ) files into a single ROS bag.' |
133 | 226 | ) |
134 | 227 |
|
135 | 228 | parser.add_argument( |
136 | | - 'files_directory', type = str, |
137 | | - help = 'The path to the directory with all the rgb images, depth maps, and point clouds. The file formats must be PNG, TIF, and NPZ, respectively' |
| 229 | + 'files_directory', |
| 230 | + type=str, |
| 231 | + help='Path to the directory containing the camera and lidar files.' |
138 | 232 | ) |
139 | 233 |
|
140 | 234 | parser.add_argument( |
141 | | - 'output_bag', type = str, |
142 | | - help = 'The path to the directory where the bag file will be saved' |
| 235 | + 'output_bag', |
| 236 | + type=str, |
| 237 | + help='Full path (including filename) where the output ROS bag will be saved.' |
143 | 238 | ) |
144 | | - |
| 239 | + |
145 | 240 | parser.add_argument( |
146 | | - 'rate', type = int, |
147 | | - help = 'The rate at which the data is collected in Hz' |
| 241 | + 'rate', |
| 242 | + type=int, |
| 243 | + help='Playback (or capture) rate in Hz (frames per second).' |
148 | 244 | ) |
| 245 | + |
149 | 246 | args = parser.parse_args() |
150 | | - directory = args.files_directory |
151 | | - output_bag = args.output_bag |
152 | | - rate = args.rate |
153 | | - # Example directories below: |
154 | | - #directory = "/home/username/host/CS588/GEMstack/data/data_sample/data/" |
155 | | - #output_bag = "/home/username/host/CS588/GEMstack/data/data_sample/data/output.bag" |
156 | | - |
| 247 | + |
157 | 248 | try: |
158 | 249 | create_rosbag_from_data( |
159 | | - directory, |
160 | | - output_bag, |
161 | | - rate |
| 250 | + data_dir=args.files_directory, |
| 251 | + output_bag_path=args.output_bag, |
| 252 | + frame_rate=args.rate |
162 | 253 | ) |
163 | 254 | print("Successfully created ROS bag file!") |
164 | | - |
165 | | - # Verify bag contents |
| 255 | + |
| 256 | + # Show basic info about the new bag |
166 | 257 | print("\nBag file contents:") |
167 | | - info_cmd = f"rosbag info {output_bag}" |
168 | | - os.system(info_cmd) |
169 | | - |
| 258 | + os.system(f"rosbag info {args.output_bag}") |
| 259 | + |
170 | 260 | except Exception as e: |
171 | | - print(f"Error creating ROS bag: {str(e)}") |
| 261 | + print(f"Error creating ROS bag: {e}") |
0 commit comments