Skip to content

Render

The rendering backend turns scenes and visuals into pixels, coordinating slicing requests and per-canvas state.

Managers

cellier.render.RenderManager

Single top-level render-layer object.

Owns the scene registry, canvas registry, shared async slicer, and slice coordinator. Exposes three reslicing entry points that cover the common triggers: all scenes, one scene, or one visual.

Construction is parameter-free; scenes, canvases, and visuals are registered via the add_* methods.

config property

Current rendering performance configuration.

Reflects live state: mutations via temporal_alpha and temporal_enabled setters are visible here immediately.

temporal_alpha property writable

temporal_alpha: float

EMA floor weight for temporal accumulation.

temporal_enabled property writable

temporal_enabled: bool

Whether the temporal accumulation pass is active.

connect_event_bus

connect_event_bus(event_bus: EventBus) -> None

Subscribe internal components to event_bus.

Must be called before the caller registers its own DimsChangedEvent handler so the SliceCoordinator invalidates stale 2D caches first.

add_scene

add_scene(scene_id: UUID, lighting: str = 'none') -> SceneManager

Create and register a new scene.

Parameters:

Name Type Description Default
scene_id UUID

Unique identifier for the scene.

required
lighting str

"none" (default) or "default". Pass "default" to add ambient and directional lights — required for MeshPhongAppearance.

'none'

Returns:

Type Description
SceneManager

The newly created scene manager.

scene_has_lighting

scene_has_lighting(scene_id: UUID) -> bool

Return True if scene_id was created with lighting enabled.

add_canvas

add_canvas(canvas_id: UUID, scene_id: UUID, parent: QWidget | None = None, **canvas_view_kwargs) -> CanvasView

Create a CanvasView, register it, and return it.

The caller embeds canvas_view.widget in their Qt layout.

Parameters:

Name Type Description Default
canvas_id UUID

Unique identifier for this canvas.

required
scene_id UUID

ID of the scene this canvas should render.

required
parent QWidget or None

Parent widget for the underlying QRenderWidget.

None
**canvas_view_kwargs

Additional keyword arguments forwarded to CanvasView.__init__ (e.g. dim, fov, depth_range, gui, size).

{}

Returns:

Type Description
CanvasView

The newly created canvas view.

add_visual

add_visual(scene_id: UUID, visual: _GFXVisual, data_store: BaseDataStore, displayed_axes: tuple[int, ...]) -> None

Register a visual with a scene and its associated data store.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene to add the visual to.

required
visual _GFXVisual

The render-layer visual object.

required
data_store BaseDataStore

The data store that will serve chunk data for this visual.

required
displayed_axes tuple[int, ...]

Current displayed axes from the scene's dims selection. Passed to SceneManager.add_visual to select the initial node.

required

add_canvas_overlay

add_canvas_overlay(canvas_id: UUID, gfx_overlay: GFXCanvasOverlay) -> None

Attach a pre-built GFX overlay to canvas_id.

Parameters:

Name Type Description Default
canvas_id UUID

ID of the canvas that should receive the overlay.

required
gfx_overlay GFXCanvasOverlay

The fully-constructed render-layer overlay.

required

Raises:

Type Description
KeyError

If canvas_id is not registered.

set_pick_details_enabled

set_pick_details_enabled(canvas_id: UUID, enabled: bool) -> None

Enable or disable element-level pick extraction for one canvas.

When disabled (the default), pointer events still carry hit_visual_id but pick_details is left None so non-picking consumers do not pay for the per-type dispatch.

Parameters:

Name Type Description Default
canvas_id UUID

The canvas whose extraction state should change.

required
enabled bool

True to extract typed VisualPickDetails on each pointer event.

required

remove_visual

remove_visual(visual_id: UUID) -> None

Remove a visual from its scene and deregister it.

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual to remove.

required

remove_scene

remove_scene(scene_id: UUID) -> None

Remove a scene and all its visuals and canvases.

Visuals are released by dropping references (pygfx has no explicit destroy API), but each canvas is closed explicitly -- see :meth:CanvasView.close, which GC alone cannot substitute for.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene to remove.

required

remove_canvas

remove_canvas(canvas_id: UUID) -> None

Remove a single canvas, closing it and dropping its references.

Parameters:

Name Type Description Default
canvas_id UUID

ID of the canvas to remove.

required

Raises:

Type Description
KeyError

If canvas_id is not registered.

close

close() -> None

Close every registered canvas and drop the render references.

Safe to call more than once.

get_scene

get_scene(scene_id: UUID) -> Scene

Return the pygfx Scene for scene_id.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene to retrieve.

required

Returns:

Type Description
Scene

reslice_scene

reslice_scene(scene_id: UUID, dims_state: DimsState, visual_configs: dict[UUID, VisualRenderConfig] | None = None, target_visual_ids: frozenset[UUID] | None = None) -> None

Reslice all visuals in one scene.

One reslicing request is submitted per registered canvas so that each canvas uses its own camera state for LOD and frustum-culling decisions.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene to reslice.

required
dims_state DimsState

Current dimension display state.

required
visual_configs dict[UUID, VisualRenderConfig] or None

Per-visual render configuration. None falls back to defaults.

None
target_visual_ids frozenset[UUID] or None

None reslices all visuals in the scene.

None

reslice_visual

reslice_visual(visual_id: UUID, dims_state: DimsState, visual_config: VisualRenderConfig | None = None) -> None

Reslice one visual.

Looks up which scene owns visual_id, then submits one ReslicingRequest per registered canvas so that each canvas uses its own camera state.

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual to reslice.

required
dims_state DimsState

Current dimension display state.

required
visual_config VisualRenderConfig or None

Render configuration for this visual. None uses defaults.

None

look_at_visual

look_at_visual(visual_id: UUID, canvas_id: UUID, view_direction: tuple[float, float, float] = (-1, -1, -1), up: tuple[float, float, float] = (0, 0, 1)) -> None

Fit a canvas camera to a visual's bounding box.

Parameters:

Name Type Description Default
visual_id UUID

ID of the target visual.

required
canvas_id UUID

ID of the canvas whose camera should be fitted.

required
view_direction tuple[float, float, float]

Camera look direction vector (need not be normalized).

(-1, -1, -1)
up tuple[float, float, float]

Camera up vector.

(0, 0, 1)

set_camera_depth_range

set_camera_depth_range(canvas_id: UUID, depth_range: tuple[float, float]) -> None

Set the near/far clip distances for a canvas camera.

Parameters:

Name Type Description Default
canvas_id UUID

ID of the target canvas.

required
depth_range tuple[float, float]

(near, far) clip distances in world units.

required

cellier.render.SceneManager

Owns one pygfx gfx.Scene and the registry of visuals attached to it.

Which node (2D or 3D) is active is determined at runtime from the dims_state carried by each ReslicingRequest, not fixed at construction time.

Parameters:

Name Type Description Default
scene_id UUID

Unique identifier for this scene.

required

has_lighting property

has_lighting: bool

True if this scene was created with lighting enabled.

scene_id property

scene_id: UUID

Unique identifier for this scene.

scene property

scene: Scene

The pygfx Scene object. Passed to CanvasView via get_scene_fn.

visual_ids property

visual_ids: list[UUID]

IDs of all registered visuals.

add_visual

add_visual(visual: _GFXVisual, displayed_axes: tuple[int, ...]) -> None

Register a visual and add its initial node to the scene graph.

Calls visual.get_node_for_dims(displayed_axes) to select the correct node, then stores it in _active_nodes and adds it to the pygfx scene.

Parameters:

Name Type Description Default
visual _GFXVisual

The GFX visual to register.

required
displayed_axes tuple[int, ...]

Current displayed axes from the scene's dims selection.

required

Raises:

Type Description
ValueError

If get_node_for_dims returns None.

get_active_node

get_active_node(visual_id: UUID) -> WorldObject | None

Return the node currently active in the scene for visual_id.

Returns None if the visual has not yet been registered or if its active node is None.

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual to query.

required

swap_node

swap_node(visual_id: UUID, new_node: WorldObject | None) -> None

Replace the active scene-graph node for visual_id.

Removes the previously active node and adds new_node. If old_node is new_node (single-node visuals such as mesh and lines), the scene graph is not touched — the node stays in the scene and the subsequent reslice updates its content.

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual whose node is being swapped.

required
new_node WorldObject or None

The node that should be active after this call.

required

remove_visual

remove_visual(visual_id: UUID) -> None

Unregister a visual and remove its node from the scene graph.

Uses _active_nodes to identify which node is currently in the scene and removes only that one. Dropping the visual from _visuals releases references to both node_3d and node_2d so GC can collect both nodes' GPU resources (pygfx has no explicit destroy API).

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual to remove.

required

get_visual_id_for_node

get_visual_id_for_node(node: WorldObject) -> UUID | None

Return the visual_id whose active scene-graph node is node.

The pick buffer returns leaf nodes (e.g. gfx.Image inside a gfx.Group), while _active_nodes stores the top-level group. This method walks up the parent chain of node until it finds a registered active node, then returns its visual_id. Returns None if no ancestor belongs to any registered visual.

Parameters:

Name Type Description Default
node WorldObject

The pygfx object returned by the pick buffer.

required

get_visual

get_visual(visual_id: UUID) -> _GFXVisual

Return the registered visual for visual_id.

Parameters:

Name Type Description Default
visual_id UUID

ID of the visual to retrieve.

required

Returns:

Type Description
_GFXVisual

Raises:

Type Description
KeyError

If visual_id is not registered in this scene.

build_slice_requests

build_slice_requests(request: ReslicingRequest, visual_configs: dict[UUID, VisualRenderConfig]) -> dict[UUID, list[ChunkRequest]]

Collect ChunkRequests from all (or targeted) registered visuals.

Dispatches to the 2D or 3D planning path based on scene dimensionality.

Parameters:

Name Type Description Default
request ReslicingRequest

The reslicing request.

required
visual_configs dict[UUID, VisualRenderConfig]

Per-visual render configuration.

required

Returns:

Type Description
dict[UUID, list[ChunkRequest]]

Mapping of visual_model_id to that visual's ChunkRequests.

cellier.render.SliceCoordinator

Thin orchestrator owned by RenderManager.

Given a ReslicingRequest, it looks up the target SceneManager, runs the synchronous planning phase, cancels in-flight tasks for the affected visuals, and submits new async load tasks.

One AsyncSlicer task maps to one (scene_id, canvas_id, visual_id) triple. A dict keyed by this triple tracks active slice IDs so that per-visual cancellation can cancel only the affected task while leaving other visuals — and other canvases — in the same scene running.

Parameters:

Name Type Description Default
scenes dict[UUID, SceneManager]

Shared scene registry from RenderManager.

required
slicer AsyncSlicer

Shared async slicer instance.

required
data_stores dict[UUID, MultiscaleZarrDataStore]

Mapping of visual_model_id to the data store for that visual.

required

submit

submit(request: ReslicingRequest, visual_configs: dict[UUID, VisualRenderConfig]) -> None

Execute the full reslicing cycle for the scene in request.scene_id.

Cancels in-flight tasks for visuals that will be re-submitted, subject to each visual's cancellable property. Visuals with cancellable = False are never cancelled; their tasks run to completion so every intermediate position reaches the GPU. This is the case for the static-geometry in-memory visuals (mesh, lines, points). The image and label visuals -- both in-memory (GFXImageMemoryVisual, GFXLabelMemoryVisual) and multiscale (GFXMultiscaleImageVisual, GFXMultiscaleLabelVisual) -- default to cancellable = True, so a superseding reslice cancels their in-flight reads. All render-layer visual classes must expose cancellable as part of their public API; an AttributeError indicates a missing implementation.

Parameters:

Name Type Description Default
request ReslicingRequest

The reslicing request to process.

required
visual_configs dict[UUID, VisualRenderConfig]

Per-visual render configuration.

required

cancel_scene

cancel_scene(scene_id: UUID) -> None

Cancel all in-flight tasks for a scene.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene whose tasks should be cancelled.

required

cancel_visual

cancel_visual(scene_id: UUID, canvas_id: UUID, visual_id: UUID) -> None

Cancel the in-flight task for one visual on one canvas.

Also calls visual.cancel_pending() or cancel_pending_2d() to release any GPU slots reserved during the last planning phase that were never committed.

Parameters:

Name Type Description Default
scene_id UUID

ID of the scene containing the visual.

required
canvas_id UUID

ID of the canvas whose request should be cancelled.

required
visual_id UUID

ID of the visual to cancel.

required

cellier.render.CanvasView

Owns one rendered canvas: widget, renderer, camera, and controller.

Responsible for rendering one scene from one camera viewpoint. CanvasView does not hold a direct reference to the scene graph; instead it receives a get_scene_fn callable that is invoked each frame so ownership of the scene stays with SceneManager.

Camera change detection is implemented by comparing a cached CameraState snapshot each frame in _draw_frame. The _applying_model_state flag suppresses detection during programmatic camera updates to prevent feedback loops.

Parameters:

Name Type Description Default
canvas_id UUID

Unique identifier for this canvas.

required
scene_id UUID

ID of the scene this canvas renders.

required
get_scene_fn Callable[[UUID], Scene]

Called each frame to retrieve the current scene. Provided by RenderManager at construction time.

required
dim str

Scene dimensionality: "2d" or "3d". Controls which camera type and interaction controller are used.

'3d'
parent QWidget or None

Parent widget for the underlying QRenderWidget.

None
fov float

Vertical field of view in degrees (3D perspective only).

70.0
depth_range tuple[float, float]

Near and far clip distances (near, far).

(1.0, 8000.0)

canvas_id property

canvas_id: UUID

Unique identifier for this canvas.

scene_id property

scene_id: UUID

ID of the scene this canvas renders.

widget property

widget: object

The render canvas element to embed in the application layout.

A QRenderWidget for the Qt backend or an AnywidgetRenderCanvas for the anywidget backend.

camera property

camera: Camera

The active pygfx camera for this canvas.

close

close() -> None

Close the canvas, stopping its draw loop and releasing the GPU.

Dropping the last Python reference to a CanvasView is not enough to reclaim it. The canvas is a parentless (top-level) render widget, so the backend owns it and keeps it alive; through its draw callback and event filter it in turn pins this view, the WgpuRenderer, and the whole object graph they reach. Closing the canvas is what breaks that chain, after which normal refcounting reclaims everything.

Safe to call more than once, and safe when the GUI backend has already destroyed the canvas itself (e.g. the user closed the window).

capture_reslicing_request

capture_reslicing_request(dims_state: DimsState, target_visual_ids: frozenset[UUID] | None = None) -> ReslicingRequest

Snapshot the current camera state into a ReslicingRequest.

All array fields are copied. Screen size is read from the canvas at call time and baked into the returned request.

Parameters:

Name Type Description Default
dims_state DimsState

Current dimension display state.

required
target_visual_ids frozenset[UUID] or None

None reslices all visuals in the scene.

None

Returns:

Type Description
ReslicingRequest

Fully populated snapshot with independent array copies.

set_depth_range

set_depth_range(depth_range: tuple[float, float]) -> None

Set the active camera near/far clip distances.

Parameters:

Name Type Description Default
depth_range tuple[float, float]

(near, far) clip distances in world units.

required

set_depth_range_for_dim

set_depth_range_for_dim(dim: str, depth_range: tuple[float, float]) -> None

Set the near/far clip distances on the 2D or 3D camera.

Unlike :meth:set_depth_range, this targets a specific camera regardless of which is currently active. Both the 2D orthographic and 3D perspective cameras are created up front (see __init__), so the reserve camera must have its depth range set independently — otherwise it keeps the active camera's range, which for a 2D->3D toggle leaves the perspective camera with an invalid (e.g. negative) near plane and renders nothing.

Parameters:

Name Type Description Default
dim str

"2d" or "3d".

required
depth_range tuple[float, float]

(near, far) clip distances in world units.

required

show_object

show_object(scene: Scene) -> None

Fit the camera to the scene bounding box and mark this dim as fitted.

Parameters:

Name Type Description Default
scene Scene

The scene to fit the camera to.

required

add_overlay

add_overlay(overlay: GFXCanvasOverlay) -> None

Attach a screen-space overlay to this canvas.

The overlay is rendered as an additional post-pass on top of the main scene each frame. Multiple overlays are rendered in insertion order.

Parameters:

Name Type Description Default
overlay GFXCanvasOverlay

The render-layer overlay to attach.

required

request_draw

request_draw() -> None

Request a redraw of the canvas.

apply_camera_state

apply_camera_state(request: ReslicingRequest) -> None

Apply a camera snapshot from the model layer (programmatic move).

The _applying_model_state guard prevents the resulting camera setter calls from firing _on_controller_event, which would otherwise cause a feedback loop: model change -> apply to pygfx -> controller event -> model change -> ...

Parameters:

Name Type Description Default
request ReslicingRequest

Camera snapshot to apply.

required

set_event_bus

set_event_bus(event_bus: EventBus) -> None

Wire the EventBus after construction.

set_controller_enabled

set_controller_enabled(enabled: bool) -> None

Enable or disable the active camera controller for this canvas.

self._controller already points to the currently active controller (_controller_2d or _controller_3d depending on the canvas dim), so this correctly targets whichever type is in use.

Parameters:

Name Type Description Default
enabled bool

False disables the controller (paint mode active). True restores normal camera interaction.

required

switch_dim

switch_dim(new_dim: str) -> bool

Switch the canvas between "2d" and "3d" rendering modes.

Disables the current controller and enables the one for new_dim. Camera pose is preserved across toggles.

Parameters:

Name Type Description Default
new_dim str

"2d" or "3d".

required

Returns:

Type Description
bool

True if this is the first time new_dim has been activated on this canvas (caller should call show_object to fit the camera). False if the camera pose was already set by a previous visit.

capture_camera_state

capture_camera_state() -> CameraState

Snapshot the current pygfx camera into a CameraState NamedTuple.

Config

cellier.render.CameraConfig pydantic-model

Bases: BaseModel

Configuration for camera-driven automatic reslicing.

Parameters:

Name Type Description Default
reslice_enabled bool

When False camera movement never triggers a reslice. Manual calls to CellierController.reslice_scene still work.

required
settle_threshold_s float

Seconds of camera stillness required before a reslice is triggered. Lower values give more responsive LOD updates; higher values reduce redundant I/O during fast panning.

required

Fields:

  • reslice_enabled (bool)
  • settle_threshold_s (float)

cellier.render.RenderManagerConfig pydantic-model

Bases: BaseModel

Top-level rendering performance configuration.

Pass an instance to CellierController(render_config=...) at construction time. The live state is always accessible and serializable via render_manager.config.

Parameters:

Name Type Description Default
slicing SlicingConfig

Async chunk-slicing pipeline settings.

required
temporal TemporalAccumulationConfig

Temporal accumulation pass settings.

required
camera CameraConfig

Camera-driven reslicing settings.

required

Examples:

Construct with custom settings and serialize:

>>> config = RenderManagerConfig(
...     slicing=SlicingConfig(batch_size=32, render_every=4),
...     temporal=TemporalAccumulationConfig(alpha=0.05),
...     camera=CameraConfig(settle_threshold_s=0.5),
... )
>>> json_str = config.model_dump_json()
>>> config2 = RenderManagerConfig.model_validate_json(json_str)

Fields:

cellier.render.SlicingConfig pydantic-model

Bases: BaseModel

Configuration for the async chunk-slicing pipeline.

These parameters are construction-time only. Changing them after RenderManager is created has no effect.

Parameters:

Name Type Description Default
batch_size int

Number of chunks fetched concurrently in each async batch. Higher values increase throughput but raise peak memory pressure.

required
render_every int

Number of completed batches between progressive redraws. 1 = redraw after every batch (lowest latency to first pixels); higher values reduce GPU upload overhead on fast I/O.

required

Fields:

  • batch_size (int)
  • render_every (int)

cellier.render.TemporalAccumulationConfig pydantic-model

Bases: BaseModel

Configuration for the temporal accumulation post-processing pass.

Parameters:

Name Type Description Default
enabled bool

When False the pass is bypassed entirely and each frame is shown raw. Useful for debugging or when jitter is disabled.

required
alpha float

Minimum EMA blend weight for the current frame. During warm-up the weight is 1 / (frame_count + 1); once that falls below alpha the weight clamps to alpha. Lower values give smoother steady-state but slower convergence after a camera move. Must be in (0, 1].

required

Fields:

Requests

cellier.render.DimsState

Bases: NamedTuple

Current dimension display state for a scene.

cellier.render.ReslicingRequest

Bases: NamedTuple

Frozen snapshot driving a complete reslicing cycle.

Constructed by CanvasView.capture_reslicing_request() and consumed by SliceCoordinator. All array fields must be .copy()d by the caller — the NamedTuple does not enforce this.

Parameters:

Name Type Description Default
camera_type str

"perspective" or "orthographic".

required
camera_pos (ndarray, shape(3))

World-space camera position, copy.

required
frustum_corners (ndarray, shape(2, 4, 3))

World-space frustum corners, copy. For orthographic cameras this is set to np.zeros((2, 4, 3)).

required
fov_y_rad float

Vertical field of view in radians. 0.0 for orthographic.

required
screen_size_px tuple[float, float]

Logical (width, height) in pixels, baked in at snapshot time.

required
world_extent tuple[float, float]

Visible (width, height) in world units for orthographic cameras. (0.0, 0.0) for perspective cameras.

required
dims_state DimsState

Current dimension display state.

required
request_id UUID

Unique identifier per trigger; used for cancellation.

required
scene_id UUID

Which scene this camera belongs to.

required
canvas_id UUID

Which canvas produced this request. Used by SliceCoordinator to key cancellation entries so that requests from different canvases rendering the same scene can be tracked and cancelled independently.

required
target_visual_ids frozenset[UUID] or None

None means reslice all visuals in the scene (camera-moved case). A non-None set means reslice only those specific visuals (data-updated case).

required

Scene config

cellier.render.VisualRenderConfig dataclass

Mutable render settings for one visual.

Passed through the call stack at reslice time. When a visual's ID is absent from the visual_configs dict supplied to SceneManager.build_slice_requests, a default instance is used.

Parameters:

Name Type Description Default
lod_bias float

Multiplier applied to LOD distance thresholds. Values greater than 1.0 favour finer (higher-resolution) levels at a given camera distance; values less than 1.0 favour coarser levels. Default 1.0 (no bias).

1.0
force_level int or None

When set, all bricks are assigned this 1-based LOD level, bypassing distance-based selection entirely. None restores automatic selection. Default None.

None
frustum_cull bool

When True, bricks outside the camera frustum are skipped. When False, all bricks in the scene are submitted regardless of visibility. Default True.

True

Temporal accumulation

cellier.render.TemporalAccumulationPass

Bases: EffectPass

Post-processing pass that accumulates jittered frames over time.

Insert as the first entry in renderer.effect_passes so it operates on the raw raymarched output before anti-aliasing.

Parameters:

Name Type Description Default
alpha float

Minimum blend weight for the current frame. During warm-up the weight is 1 / (frame_count + 1); once that falls below alpha the weight clamps to alpha. Lower values give smoother steady-state but slower convergence. Default 0.1 (steady state reached in ~10 still frames).

0.1

alpha property writable

alpha: float

Minimum blend weight for the current frame (EMA floor).

Lower values give smoother steady-state at the cost of slower convergence after a reset.

reset

reset() -> None

Discard accumulated history.

The next frame's blend weight becomes 1.0, so history is immediately overwritten. No GPU memory is freed or cleared.