Skip to content

ParaView backend

quiltwright.paraview

ParaView state files, via headless pvpython

A scene composed in ParaView <https://www.paraview.org/>_ -- the filter pipeline, the colour maps, the opacity transfer functions, the representation settings, the camera -- lives in a .pvsm state file. None of that survives exporting the data to a .vtm and reading it back into PyVista: the geometry comes across and everything the user spent the afternoon on does not. This module takes the other route. It hands the state file back to ParaView's own Python interpreter, pvpython, and sweeps the camera of the render view in place, so the frames are of the session the user actually built.

The sweep is the same one every backend makes. For each view the camera position and focal point translate together along the camera's right vector, no rotation, and vtkCamera.SetWindowCenter shears the frustum back so the original focal point stays centred -- the off-axis recipe of :func:~quiltwright.lfd.render_quilt, applied to the vtkCamera ParaView owns instead of the one PyVista owns. The frames are then tiled by :func:~quiltwright.quilt.assemble_quilt, as the POV-Ray and Cycles frames are.

ParaView is optional, and stays external. It is a 500 MB desktop application with its own Python, not a package a virtualenv can import, so this module drives it by subprocess only and finds the binary through :func:find_pvpython. Everything that can be done in a normal interpreter is done here, where it is tested without ParaView present; the script that runs inside pvpython is short and does nothing it does not have to.

What has been verified, and what has not. SetWindowCenter survives ParaView's Render() in the default builtin server mode (what pvpython runs on its own, a laptop session) and over a genuine single-process client-server connection -- a separate pvserver reached with Connect(), the shape this module actually asks for. Whether the shear survives IceT compositing across multiple MPI ranks is still untested: ParaView syncs camera position, focal point, view-up and view angle to each rank's remote server through named proxy properties, and WindowCenter is not among them, so a distributed-render sweep may come back un-sheared.

Typical use::

from quiltwright import QUILT_PRESETS, save_quilt
from quiltwright.paraview import render_paraview_quilt

spec = QUILT_PRESETS["portrait"]
quilt = render_paraview_quilt("session.pvsm", spec)
save_quilt(quilt, "session", spec)

Part of Quiltwright -- https://github.com/Flux-Frontiers/quiltwright Author: Eric G. Suchanek, PhD

ParaViewCamera(view, position, focal_point, view_up, fov, focal_distance, depths) dataclass

The render view's camera, as the sweep will use it.

What :func:probe_paraview_state reads back out of the state after the same framing :func:render_paraview_views applies -- the FOV narrowed and the camera dollied back, then the zoom dolly -- so the numbers describe the render about to be made, not the session as saved. Satisfies :class:~quiltwright.quilt.HasLens, so it goes straight to :func:~quiltwright.povray.format_depth_budget.

Parameters:

Name Type Description Default
view str

Name of the render view swept. A state can hold several; the active one is used if it is a render view, else the first.

required
position tuple[float, float, float]

Eye, in scene units, after framing.

required
focal_point tuple[float, float, float]

Look-at point, which becomes the holographic focal plane.

required
view_up tuple[float, float, float]

Camera up vector.

required
fov float

Vertical field of view in degrees, after framing.

required
focal_distance float

Eye to focal plane, in scene units, after framing.

required
depths Mapping[str, float | None]

Labelled near / focal / far distances along the view axis, from the bounds of every visible source. Near and far are None when nothing visible reports bounds.

required

ParaViewNotAvailable

Bases: RuntimeError

Raised when no pvpython can be found.

available()

Path to pvpython if one can be found, else None.

Source code in src/quiltwright/paraview.py
129
130
131
132
133
134
def available() -> str | None:
    """Path to ``pvpython`` if one can be found, else ``None``."""
    try:
        return find_pvpython()
    except ParaViewNotAvailable:
        return None

depth_report(camera, spec, *, soft_px=5.5)

Depth budget for a probed state, as a report to print before rendering.

The ParaView counterpart to :func:quiltwright.lfd.depth_report.

Parameters:

Name Type Description Default
camera ParaViewCamera

From :func:probe_paraview_state, with the same fov and zoom the render will use.

required
spec QuiltSpec

Quilt specification.

required
soft_px float

Disparity above which a row is flagged as soft.

5.5

Returns:

Type Description
str

Multi-line report.

Source code in src/quiltwright/paraview.py
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
def depth_report(camera: ParaViewCamera, spec: QuiltSpec, *, soft_px: float = 5.5) -> str:
    """Depth budget for a probed state, as a report to print before rendering.

    The ParaView counterpart to :func:`quiltwright.lfd.depth_report`.

    :param camera: From :func:`probe_paraview_state`, with the same *fov* and
        *zoom* the render will use.
    :param spec: Quilt specification.
    :param soft_px: Disparity above which a row is flagged as soft.
    :return: Multi-line report.
    """
    from .povray import format_depth_budget

    depths = {k: v for k, v in camera.depths.items() if v is not None}
    return format_depth_budget(spec, camera, depths, soft_px=soft_px)

find_pvpython(binary=None)

Locate the pvpython executable.

Parameters:

Name Type Description Default
binary str | None

Explicit path or command name; falls back to the :data:PARAVIEW_ENV environment variable, then pvpython on PATH, then the macOS application bundle.

None

Returns:

Type Description
str

Path to the executable.

Raises:

Type Description
ParaViewNotAvailable

If none of those turn one up.

Source code in src/quiltwright/paraview.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def find_pvpython(binary: str | None = None) -> str:
    """Locate the ``pvpython`` executable.

    :param binary: Explicit path or command name; falls back to the
        :data:`PARAVIEW_ENV` environment variable, then ``pvpython`` on
        ``PATH``, then the macOS application bundle.
    :return: Path to the executable.
    :raises ParaViewNotAvailable: If none of those turn one up.
    """
    candidate = binary or os.environ.get(PARAVIEW_ENV)
    if candidate:
        found = shutil.which(candidate)
        if found:
            return found
        raise ParaViewNotAvailable(f"pvpython {candidate!r} not found or not executable.")
    found = shutil.which("pvpython")
    if found:
        return found
    bundles = sorted(glob.glob(_MAC_BUNDLES))
    if bundles:
        return bundles[-1]
    raise ParaViewNotAvailable(
        "ParaView's pvpython is not on PATH and no /Applications/ParaView*.app was found.\n"
        "  brew install --cask paraview                # macOS\n"
        "  https://www.paraview.org/download/          # binaries for every platform\n"
        f"or set {PARAVIEW_ENV} to the full path of pvpython."
    )

probe_paraview_state(state, spec, *, fov=14.0, zoom=None, binary=None)

Read the camera and depth range a sweep of state would use.

Loads the state in pvpython without rendering a frame, applies the same framing :func:render_paraview_views will, and reports the result. Print :func:depth_report from it before paying for the sweep; a ParaView startup costs a few seconds, a 48-view sweep of a large dataset costs a good deal more.

Parameters:

Name Type Description Default
state str | Path

The .pvsm file. Not modified.

required
spec QuiltSpec

Quilt specification; only its aspect matters here.

required
fov float | None

Vertical FOV the sweep will use, with the :func:~quiltwright.lfd.render_quilt dolly-back convention. None keeps the state's own.

14.0
zoom float | None

Zoom the sweep will use.

None
binary str | None

pvpython executable; see :func:find_pvpython.

None

Returns:

Type Description
ParaViewCamera

The framed camera and its depth range.

Source code in src/quiltwright/paraview.py
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
def probe_paraview_state(
    state: str | Path,
    spec: QuiltSpec,
    *,
    fov: float | None = 14.0,
    zoom: float | None = None,
    binary: str | None = None,
) -> ParaViewCamera:
    """Read the camera and depth range a sweep of *state* would use.

    Loads the state in ``pvpython`` without rendering a frame, applies the
    same framing :func:`render_paraview_views` will, and reports the result.
    Print :func:`depth_report` from it before paying for the sweep; a
    ParaView startup costs a few seconds, a 48-view sweep of a large dataset
    costs a good deal more.

    :param state: The ``.pvsm`` file.  Not modified.
    :param spec: Quilt specification; only its aspect matters here.
    :param fov: Vertical FOV the sweep will use, with the
        :func:`~quiltwright.lfd.render_quilt` dolly-back convention.
        ``None`` keeps the state's own.
    :param zoom: Zoom the sweep will use.
    :param binary: ``pvpython`` executable; see :func:`find_pvpython`.
    :return: The framed camera and its depth range.
    """
    pvpython = find_pvpython(binary)
    path = _check(state)
    with tempfile.TemporaryDirectory(prefix="paraview_probe_") as tmp:
        meta_out = Path(tmp) / "camera.json"
        script = _script(
            path,
            spec,
            Path(tmp),
            meta_out,
            fov=fov,
            zoom=zoom,
            hide_orientation_axes=True,
            probe_only=True,
        )
        code, out, err = _run_pvpython(pvpython, script, n_views=0, progress=False)
        if code != 0 or not meta_out.exists():
            reason = f"exited {code}" if code != 0 else "did not write the camera probe"
            raise RuntimeError(f"pvpython {reason}\n{out}\n{err}")
        return ParaViewCamera._from_meta(json.loads(meta_out.read_text()))

render_paraview_quilt(state, spec, *, fov=14.0, zoom=None, view_cone=None, hide_orientation_axes=True, binary=None, keep_views=None, progress=True)

Render a ParaView state file into a Looking Glass quilt.

Loads the .pvsm in pvpython, sweeps the render view's camera across the display's view cone with off-axis projections, and tiles the frames with :func:~quiltwright.quilt.assemble_quilt. The whole sweep runs in one ParaView process, so the state is loaded once.

Parameters:

Name Type Description Default
state str | Path

The .pvsm file. Not modified.

required
spec QuiltSpec

Quilt specification (grid, size, aspect, cone).

required
fov float | None

Vertical field of view in degrees; see :func:render_paraview_views.

14.0
zoom float | None

Optional dolly factor applied after framing.

None
view_cone float | None

Override the spec's view cone in degrees.

None
hide_orientation_axes bool

Turn off ParaView's corner axes widget.

True
binary str | None

pvpython executable; see :func:find_pvpython.

None
keep_views str | Path | None

Directory to retain the per-view PNGs and the camera.json probe in. Discarded if None.

None
progress bool

Print a progress line while rendering.

True

Returns:

Type Description
ndarray

uint8 RGB array of shape (quilt_height, quilt_width, 3).

Source code in src/quiltwright/paraview.py
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
def render_paraview_quilt(
    state: str | Path,
    spec: QuiltSpec,
    *,
    fov: float | None = 14.0,
    zoom: float | None = None,
    view_cone: float | None = None,
    hide_orientation_axes: bool = True,
    binary: str | None = None,
    keep_views: str | Path | None = None,
    progress: bool = True,
) -> np.ndarray:
    """Render a ParaView state file into a Looking Glass quilt.

    Loads the ``.pvsm`` in ``pvpython``, sweeps the render view's camera
    across the display's view cone with off-axis projections, and tiles the
    frames with :func:`~quiltwright.quilt.assemble_quilt`.  The whole sweep
    runs in one ParaView process, so the state is loaded once.

    :param state: The ``.pvsm`` file.  Not modified.
    :param spec: Quilt specification (grid, size, aspect, cone).
    :param fov: Vertical field of view in degrees; see
        :func:`render_paraview_views`.
    :param zoom: Optional dolly factor applied after framing.
    :param view_cone: Override the spec's view cone in degrees.
    :param hide_orientation_axes: Turn off ParaView's corner axes widget.
    :param binary: ``pvpython`` executable; see :func:`find_pvpython`.
    :param keep_views: Directory to retain the per-view PNGs and the
        ``camera.json`` probe in.  Discarded if ``None``.
    :param progress: Print a progress line while rendering.
    :return: ``uint8`` RGB array of shape ``(quilt_height, quilt_width, 3)``.
    """
    from PIL import Image

    with tempfile.TemporaryDirectory(prefix="paraview_quilt_") as tmp:
        workdir = Path(keep_views).expanduser() if keep_views is not None else Path(tmp)
        views, _camera = render_paraview_views(
            state,
            spec,
            workdir,
            fov=fov,
            zoom=zoom,
            view_cone=view_cone,
            hide_orientation_axes=hide_orientation_axes,
            binary=binary,
            progress=progress,
        )
        return assemble_quilt((np.asarray(Image.open(png).convert("RGB")) for png in views), spec)

render_paraview_views(state, spec, out_dir, *, fov=14.0, zoom=None, view_cone=None, hide_orientation_axes=True, binary=None, progress=True)

Sweep the state's render view and write one PNG per view.

The per-view half of :func:render_paraview_quilt, for a hologram printer's sweep or for inspecting the frames.

Parameters:

Name Type Description Default
state str | Path

The .pvsm file. Not modified.

required
spec QuiltSpec

Quilt specification (grid, size, aspect, cone).

required
out_dir str | Path

Directory to write view000.png ... into; created if absent.

required
fov float | None

Vertical field of view in degrees, with the :func:~quiltwright.lfd.render_quilt dolly-back convention; None keeps the state's own FOV and distance.

14.0
zoom float | None

Optional dolly factor applied after framing.

None
view_cone float | None

Override the spec's view cone in degrees.

None
hide_orientation_axes bool

Turn off ParaView's corner axes widget. It is pinned to the screen, so it would sit on the glass in every view.

True
binary str | None

pvpython executable; see :func:find_pvpython.

None
progress bool

Print a progress line while rendering.

True

Returns:

Type Description
tuple[list[Path], ParaViewCamera]

The PNG paths in view order (view 0 leftmost), and the framed camera the sweep used.

Source code in src/quiltwright/paraview.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
def render_paraview_views(
    state: str | Path,
    spec: QuiltSpec,
    out_dir: str | Path,
    *,
    fov: float | None = 14.0,
    zoom: float | None = None,
    view_cone: float | None = None,
    hide_orientation_axes: bool = True,
    binary: str | None = None,
    progress: bool = True,
) -> tuple[list[Path], ParaViewCamera]:
    """Sweep the state's render view and write one PNG per view.

    The per-view half of :func:`render_paraview_quilt`, for a hologram
    printer's sweep or for inspecting the frames.

    :param state: The ``.pvsm`` file.  Not modified.
    :param spec: Quilt specification (grid, size, aspect, cone).
    :param out_dir: Directory to write ``view000.png`` ... into; created if
        absent.
    :param fov: Vertical field of view in degrees, with the
        :func:`~quiltwright.lfd.render_quilt` dolly-back convention;
        ``None`` keeps the state's own FOV and distance.
    :param zoom: Optional dolly factor applied after framing.
    :param view_cone: Override the spec's view cone in degrees.
    :param hide_orientation_axes: Turn off ParaView's corner axes widget.  It
        is pinned to the screen, so it would sit on the glass in every view.
    :param binary: ``pvpython`` executable; see :func:`find_pvpython`.
    :param progress: Print a progress line while rendering.
    :return: The PNG paths in view order (view 0 leftmost), and the framed
        camera the sweep used.
    """
    pvpython = find_pvpython(binary)
    path = _check(state)
    if view_cone is not None:
        spec = replace(spec, view_cone=view_cone)
    out = Path(out_dir).expanduser()
    out.mkdir(parents=True, exist_ok=True)
    meta_out = out / "camera.json"
    script = _script(
        path,
        spec,
        out,
        meta_out,
        fov=fov,
        zoom=zoom,
        hide_orientation_axes=hide_orientation_axes,
        probe_only=False,
    )
    code, stdout, stderr = _run_pvpython(pvpython, script, n_views=spec.n_views, progress=progress)
    views = [out / f"view{i:03d}.png" for i in range(spec.n_views)]
    missing = [p.name for p in views if not p.exists()]
    if code != 0 or missing or not meta_out.exists():
        # pvpython can exit 0 with nothing written -- a bad state path is
        # logged, not raised -- so the frames are the real success signal.
        reason = f"exited {code}" if code != 0 else f"did not write {missing or [meta_out.name]}"
        raise RuntimeError(f"pvpython {reason}\n{stdout}\n{stderr}")
    camera = ParaViewCamera._from_meta(json.loads(meta_out.read_text()))
    return views, camera