-
Notifications
You must be signed in to change notification settings - Fork 11
Lidar Scene Transforms #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 10 commits
c284792
9b31d95
84e1eec
5f155ec
19c57c6
479c342
96cacb9
4095a57
3cea689
26c3578
f3c2fa7
9d9e32d
5021ce6
4cca2e6
f7c0d39
203ec13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| from nucleus.sensorfusion import * | ||
| import numpy as np | ||
| import glob | ||
|
|
||
| def format_pointcloud(lidar_np): | ||
| mask = lidar_np[:, 4] == 0.0 | ||
| pc_1 = lidar_np[mask, :] | ||
| pc_1 = np.delete(pc_1, (4), 1) | ||
| return pc_1 | ||
|
|
||
| npz_files = sorted(glob.glob("sdk-sample-data/*")) | ||
| npz_files.remove("sdk-sample-data/extrinsics_seq000-003_11042021.npz") | ||
|
|
||
| updated_extrinsics = np.load("sdk-sample-data/extrinsics_seq000-003_11042021.npz", allow_pickle = True) | ||
| wnsl_extrinsics = updated_extrinsics['camera_WindshieldNarrowStereoLeft_lidar_extrinsic'] | ||
| print(f"Camera Extrinsics: \n{wnsl_extrinsics}") | ||
|
|
||
| cam_1_calib = CamCalibration(extrinsic_matrix=wnsl_extrinsics) | ||
| print(f"Original Camera Pose:{cam_1_calib.pose}") | ||
|
|
||
| RawScene = RawScene() | ||
| for idx,npz_fp in enumerate(npz_files): | ||
| print(f"Frame Index: {idx}") | ||
|
|
||
| frame_npz = np.load(npz_fp, allow_pickle=True) | ||
|
|
||
| pointcloud_np= format_pointcloud(frame_npz['points']) | ||
| print(f"PointCloud Shape: {pointcloud_np.shape}") | ||
|
|
||
| pointcloud = RawPointCloud(points=pointcloud_np) | ||
|
|
||
| print(f"World Coordinate Transformation:\n{frame_npz['vehicle_local_tf']}") | ||
| frame_pose = Transform(frame_npz['vehicle_local_tf']) | ||
| print(f"Frame Pose: {frame_pose}") | ||
|
|
||
| raw_frame = RawFrame(lidar=pointcloud, cam1=cam_1_calib, dev_pose=frame_pose) | ||
| RawScene.add_frame(raw_frame) | ||
|
|
||
| print(f"Frame 5, Point1 PreTransform: {RawScene.frames[4].items['lidar'].points[0]}") | ||
| print(f"Frame 5, Camera Extrinsics PreTransform: {RawScene.frames[4].items['cam1'].pose}") | ||
| RawScene.apply_transforms(relative=True) | ||
| print(f"Frame 5, Point1 in World: {RawScene.frames[4].items['lidar'].points[0]}") | ||
| print(f"Frame 5, Camera Extrinsics PostTransform: {RawScene.frames[4].items['cam1'].pose}") |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,138 @@ | ||||||
| import numpy as np | ||||||
| from nucleus.transform import Transform | ||||||
| from nucleus.utils import read_pcd | ||||||
| import copy | ||||||
|
|
||||||
| class RawPointCloud: | ||||||
| ''' | ||||||
| RawPointClouds are containers for raw point cloud data. This structure contains Point Clouds as (N,3) or (N,4) numpy arrays | ||||||
| Point cloud data is assumed to be in ego coordinates. | ||||||
| If the point cloud is in World Coordinates, one can provide the inverse pose as the "transform" argument. If this argument | ||||||
| is present, the point cloud will be transformed back into ego coordinates | ||||||
patrick-scale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| Args: | ||||||
| points (np.array): Point cloud data represented as (N,3) or (N,4) numpy arrays | ||||||
| transform (:class:`Transform`): If in World Coordinate, the transformation used to transform lidar points into ego coordinates | ||||||
patrick-scale marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ''' | ||||||
|
|
||||||
| def __init__(self, points: np.array = None, transform: Transform = None): | ||||||
| self.points = points | ||||||
patrick-scale marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| if transform is not None: | ||||||
| self.points = transform.apply(points) | ||||||
|
|
||||||
| def load_pcd(self, filepath: str, transform: Transform = None): | ||||||
| ''' | ||||||
| Takes raw pcd file and reads in as numpy array. | ||||||
| Args: | ||||||
| filepath (str): Local filepath to pcd file | ||||||
| transform (:class:`Transform`): If in world, the transformation used to transform lidar points into ego coordinates | ||||||
| ''' | ||||||
| points = read_pcd(filepath) | ||||||
| self.points = points | ||||||
| if transform is not None: | ||||||
| self.points = transform.apply(points) | ||||||
|
|
||||||
|
|
||||||
| class CamCalibration: | ||||||
patrick-scale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| ''' | ||||||
| CamCalibration solely holds the pose of the camera | ||||||
| This CamCalibration will inevitably be transformed by the device_pose | ||||||
| Args: | ||||||
| extrinsic_matrix (np.array): (4,4) extrinsic transformation matrix representing device_to_lidar | ||||||
| ''' | ||||||
|
|
||||||
| def __init__(self, extrinsic_matrix=None): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :nit: try making this a dataclass for some syntactic sugar! |
||||||
| self.extrinsic_matrix = extrinsic_matrix | ||||||
|
|
||||||
| @property | ||||||
| def extrinsic_matrix(self): | ||||||
| """Camera extrinsic | ||||||
| :getter: Return camera's extrinsic matrix (pose.inverse[:3, :4]) | ||||||
| :setter: pose = Transform(matrix).inverse | ||||||
| :type: 3x4 matrix | ||||||
| """ | ||||||
| return self.pose.inverse[:3, :4] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. self.pose may be did you mean to initialize it in the constructor? |
||||||
|
|
||||||
| @extrinsic_matrix.setter | ||||||
| def extrinsic_matrix(self, matrix): | ||||||
| ''' | ||||||
| Sets pose as inverse of extrinsic matrix | ||||||
| ''' | ||||||
| self.pose = Transform(matrix).inverse | ||||||
|
|
||||||
|
|
||||||
| class RawFrame: | ||||||
| ''' | ||||||
| RawFrames are containers for point clouds, image extrinsics, and device pose. | ||||||
| These objects most notably are leveraged to transform point clouds and image extrinsic matrices to the world coordinate frame. | ||||||
| Args: | ||||||
| dev_pose (:class:`Transform`): World Coordinate transformation for frame | ||||||
| **kwargs (Dict[str, :class:`RawPointCloud, :class: CameraCalibration`]): Mappings from sensor name | ||||||
| to pointcloud and camera calibrations. Each frame of a lidar scene must contain exactly one | ||||||
| pointcloud and any number of camera calibrations | ||||||
| ''' | ||||||
| def __init__(self, dev_pose: Transform = None, **kwargs): | ||||||
patrick-scale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| self.dev_pose = dev_pose | ||||||
| self.items = {} | ||||||
| for key, value in kwargs.items(): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to name the variables you expect in the constructor explicitly instead of relying on kwargs, whenever possible
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed - but it's tough because there is a variable number of camera items that they may add to each frame dependent on their sensor suite |
||||||
| if isinstance(value, CamCalibration): | ||||||
| self.items[key] = copy.copy(value) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why copy?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Otherwise this basically passes by reference. We want to generate a xFrames camera calibrations and this will just continually update the same variable otherwise |
||||||
| else: | ||||||
| self.items[key] = value | ||||||
|
|
||||||
| def get_world_points(self): | ||||||
| """Return the list of points with the frame transformation applied | ||||||
| :returns: List of points in world coordinates | ||||||
| :rtype: np.array | ||||||
| """ | ||||||
| for item in self.items: | ||||||
| if isinstance(self.items[item], RawPointCloud): | ||||||
| return np.hstack( | ||||||
| [ | ||||||
| self.dev_pose @ self.items[item][:, :3], | ||||||
| self.items[item][:, 3:4], | ||||||
| self.items[item][:, 4:5] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add validation on item to make sure item.shape[1] >= 5
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually just will skip this step if the shape is < 5 (add nothing) |
||||||
| ] | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| class RawScene: | ||||||
| ''' | ||||||
| RawsScenes are containers for frames | ||||||
| These objects most notably are leveraged to transform point clouds and image extrinsic matrices to the world coordinate frame. | ||||||
| Args: | ||||||
| dev_pose (:class:`Transform`): World Coordinate transformation for frame | ||||||
| **kwargs (Dict[str, :class:`RawPointCloud, :class: CameraCalibration`]): Mappings from sensor name | ||||||
| to pointcloud and camera calibrations. Each frame of a lidar scene must contain exactly one | ||||||
| pointcloud and any number of camera calibrations | ||||||
| ''' | ||||||
| def __init__(self, frames: [RawFrame] = None): | ||||||
patrick-scale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| if frames is None: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just init to empty list as suggested above |
||||||
| self.frames = [] | ||||||
| else: | ||||||
| self.frames = frames | ||||||
|
|
||||||
| def add_frame(self, frame: RawFrame = None): | ||||||
|
||||||
| def add_frame(self, frame: RawFrame = None): | |
| def add_frame(self, frame: RawFrame): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if a user double applies make_transforms_relative? is it bad? if yes, we should prevent them from doing it twice (maybe add a boolean flag to represent if it's already been applied)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commited new changes to handle double transformation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe can get rid of this check, what other class would it be an instance of?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be a CameraCalibration object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the ordering of these imports is off, we have a pre-commit hook that fixes import oordering automatically, my bet is that you just don't have it installed.
Try running the following in your repo:
poetry run pre-commit installThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having trouble running this