Skip to content
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

Backport #199 to Humble #211

Merged
merged 1 commit into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions sensor_msgs_py/sensor_msgs_py/point_cloud2.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def read_points(
# Cast bytes to numpy array
points = np.ndarray(
shape=(cloud.width * cloud.height, ),
dtype=dtype_from_fields(cloud.fields),
dtype=dtype_from_fields(cloud.fields, point_step=cloud.point_step),
buffer=cloud.data)

# Keep only the requested fields
Expand Down Expand Up @@ -194,12 +194,14 @@ def read_points_list(
skip_nans, uvs)]


def dtype_from_fields(fields: Iterable[PointField]) -> np.dtype:
def dtype_from_fields(fields: Iterable[PointField], point_step: Optional[int] = None) -> np.dtype:
"""
Convert a Iterable of sensor_msgs.msg.PointField messages to a np.dtype.

:param fields: The point cloud fields.
(Type: iterable of sensor_msgs.msg.PointField)
:param point_step: Point step size in bytes. Calculated from the given fields by default.
(Type: optional of integer)
:returns: NumPy datatype
"""
# Create a lists containing the names, offsets and datatypes of all fields
Expand Down Expand Up @@ -229,12 +231,15 @@ def dtype_from_fields(fields: Iterable[PointField]) -> np.dtype:
field_offsets.append(field.offset + a * datatype.itemsize)
field_datatypes.append(datatype.str)

# Create a tuple for each field containing name and data type
return np.dtype({
'names': field_names,
'formats': field_datatypes,
'offsets': field_offsets,
})
# Create dtype
dtype_dict = {
'names': field_names,
'formats': field_datatypes,
'offsets': field_offsets
}
if point_step is not None:
dtype_dict['itemsize'] = point_step
return np.dtype(dtype_dict)


def create_cloud(
Expand Down
36 changes: 34 additions & 2 deletions sensor_msgs_py/test/test_point_cloud2.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@

import numpy as np
try:
from numpy.lib.recfunctions import structured_to_unstructured
from numpy.lib.recfunctions import structured_to_unstructured, unstructured_to_structured
except ImportError:
from sensor_msgs_py.numpy_compat import structured_to_unstructured
from sensor_msgs_py.numpy_compat import (structured_to_unstructured,
unstructured_to_structured)

from sensor_msgs.msg import PointCloud2, PointField
from sensor_msgs_py import point_cloud2
Expand Down Expand Up @@ -270,6 +271,37 @@ def test_create_cloud__non_one_count(self):
points)
self.assertEqual(thispcd, pcd5)

def test_read_cloud_with_non_standard_point_step(self):
itemsize = 123 # Larger than normal point step size

# Copy to new array with larger itemsize
points_larger_itemsize = np.array(
unstructured_to_structured(points),
dtype=np.dtype({
'names': ['x', 'y', 'z'],
'formats': ['<f4', '<f4', '<f4'],
'offsets': [0, 4, 8],
'itemsize': itemsize
})
)

# Create pointcloud with itemsize == point_step from the padded array
pc = PointCloud2(
header=Header(frame_id='frame'),
height=1,
width=points_larger_itemsize.shape[0],
is_dense=False,
is_bigendian=sys.byteorder != 'little',
fields=fields,
point_step=itemsize,
row_step=itemsize * points_larger_itemsize.shape[0],
data=points_larger_itemsize.tobytes()
)

# Deserialize point cloud
reconstructed_points = point_cloud2.read_points_numpy(pc)
self.assertTrue(np.allclose(points, reconstructed_points, equal_nan=True))


if __name__ == '__main__':
unittest.main()