Files
Aethera/render_3D/third_party/datoviz/testing/test_array_facade.py
T
2026-08-20 10:23:47 +08:00

767 lines
26 KiB
Python

"""Tests for the generated top-level array-aware facade."""
from __future__ import annotations
import ctypes
import importlib
import sys
import types
from pathlib import Path
import numpy as np
import pytest
ROOT_DIR = Path(__file__).resolve().parents[1]
pytestmark = pytest.mark.skipif(
not (ROOT_DIR / 'datoviz' / '_array_facade.py').exists(),
reason='array facade has not been generated',
)
def _raw_function(argtypes, restype=ctypes.c_int):
calls = []
def func(*args):
calls.append(args)
return 17
func.argtypes = argtypes
func.restype = restype
func.__doc__ = 'raw function'
func.calls = calls
return func
@pytest.fixture()
def fake_facade(monkeypatch):
raw = types.ModuleType('datoviz.raw')
raw.DvzVisual = type('DvzVisual', (), {})
raw.DvzSizeSpace = type('DvzSizeSpace', (), {'DVZ_SIZE_LOGICAL': 0})
raw.DVZ_SIZE_LOGICAL = raw.DvzSizeSpace.DVZ_SIZE_LOGICAL
class DvzAxisTicks(ctypes.Structure):
_fields_ = [
('struct_size', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('count', ctypes.c_uint32),
('values', ctypes.POINTER(ctypes.c_double)),
('labels', ctypes.POINTER(ctypes.c_char_p)),
]
class DvzColorbarTicks(ctypes.Structure):
_fields_ = [
('struct_size', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('count', ctypes.c_uint32),
('values', ctypes.POINTER(ctypes.c_double)),
('labels', ctypes.POINTER(ctypes.c_char_p)),
]
class DvzVisualDataUpdate(ctypes.Structure):
_fields_ = [
('attr_name', ctypes.c_char_p),
('data', ctypes.c_void_p),
('item_count', ctypes.c_uint32),
]
class DvzSampledFieldDesc(ctypes.Structure):
_fields_ = [
('struct_size', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('dim', ctypes.c_int),
('format', ctypes.c_int),
('semantic', ctypes.c_int),
('color_role', ctypes.c_int),
('width', ctypes.c_uint32),
('height', ctypes.c_uint32),
('depth', ctypes.c_uint32),
]
class DvzFieldDataView(ctypes.Structure):
_fields_ = [
('struct_size', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('data', ctypes.c_void_p),
('bytes_per_row', ctypes.c_uint64),
('rows_per_image', ctypes.c_uint64),
]
class DvzFieldRegion(ctypes.Structure):
_fields_ = [
('x', ctypes.c_uint32),
('y', ctypes.c_uint32),
('z', ctypes.c_uint32),
('width', ctypes.c_uint32),
('height', ctypes.c_uint32),
('depth', ctypes.c_uint32),
]
raw.DvzAxisTicks = DvzAxisTicks
raw.DvzColorbarTicks = DvzColorbarTicks
raw.DvzVisualDataUpdate = DvzVisualDataUpdate
raw.DvzSampledFieldDesc = DvzSampledFieldDesc
raw.DvzFieldDataView = DvzFieldDataView
raw.DvzFieldRegion = DvzFieldRegion
raw.DVZ_FIELD_DIM_2D = 0
raw.DVZ_FIELD_DIM_3D = 1
raw.DVZ_FIELD_FORMAT_R8_UNORM = 0
raw.DVZ_FIELD_FORMAT_R32_UINT = 9
raw.DVZ_FIELD_FORMAT_R32_FLOAT = 11
raw.DVZ_FIELD_FORMAT_RGBA8_UNORM = 22
raw.DVZ_FIELD_SEMANTIC_SCALAR = 1
raw.DVZ_FIELD_SEMANTIC_COLOR = 4
raw.DVZ_FIELD_SEMANTIC_LABEL = 5
raw.DVZ_COLOR_ROLE_NONE = 0
raw.DVZ_COLOR_ROLE_SRGB_COLOR = 1
raw.DVZ_COLOR_ROLE_DATA = 3
raw.dvz_axis_set_ticks = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(DvzAxisTicks)], ctypes.c_bool
)
raw.dvz_colorbar_set_ticks = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(DvzColorbarTicks)], ctypes.c_bool
)
raw.dvz_passthrough = _raw_function([ctypes.c_int, ctypes.c_int])
raw.dvz_scene_buffer_set_data = _raw_function(
[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint64], ctypes.c_bool
)
raw.dvz_visual_set_data = _raw_function(
[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_uint32]
)
raw.dvz_visual_set_data_many = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(DvzVisualDataUpdate), ctypes.c_uint32]
)
raw.dvz_visual_set_data_range = _raw_function(
[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_uint32]
)
raw.dvz_visual_set_index_data = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint32]
)
raw.dvz_bars_set_intervals = _raw_function(
[
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.c_uint32,
]
)
raw.dvz_band_set_bounds = _raw_function(
[
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.c_uint32,
]
)
raw.dvz_band_set_center = _raw_function(
[
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.c_uint32,
]
)
raw.dvz_text_set_items = _raw_function(
[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
)
raw.dvz_text_set_positions = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_double), ctypes.c_uint32]
)
raw.dvz_text_set_offsets = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_uint32]
)
raw.dvz_text_set_anchors = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_uint32]
)
raw.dvz_text_set_sizes = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_uint32]
)
raw.dvz_text_set_colors = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint8), ctypes.c_uint32]
)
raw.dvz_text_set_angles = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_uint32]
)
raw.dvz_view_window = _raw_function(
[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_char_p],
ctypes.c_void_p,
)
raw.dvz_view_canvas = _raw_function([ctypes.c_void_p], ctypes.c_void_p)
raw.dvz_view_framebuffer_size = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32)],
None,
)
raw.dvz_canvas_capture_rgba_into = _raw_function(
[
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t,
]
)
def sampled_field_desc():
desc = DvzSampledFieldDesc()
desc.struct_size = ctypes.sizeof(DvzSampledFieldDesc)
return desc
def field_data_view():
view = DvzFieldDataView()
view.struct_size = ctypes.sizeof(DvzFieldDataView)
return view
raw.dvz_sampled_field_desc = sampled_field_desc
raw.dvz_field_data_view = field_data_view
raw.dvz_sampled_field = _raw_function(
[ctypes.c_void_p, ctypes.POINTER(DvzSampledFieldDesc)],
ctypes.c_void_p,
)
sampled_field_set_data_calls = []
def sampled_field_set_data(field, view):
sampled_field_set_data_calls.append((field, view))
return 0
sampled_field_set_data.argtypes = [ctypes.c_void_p, ctypes.POINTER(DvzFieldDataView)]
sampled_field_set_data.restype = ctypes.c_int
sampled_field_set_data.__doc__ = 'sampled field set data'
sampled_field_set_data.calls = sampled_field_set_data_calls
raw.dvz_sampled_field_set_data = sampled_field_set_data
sampled_field_update_region_calls = []
def sampled_field_update_region(field, region, view):
sampled_field_update_region_calls.append((field, region, view))
return 0
sampled_field_update_region.argtypes = [
ctypes.c_void_p,
DvzFieldRegion,
ctypes.POINTER(DvzFieldDataView),
]
sampled_field_update_region.restype = ctypes.c_int
sampled_field_update_region.__doc__ = 'sampled field update region'
sampled_field_update_region.calls = sampled_field_update_region_calls
raw.dvz_sampled_field_update_region = sampled_field_update_region
raw.__all__ = [
name for name in vars(raw) if name.startswith(('dvz_', 'Dvz', 'DVZ_'))
]
monkeypatch.setitem(sys.modules, 'datoviz.raw', raw)
sys.modules.pop('datoviz._array_facade', None)
import datoviz
datoviz.raw = raw
if '_array_facade' in datoviz.__dict__:
delattr(datoviz, '_array_facade')
facade = importlib.import_module('datoviz._array_facade')
yield raw, facade
sys.modules.pop('datoviz._array_facade', None)
def test_array_facade_exports_raw_names_and_passthrough(fake_facade):
raw, facade = fake_facade
assert 'dvz_visual_set_data' in facade.__all__
assert 'dvz_colorbar_set_ticks' in facade.__all__
assert 'dvz_passthrough' in facade.__all__
assert 'DVZ_SIZE_LOGICAL' in facade.__all__
assert facade.DvzVisual is raw.DvzVisual
assert facade.DvzSizeSpace is raw.DvzSizeSpace
assert facade.DVZ_SIZE_LOGICAL == raw.DVZ_SIZE_LOGICAL
assert facade.dvz_passthrough(1, 2) == 17
assert raw.dvz_passthrough.calls[-1] == (1, 2)
def test_top_level_package_reexports_raw_enum_constants(fake_facade):
raw, facade = fake_facade
import datoviz
assert datoviz.DVZ_SIZE_LOGICAL == raw.DVZ_SIZE_LOGICAL
assert datoviz.DvzSizeSpace is raw.DvzSizeSpace
assert 'DVZ_SIZE_LOGICAL' in dir(datoviz)
def test_visual_set_data_encodes_string_and_infers_shape0(fake_facade):
raw, facade = fake_facade
data = np.arange(6, dtype=np.float32).reshape(3, 2)
assert facade.dvz_visual_set_data(ctypes.c_void_p(1), 'position', data) == 17
visual, attr_name, pointer, item_count = raw.dvz_visual_set_data.calls[-1]
assert visual.value == 1
assert attr_name == b'position'
assert pointer.value == data.ctypes.data
assert item_count == 3
def test_view_window_encodes_title(fake_facade):
raw, facade = fake_facade
assert facade.dvz_view_window(ctypes.c_void_p(1), ctypes.c_void_p(2), 320, 240, 'Datoviz') == 17
app, figure, width, height, title = raw.dvz_view_window.calls[-1]
assert app.value == 1
assert figure.value == 2
assert width == 320
assert height == 240
assert title == b'Datoviz'
def test_visual_set_data_range_infers_count_after_first_item(fake_facade):
raw, facade = fake_facade
data = np.arange(4, dtype=np.float32).reshape(2, 2)
facade.dvz_visual_set_data_range(ctypes.c_void_p(2), 'position', 5, data)
_, attr_name, first_item, pointer, item_count = raw.dvz_visual_set_data_range.calls[-1]
assert attr_name == b'position'
assert pointer.value == data.ctypes.data
assert first_item == 5
assert item_count == 2
def test_visual_set_data_many_accepts_mapping(fake_facade):
raw, facade = fake_facade
positions = np.arange(9, dtype=np.float32).reshape(3, 3)
colors = np.full((3, 4), 255, dtype=np.uint8)
assert facade.dvz_visual_set_data_many(
ctypes.c_void_p(7), {'position': positions, 'color': colors}
) == 17
visual, updates, update_count = raw.dvz_visual_set_data_many.calls[-1]
assert visual.value == 7
assert update_count == 2
assert updates[0].attr_name == b'position'
assert updates[0].data == positions.ctypes.data
assert updates[0].item_count == 3
assert updates[1].attr_name == b'color'
assert updates[1].data == colors.ctypes.data
assert updates[1].item_count == 3
def test_visual_set_data_many_copies_non_contiguous_inputs(fake_facade):
raw, facade = fake_facade
data = np.arange(12, dtype=np.float32).reshape(3, 4)[:, ::2]
observed = {}
def inspect_many(_visual, updates, update_count):
observed['update_count'] = update_count
observed['pointer'] = updates[0].data
copied = np.ctypeslib.as_array((ctypes.c_float * data.size).from_address(updates[0].data))
observed['data'] = copied.reshape(data.shape).copy()
return 17
inspect_many.argtypes = raw.dvz_visual_set_data_many.argtypes
inspect_many.restype = raw.dvz_visual_set_data_many.restype
inspect_many.__doc__ = raw.dvz_visual_set_data_many.__doc__
inspect_many.calls = []
raw.dvz_visual_set_data_many = inspect_many
facade.dvz_visual_set_data_many(ctypes.c_void_p(8), {'position': data})
assert observed['update_count'] == 1
assert observed['pointer'] != data.ctypes.data
np.testing.assert_array_equal(observed['data'], np.ascontiguousarray(data))
def test_visual_set_data_many_rejects_inconsistent_counts_before_raw_call(fake_facade):
raw, facade = fake_facade
with pytest.raises(ValueError, match='item count 2 does not match expected 3'):
facade.dvz_visual_set_data_many(
ctypes.c_void_p(9),
{
'position': np.zeros((3, 3), dtype=np.float32),
'color': np.zeros((2, 4), dtype=np.uint8),
},
)
assert raw.dvz_visual_set_data_many.calls == []
def test_visual_set_data_many_raw_passthrough_requires_count(fake_facade):
raw, facade = fake_facade
updates = (raw.DvzVisualDataUpdate * 1)()
with pytest.raises(TypeError, match='update_count must be provided'):
facade.dvz_visual_set_data_many(ctypes.c_void_p(10), updates)
facade.dvz_visual_set_data_many(ctypes.c_void_p(10), updates, 1)
_, raw_updates, update_count = raw.dvz_visual_set_data_many.calls[-1]
assert raw_updates is updates
assert update_count == 1
def test_axis_set_ticks_accepts_values_and_labels(fake_facade):
raw, facade = fake_facade
values = np.array([0.0, 5.0, 10.0], dtype=np.float64)
assert facade.dvz_axis_set_ticks(ctypes.c_void_p(11), values, ['zero', 'five', 'ten']) == 17
axis, ticks_ptr = raw.dvz_axis_set_ticks.calls[-1]
ticks = ticks_ptr._obj
assert axis.value == 11
assert ticks.struct_size == ctypes.sizeof(raw.DvzAxisTicks)
assert ticks.flags == 0
assert ticks.count == 3
copied_values = np.ctypeslib.as_array(ticks.values, shape=(3,))
np.testing.assert_array_equal(copied_values, values)
assert [ticks.labels[i] for i in range(3)] == [b'zero', b'five', b'ten']
def test_axis_set_ticks_accepts_empty_explicit_ticks(fake_facade):
raw, facade = fake_facade
facade.dvz_axis_set_ticks(ctypes.c_void_p(12), [])
_, ticks_ptr = raw.dvz_axis_set_ticks.calls[-1]
ticks = ticks_ptr._obj
assert ticks.count == 0
assert not bool(ticks.values)
assert not bool(ticks.labels)
def test_axis_set_ticks_rejects_label_count_mismatch_before_raw_call(fake_facade):
raw, facade = fake_facade
with pytest.raises(ValueError, match='labels length 1 does not match tick count 2'):
facade.dvz_axis_set_ticks(ctypes.c_void_p(13), [0.0, 1.0], ['zero'])
assert raw.dvz_axis_set_ticks.calls == []
def test_axis_set_ticks_raw_descriptor_passthrough(fake_facade):
raw, facade = fake_facade
ticks = raw.DvzAxisTicks()
ticks.struct_size = ctypes.sizeof(raw.DvzAxisTicks)
facade.dvz_axis_set_ticks(ctypes.c_void_p(14), ctypes.byref(ticks))
_, raw_ticks = raw.dvz_axis_set_ticks.calls[-1]
assert raw_ticks._obj is ticks
with pytest.raises(TypeError, match='labels must be omitted'):
facade.dvz_axis_set_ticks(ctypes.c_void_p(14), ctypes.byref(ticks), ['unused'])
def test_colorbar_set_ticks_accepts_values_and_labels(fake_facade):
raw, facade = fake_facade
values = np.array([0.0, 0.5, 1.0], dtype=np.float64)
assert facade.dvz_colorbar_set_ticks(
ctypes.c_void_p(15), values, ['low', 'mid', 'high']
) == 17
colorbar, ticks_ptr = raw.dvz_colorbar_set_ticks.calls[-1]
ticks = ticks_ptr._obj
assert colorbar.value == 15
assert ticks.struct_size == ctypes.sizeof(raw.DvzColorbarTicks)
assert ticks.flags == 0
assert ticks.count == 3
copied_values = np.ctypeslib.as_array(ticks.values, shape=(3,))
np.testing.assert_array_equal(copied_values, values)
assert [ticks.labels[i] for i in range(3)] == [b'low', b'mid', b'high']
def test_colorbar_set_ticks_accepts_empty_explicit_ticks(fake_facade):
raw, facade = fake_facade
facade.dvz_colorbar_set_ticks(ctypes.c_void_p(16), [])
_, ticks_ptr = raw.dvz_colorbar_set_ticks.calls[-1]
ticks = ticks_ptr._obj
assert ticks.count == 0
assert not bool(ticks.values)
assert not bool(ticks.labels)
def test_colorbar_set_ticks_rejects_label_count_mismatch_before_raw_call(fake_facade):
raw, facade = fake_facade
with pytest.raises(ValueError, match='labels length 1 does not match tick count 2'):
facade.dvz_colorbar_set_ticks(ctypes.c_void_p(17), [0.0, 1.0], ['low'])
assert raw.dvz_colorbar_set_ticks.calls == []
def test_colorbar_set_ticks_raw_descriptor_passthrough(fake_facade):
raw, facade = fake_facade
ticks = raw.DvzColorbarTicks()
ticks.struct_size = ctypes.sizeof(raw.DvzColorbarTicks)
facade.dvz_colorbar_set_ticks(ctypes.c_void_p(18), ctypes.byref(ticks))
_, raw_ticks = raw.dvz_colorbar_set_ticks.calls[-1]
assert raw_ticks._obj is ticks
with pytest.raises(TypeError, match='labels must be omitted'):
facade.dvz_colorbar_set_ticks(ctypes.c_void_p(18), ctypes.byref(ticks), ['unused'])
def test_non_contiguous_visual_data_is_copied_during_call(fake_facade):
raw, facade = fake_facade
data = np.arange(12, dtype=np.float32).reshape(3, 4)[:, ::2]
observed = {}
def inspect_copy(_visual, _attr_name, pointer, item_count):
observed['item_count'] = item_count
copied = np.ctypeslib.as_array((ctypes.c_float * data.size).from_address(pointer.value))
observed['data'] = copied.reshape(data.shape).copy()
observed['pointer'] = pointer.value
return 17
inspect_copy.argtypes = raw.dvz_visual_set_data.argtypes
inspect_copy.restype = raw.dvz_visual_set_data.restype
inspect_copy.__doc__ = raw.dvz_visual_set_data.__doc__
inspect_copy.calls = []
raw.dvz_visual_set_data = inspect_copy
facade.dvz_visual_set_data(ctypes.c_void_p(3), 'position', data)
assert observed['item_count'] == 3
assert observed['pointer'] != data.ctypes.data
np.testing.assert_array_equal(observed['data'], np.ascontiguousarray(data))
def test_scene_buffer_set_data_infers_nbytes(fake_facade):
raw, facade = fake_facade
data = bytearray(b'abcd')
facade.dvz_scene_buffer_set_data(ctypes.c_void_p(4), data)
_, pointer, byte_size = raw.dvz_scene_buffer_set_data.calls[-1]
assert pointer.value is not None
assert byte_size == 4
def test_index_data_requires_declared_dtype(fake_facade):
raw, facade = fake_facade
indices = np.array([0, 2, 1], dtype=np.uint32)
facade.dvz_visual_set_index_data(ctypes.c_void_p(5), indices)
_, pointer, index_count = raw.dvz_visual_set_index_data.calls[-1]
assert ctypes.cast(pointer, ctypes.c_void_p).value == indices.ctypes.data
assert index_count == 3
with pytest.raises(ValueError, match='dtype uint32'):
facade.dvz_visual_set_index_data(ctypes.c_void_p(5), indices.astype(np.uint16))
def test_shared_count_array_facade_validates_all_arrays(fake_facade):
raw, facade = fake_facade
starts = np.array([0.0, 1.0, 2.0], dtype=np.float64)
ends = starts + 0.8
values = np.array([0.5, 0.9, 0.7], dtype=np.float64)
facade.dvz_bars_set_intervals(ctypes.c_void_p(19), starts, ends, values)
bars, starts_ptr, ends_ptr, values_ptr, count = raw.dvz_bars_set_intervals.calls[-1]
assert bars.value == 19
assert starts_ptr.contents.value == starts[0]
assert ends_ptr.contents.value == ends[0]
assert values_ptr.contents.value == values[0]
assert count == 3
with pytest.raises(ValueError, match='values count 2 does not match count 3'):
facade.dvz_bars_set_intervals(
ctypes.c_void_p(19),
starts,
ends,
values[:2],
)
assert len(raw.dvz_bars_set_intervals.calls) == 1
def test_plot_array_facade_requires_float64(fake_facade):
_raw, facade = fake_facade
with pytest.raises(ValueError, match='dtype float64'):
facade.dvz_band_set_center(
ctypes.c_void_p(20),
np.array([0.0, 1.0], dtype=np.float32),
np.array([0.5, 0.75], dtype=np.float64),
)
def test_view_capture_rgba_returns_numpy_array(fake_facade):
raw, facade = fake_facade
canvas = ctypes.c_void_p(123)
def view_canvas(_view):
return canvas
def framebuffer_size(_view, out_width, out_height):
out_width._obj.value = 3
out_height._obj.value = 2
def capture_into(_canvas, width, height, out_rgba, out_size):
assert _canvas == canvas
assert width == 3
assert height == 2
assert out_size == 24
data = np.ctypeslib.as_array(out_rgba, shape=(out_size,))
data[:] = np.arange(out_size, dtype=np.uint8)
return 0
raw.dvz_view_canvas = view_canvas
raw.dvz_view_framebuffer_size = framebuffer_size
raw.dvz_canvas_capture_rgba_into = capture_into
rgba = facade.dvz_view_capture_rgba(ctypes.c_void_p(11))
assert rgba.shape == (2, 3, 4)
assert rgba.dtype == np.uint8
np.testing.assert_array_equal(rgba.ravel(), np.arange(24, dtype=np.uint8))
def test_view_capture_rgba_raises_on_missing_canvas(fake_facade):
raw, facade = fake_facade
raw.dvz_view_canvas = lambda _view: None
with pytest.raises(RuntimeError, match='no canvas'):
facade.dvz_view_capture_rgba(ctypes.c_void_p(12))
def test_view_capture_rgba_raises_on_capture_failure(fake_facade):
raw, facade = fake_facade
raw.dvz_view_canvas = lambda _view: ctypes.c_void_p(123)
def framebuffer_size(_view, out_width, out_height):
out_width._obj.value = 1
out_height._obj.value = 1
raw.dvz_view_framebuffer_size = framebuffer_size
raw.dvz_canvas_capture_rgba_into = lambda *_args: -7
with pytest.raises(RuntimeError, match='code -7'):
facade.dvz_view_capture_rgba(ctypes.c_void_p(13))
def test_sampled_field_from_rgba_array_infers_desc_and_upload_view(fake_facade):
raw, facade = fake_facade
rgba = np.arange(3 * 4 * 4, dtype=np.uint8).reshape(3, 4, 4)
assert facade.dvz_sampled_field_from_array(ctypes.c_void_p(21), rgba) == 17
scene, desc_ptr = raw.dvz_sampled_field.calls[-1]
desc = desc_ptr._obj
assert scene.value == 21
assert desc.dim == raw.DVZ_FIELD_DIM_2D
assert desc.format == raw.DVZ_FIELD_FORMAT_RGBA8_UNORM
assert desc.semantic == raw.DVZ_FIELD_SEMANTIC_COLOR
assert desc.color_role == raw.DVZ_COLOR_ROLE_SRGB_COLOR
assert (desc.width, desc.height, desc.depth) == (4, 3, 1)
field, view_ptr = raw.dvz_sampled_field_set_data.calls[-1]
view = view_ptr._obj
assert field == 17
assert view.data == rgba.ctypes.data
assert view.bytes_per_row == rgba.strides[0]
assert view.rows_per_image == 0
def test_sampled_field_from_scalar_3d_array_uses_volume_layout(fake_facade):
raw, facade = fake_facade
volume = np.zeros((5, 3, 4), dtype=np.float32)
facade.dvz_sampled_field_from_array(ctypes.c_void_p(22), volume)
_, desc_ptr = raw.dvz_sampled_field.calls[-1]
desc = desc_ptr._obj
assert desc.dim == raw.DVZ_FIELD_DIM_3D
assert desc.format == raw.DVZ_FIELD_FORMAT_R32_FLOAT
assert desc.semantic == raw.DVZ_FIELD_SEMANTIC_SCALAR
assert desc.color_role == raw.DVZ_COLOR_ROLE_DATA
assert (desc.width, desc.height, desc.depth) == (4, 3, 5)
_, view_ptr = raw.dvz_sampled_field_set_data.calls[-1]
view = view_ptr._obj
assert view.bytes_per_row == volume.strides[1]
assert view.rows_per_image == 3
def test_sampled_field_from_non_contiguous_array_copies_before_upload(fake_facade):
raw, facade = fake_facade
data = np.arange(24, dtype=np.float32).reshape(3, 8)[:, ::2]
facade.dvz_sampled_field_from_array(ctypes.c_void_p(23), data)
_, desc_ptr = raw.dvz_sampled_field.calls[-1]
desc = desc_ptr._obj
assert (desc.width, desc.height, desc.depth) == (4, 3, 1)
_, view_ptr = raw.dvz_sampled_field_set_data.calls[-1]
view = view_ptr._obj
assert view.data != data.ctypes.data
uploaded = np.ctypeslib.as_array((ctypes.c_float * data.size).from_address(view.data))
np.testing.assert_array_equal(uploaded.reshape(data.shape), np.ascontiguousarray(data))
def test_sampled_field_rejects_unsupported_shape_without_raw_call(fake_facade):
raw, facade = fake_facade
with pytest.raises(ValueError, match='unsupported sampled-field dtype/shape'):
facade.dvz_sampled_field_from_array(ctypes.c_void_p(24), np.zeros((3, 4), np.float64))
assert raw.dvz_sampled_field.calls == []
def test_sampled_field_update_from_array_builds_region_and_view(fake_facade):
raw, facade = fake_facade
patch = np.arange(12 * 12, dtype=np.float32).reshape(12, 12)
field = ctypes.c_void_p(31)
assert facade.dvz_sampled_field_update_from_array(field, patch, offset=(4, 5)) is field
field_arg, region, view_ptr = raw.dvz_sampled_field_update_region.calls[-1]
view = view_ptr._obj
assert field_arg is field
assert (region.x, region.y, region.z) == (4, 5, 0)
assert (region.width, region.height, region.depth) == (12, 12, 1)
assert view.data == patch.ctypes.data
assert view.bytes_per_row == patch.strides[0]
assert view.rows_per_image == 0
def test_sampled_field_update_from_non_contiguous_array_copies_patch(fake_facade):
raw, facade = fake_facade
patch = np.arange(8 * 6, dtype=np.float32).reshape(6, 8)[:, ::2]
facade.dvz_sampled_field_update_from_array(ctypes.c_void_p(32), patch, offset=(1, 2, 0))
_, region, view_ptr = raw.dvz_sampled_field_update_region.calls[-1]
view = view_ptr._obj
assert (region.width, region.height, region.depth) == (4, 6, 1)
assert view.data != patch.ctypes.data
uploaded = np.ctypeslib.as_array((ctypes.c_float * patch.size).from_address(view.data))
np.testing.assert_array_equal(uploaded.reshape(patch.shape), np.ascontiguousarray(patch))
def test_sampled_field_update_rejects_mismatched_extent_without_raw_call(fake_facade):
raw, facade = fake_facade
with pytest.raises(ValueError, match='does not match array-derived extent'):
facade.dvz_sampled_field_update_from_array(
ctypes.c_void_p(33),
np.zeros((3, 4), dtype=np.float32),
offset=(0, 0),
extent=(5, 3),
)
assert raw.dvz_sampled_field_update_region.calls == []
def test_top_level_datoviz_resolves_facade_lazily(fake_facade):
raw, _facade = fake_facade
import datoviz
assert datoviz.dvz_visual_set_data(ctypes.c_void_p(6), 'color', np.zeros((1, 4), np.uint8)) == 17
assert raw.dvz_visual_set_data.calls[-1][1] == b'color'