Skip to content

Patch release 2.18.0.2 - #341

Merged
johningve merged 7 commits into
masterfrom
MISC-2026-09-22-patch-release-2.18.0.2
Sep 30, 2026
Merged

johningve merged 7 commits into
masterfrom
MISC-2026-09-22-patch-release-2.18.0.2

Conversation

@johningve

Copy link
Copy Markdown
Contributor

No description provided.

torbsorb and others added 4 commits September 22, 2026 08:26
Expose each datamodel node's description through the pybind binding
(DataModelWrapper.h, alongside name/path/node_type) and emit it as a
class, enum-class and property docstring in the generated Python
wrappers, so the Python API reference gains the field-level
documentation the C++ and C# references already have.

The datamodel wrappers are regenerated in-tree against SDK 2.18 and
verified: _zivid exposes the descriptions, the wrappers compile, and
Sphinx autodoc renders them (Settings, Acquisition, Aperture, ...).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Building zivid-python in a pixi / conda-forge environment failed with
the compiler reporting missing Zivid headers. The cause is an interplay
between CMake and the compiler.

CMake maintains a list of "implicit include directories" gathered by
probing the compiler -- directories the compiler always searches. For a
normal GCC installation this includes /usr/include. CMake omits these
from the compiler invocation, since they are redundant and since naming
them explicitly would disturb the order in which the compiler searches
its own directories.

When a compiler has different implicit include directories, say
<prefix>/sysroot/usr/include, CMake treats them as a sysroot alias of
/usr/include and still omits /usr/include from the invocation. But such
a compiler does not implicitly read /usr/include, so the Zivid headers
installed there are never found. This has been CMake's behavior since
3.14 and is unchanged as of 4.4, so it is not a regression to wait out
or pin around.

This detects the situation and re-adds /usr/include with -idirafter.
That flag appends the directory after the compiler's own directories, so
the sysroot's libc and libstdc++ headers keep priority; plain -I would
reintroduce the very header shadowing CMake was guarding against.

Note that an environment which replaces the system compiler toolchain
arguably should not discover a Zivid CMake package installed outside the
environment in the first place. Either CMAKE_FIND_ROOT_PATH together
with CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY, or
CMAKE_IGNORE_PREFIX_PATH="/usr;/", makes the system-installed package
correctly invisible from inside such an environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
device_colors() accepted only the four 8-bit color formats, while the
equivalent accessors on Frame2D.image_device_array and organized
PointCloud.device_image already accepted RGBAF. The Zivid SDK has always
supported it; only the binding and the allow-list entry were missing.

This matters to consumers feeding unorganized point clouds into GPU
frameworks that want linear float32 colors from 0 to 1, which otherwise
had to convert with astype(float32) / 255.0 on every capture.

RGBAF is 4-channel, so callers that want a packed 3-channel float32
buffer still have to drop the alpha channel themselves.

test_unorganized_device_colors_rejects_rgbaf asserted the old
restriction, so it is replaced by a positive test. RGB takes over as the
rejected-format case, since the accessor still has to reject something.
A third test compares RGBAF against RGBA / 255.0 on the GPU to confirm
the two formats agree; it needs CUDA and torch, so it skips elsewhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DeviceArray implemented __cuda_array_interface__ but not DLPack, so a
consumer that wants a capsule directly had to build a DLManagedTensor by
hand with ctypes, the way the CaptureAndConvertToDlpackTensorOnCuda
sample does. That was never a deliberate choice; nobody had implemented
it.

__dlpack__ and __dlpack_device__ are added in the pybind layer, where
the capsule holds a copy of the reference-counted DeviceArray and drops
it from the capsule deleter once the consumer is done. Both methods
reach every device-array type through the two existing
addCommon*DeviceArrayMethods helpers.

The consumer's stream argument is accepted and ignored: the buffer was
already ordered against the stream or queue passed at acquisition.

The device ordinal comes from cuPointerGetAttribute, resolved from the
CUDA driver already loaded into the process, because neither
DeviceArray nor ComputeDevice exposes it. The resolved pointer is typed
__stdcall on Windows to match the CUDA driver API's CUDAAPI convention,
since calling it as cdecl would corrupt the stack on 32-bit Windows.

One deviation from offensive programming to note for review: the capsule
destructor returns without freeing when the capsule is no longer named
"dltensor". That is not a fallback but the DLPack hand-off protocol -- a
consumer that takes ownership renames the capsule to "used_dltensor",
and the producer must then not free the tensor. Freeing unconditionally
would double-free every imported tensor.

test_device_array_dlpack_protocol_without_a_consumer covers the protocol
without PyTorch, so the ordinal lookup, capsule creation and the capsule
deleter are exercised in CI, where the PyTorch CUDA tests are skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@johningve
johningve requested a review from a team as a code owner September 22, 2026 06:31
jorgen
jorgen previously approved these changes Sep 22, 2026
vawale
vawale previously approved these changes Sep 22, 2026
@johningve
johningve dismissed stale reviews from vawale and jorgen via 63c4b36 September 30, 2026 07:30
@johningve
johningve force-pushed the MISC-2026-09-22-patch-release-2.18.0.2 branch from 5a5117d to 63c4b36 Compare September 30, 2026 07:30
vawale
vawale previously approved these changes Sep 30, 2026
torbsorb and others added 2 commits September 30, 2026 11:38
Several Mask docstrings describe the opposite of what the code does, so
users (and code-completion tools) reason about masks backwards. The
polarity is: non-zero values mark a point for removal (set to NaN) and
zero values preserve it. A mask built from a Resolution alone is
nonzero-filled, i.e. it masks out every point.

The C++ public headers and the point_cloud/frame mask() docstrings were
correct in the 2.18 release; these wrapper sites were missed:

- The Python Mask class docstring stated non-zero preserves and zero
  masks out -- inverted.
- The Python resolution constructors were documented as
  creating an "empty" mask, and the pybind binding as "zero-filled",
  when the result is nonzero-filled (every point masked out).

Co-Authored-By: John Ingve Schjølberg <john.schjolberg@zivid.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@johningve
johningve force-pushed the MISC-2026-09-22-patch-release-2.18.0.2 branch from 63c4b36 to 8c51819 Compare September 30, 2026 09:38
Beginning with SDK 2.18.0, the Windows testing in CI has been unstable.
The test process dies without a Python traceback in a few percent of
runs, usually early in the session.

Analyzing a core dump from CI showed the fault coming from the Intel
OpenCL JIT compiler, which aborts while building the kernels. Our own
frames only call into it, and the same kernel source builds fine in the
runs that pass.

The runtime in use was built in 2023. Attempt to solve this by
upgrading to the current one.
@johningve
johningve merged commit 8013bd4 into master Sep 30, 2026
26 checks passed
@johningve
johningve deleted the MISC-2026-09-22-patch-release-2.18.0.2 branch September 30, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants