Files
cristhian c1a7d70ba6 add typed Arrow payloads and a dora-image-view dataflow
dora-image-view takes std/media/v1/Image, and dora's compatibility graph
has no edge from std/core/v1/UInt8 to it, so wiring the two failed
`dora validate` whatever the node actually sent. Its flat-buffer fallback
would not have decoded these frames either: it reads width/height
metadata, and this node only published shape.

image and depth now go out as the Image struct, camera_info and
point_cloud as Float32 arrays and camera_state as a string, all built
against the Arrow C data interface in src/arrow so the node still links
no Arrow C++. Shape, dtype and encoding metadata are dropped where the
payload describes itself; intrinsics, frame_id and both timestamps are
unchanged. tests/run.sh covers the packing without a camera or the SDK,
round-tripping each array through pyarrow and rebuilding it under the
sanitizers.

Breaking: image_bgr is renamed to image and every payload changes shape.
dataflow_plot.yml is replaced by dataflow_view.yml, which wires the
viewer from a tag and repeats its contract in input_types so validate
checks both sides of the wiring.
2026-09-15 10:52:08 -05:00

130 lines
4.0 KiB
Python

"""Round-trip every exported payload through a real Arrow consumer.
The node builds its Arrow arrays by hand against the C data interface rather
than linking Arrow C++, so this is what shows that what dora imports on the
other side of `send_arrow_output` is what went in. It needs neither a camera nor
the ZED SDK.
"""
import ctypes
import pathlib
import sys
import pyarrow as pa
LIBRARY = pathlib.Path(__file__).resolve().parent.parent / "build" / "tests" / "libzed_arrow.so"
# ArrowArray is ten machine words and ArrowSchema nine, and both want the
# 8-byte alignment an array of uint64 gives.
WORDS = 16
# Index of the `release` field in ArrowArray: a consumer that took ownership
# zeroes it, which is how this test sees the move happen.
RELEASE = 8
IMAGE_TYPE = pa.struct([
("width", pa.uint32()),
("height", pa.uint32()),
("encoding", pa.utf8()),
("data", pa.large_binary()),
])
def load():
"""Load the test library and declare the argument types of its exports."""
library = ctypes.CDLL(str(LIBRARY))
library.zed_export_image.argtypes = [
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32,
ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t,
]
library.zed_export_float32.argtypes = [
ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_size_t,
]
library.zed_export_string.argtypes = [
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t,
]
return library
def structs():
"""Allocate the ArrowArray and ArrowSchema the producer fills in."""
return (ctypes.c_uint64 * WORDS)(), (ctypes.c_uint64 * WORDS)()
def imported(array, schema):
"""Import the pair as pyarrow does, taking ownership of both."""
result = pa.Array._import_from_c(ctypes.addressof(array), ctypes.addressof(schema))
assert array[RELEASE] == 0, "the consumer should have taken ownership of the array"
return result
def test_image(library):
"""An image row carries its own dimensions, encoding and bytes."""
array, schema = structs()
pixels = bytes(range(12))
library.zed_export_image(
ctypes.byref(array), ctypes.byref(schema), 2, 2, b"bgr8", pixels, len(pixels)
)
result = imported(array, schema)
assert result.type == IMAGE_TYPE, result.type
assert result.to_pylist() == [
{"width": 2, "height": 2, "encoding": "bgr8", "data": pixels}
]
def test_depth(library):
"""A depth map is an image whose bytes are float32 millimetres."""
array, schema = structs()
millimetres = [1500.0, 0.0, 250.5, 4000.0]
payload = pa.array(millimetres, type=pa.float32()).buffers()[1].to_pybytes()
library.zed_export_image(
ctypes.byref(array), ctypes.byref(schema), 2, 2, b"32FC1", payload, len(payload)
)
row = imported(array, schema).to_pylist()[0]
assert row["encoding"] == "32FC1"
assert pa.Array.from_buffers(
pa.float32(), 4, [None, pa.py_buffer(row["data"])]
).to_pylist() == millimetres
def test_float32(library):
"""Intrinsics and the point cloud go out as plain float32 values."""
array, schema = structs()
values = (ctypes.c_float * 4)(700.5, 700.5, 640.0, 360.0)
library.zed_export_float32(ctypes.byref(array), ctypes.byref(schema), values, 4)
result = imported(array, schema)
assert result.type == pa.float32()
assert result.to_pylist() == [700.5, 700.5, 640.0, 360.0]
def test_string(library):
"""The camera state goes out as one UTF-8 string."""
array, schema = structs()
library.zed_export_string(ctypes.byref(array), ctypes.byref(schema), b"streaming", 9)
result = imported(array, schema)
assert result.type == pa.utf8()
assert result.to_pylist() == ["streaming"]
def main():
"""Run every test, reporting the first failure."""
library = load()
for test in (test_image, test_depth, test_float32, test_string):
test(library)
print(f"ok {test.__name__}")
return 0
if __name__ == "__main__":
sys.exit(main())