Skip to main content

Quiltwright

Python License: BSD-3-Clause PyPI Version Tests DOI

Quiltwright -- holographic output for scientific visualisation.

Eric G. Suchanek, PhD -- Flux-Frontiers

Quiltwright is the last stage of a scientific rendering pipeline -- any pipeline that ends in a scene. It takes what you already have, whether that is a PyVista or VTK scene built in memory, a POV-Ray scene on disk, or a .pov file written thirty years ago by someone who is no longer around to explain it, and puts it on holographic hardware in glasses-free depth. Nothing is rewritten to get there: a POV-Ray scene is ray-traced unmodified, with a camera appended per view.

It is used that way by WaveRider for geometric ML manifolds and by pdb2pov for molecular structures, but neither is a prerequisite. If you can render it, you can hang it in the air.

Eric's Science Museum, the canonical POV-Ray render

A career in structural biophysics, arranged as exhibits: B-DNA and Z-DNA under bell jars, Ras and my original DNA Under Glass on the walls. The molecular models were generated by pdb2pov in 1997; the room dates to 1995. Quiltwright ray-traces it into a 48-view light-field quilt for Looking Glass light-field panels, or into 2-D video for Hololuminescent displays. A third output -- a 23-view sweep for LitiHolo's desktop hologram printer -- is in development. About the image


Latest news

v0.7.0 (2026-08-18). The CLI now covers the whole tail of the pipeline. quiltwright cast puts a saved quilt on the panel, recovering its tiling from the filename; quiltwright wallpaper completes the no-Bridge path, hanging a woven frame on the desktop of the panel it was woven for, matched by serial; and quiltwright bridge status / reset exist because Bridge keeps answering HTTP after it has crashed, so a cast can report success against a daemon that will never draw. Every full quilt now writes a provenance record to renders/reports/ -- scene hash, commit, camera, measured depths, the depth budget verbatim and the output's digest -- because a quilt is a gitignored 25-40 MB PNG that says nothing about where it came from. A render started outside make no longer takes every core on the machine.

Full history: CHANGELOG.md and releases.


What it's for

     scene sources                  quiltwright                  outputs

  PyVista / VTK  -----+        +------------------+        +-->  LFD  light-field panels
   (WaveRider, TVB)   |        |  off-axis views  |        |          multi-view quilts
                      +------->|  depth budget    |------->+-->  HLD  hololuminescent
  POV-Ray  -----------+        |  quilt assembly  |        |          2-D video
   (pdb2pov)                   |  view sweeps     |        +-->  LitiHolo  hogel sweeps
                               +------------------+                       (in development)

Two backends, not two pipelines. render_quilt() sweeps any PyVista/VTK scene held in memory. render_pov_quilt() ray-traces any POV-Ray scene on disk, appending a camera per view and modifying nothing -- which is what lets it render files written decades ago, by tools that no longer exist, without touching them. The two meet at a shared, renderer-agnostic assembler, so everything downstream of that point is indifferent to which one produced the views.

What feeds the backends is open. WaveRider's voxel and manifold visualiser and pdb2pov's PDB conversion are the two that drove the design, but quiltwright.tvb_data pulls real brain geometry from The Virtual Brain, PyVista's own example datasets work as-is, quiltwright.povgen writes POV-Ray from analytic primitives, and a plain .pov file off your disk needs no pipeline at all.

Two display technologies, which are easy to confuse because one company sells both. Light-field displays (LFD -- Portrait, Go, 16"/27"/32"/65") are lenticular panels that consume quilts: N views of the same scene tiled into one image, fused optically into real depth. Hololuminescent displays (HLD -- 16"/27"/86") play ordinary 2-D video behind a fixed holographic optic, and need styling rather than parallax -- dark field, high contrast, generous safe margins. quiltwright.lfd targets the first; quiltwright.hld the second.

The shared middle is what makes this a package rather than two scripts: quilt geometry and device presets, the depth-budget arithmetic that decides whether a scene will fuse before you spend an hour rendering it, filename conventions Looking Glass software parses, video encoding, and direct Bridge control.

A third output, under development. That middle also serves consumers that are not panels at all: render_pov_views() writes the sweep as separate frames, and sweep_spec() / LITIHOLO_SWEEP give the single-row layout a hologram printer's prime view count needs and a quilt grid cannot express -- so one scene feeds a light-field panel and a hologram printer without being rebuilt. Nothing has been through a printer's software yet, so the claim is a sweep matching LitiHolo's published specification rather than verified compatibility; docs/lfd.md records what is still open.

The part that is easy to get wrong

Each view must use an off-axis (asymmetric-frustum) projection -- the camera slides sideways while continuing to face the same direction, with the image plane sheared back onto the original view axis.

The intuitive alternative is to swivel each camera to keep the subject centred. That is "toe-in", and it introduces vertical parallax and keystone distortion, so the display cannot fuse the views: you get ghosting instead of depth. It is the single most common way light-field renders go wrong, and it produces output that looks perfectly plausible in any individual frame. Quiltwright does the off-axis projection correctly in both backends, and gives you the arithmetic to know in advance whether a scene will fuse.


Install

pip install quiltwright              # core: quilt geometry + Bridge control
pip install "quiltwright[viz]"       # + PyVista/VTK rendering backend

The POV-Ray backend needs a povray binary on PATH rather than a Python package:

brew install povray                  # macOS

For the complete stack -- renderers, ffmpeg, Looking Glass Bridge, pdb2pov -- see the installation guide.


Quick start

From a PyVista scene

import pyvista as pv
from quiltwright import QUILT_PRESETS, render_quilt, save_quilt

p = pv.Plotter(off_screen=True)
p.add_mesh(pv.ParametricTorus())

spec = QUILT_PRESETS["portrait"]
save_quilt(render_quilt(p, spec), "torus", spec)   # -> torus_qs8x6a0.75.png

From a POV-Ray scene

The scene file is never modified -- each view wraps it with #include and appends one camera.

from quiltwright import QUILT_PRESETS, PovCamera, render_pov_quilt, save_quilt

camera = PovCamera(location=(15, 20, 6), look_at=(44, 19.2, 45.1), fov=53.13)
spec = QUILT_PRESETS["16-landscape"]
quilt = render_pov_quilt("pov-scenes/museum/museum.pov", spec, camera,
                         include_paths=["pov-scenes/myinclude", "pov-scenes"])
save_quilt(quilt, "museum", spec)

The museum scene above ships in pov-scenes/, and scripts/render_museum_hologram.py renders it end-to-end with a measured depth budget -- the worked case study in docs/povray.md, and the scene itself in docs/about-the-image.md.

Two more scene trees ship alongside it -- the bell-jar DNA still lifes the museum's pedestals were built from, and porin's beta-barrel over water. What is in each, and how to render them directly, is in pov-scenes/README.md.

Send it to the display

from quiltwright import cast_quilt, pause_quilt, resume_quilt, stop_quilt

cast_quilt("museum_qs8x6a1.77778.png", spec)   # needs Looking Glass Bridge >= 2.2

save_quilt takes the array and cast_quilt takes a path, and mixing them up only surfaces minutes into a ray-traced render. save_and_cast_quilt composes the two in the right order, and returns a failed cast rather than raising, so a Bridge that isn't running never costs you the render:

from quiltwright import save_and_cast_quilt

path, error = save_and_cast_quilt(quilt, "museum", spec)

Saved filenames carry the _qs<cols>x<rows>a<aspect> suffix that Looking Glass Studio and Bridge parse, so playback settings are detected automatically.

Send it to a hologram printer (in development)

A printer wants the views as separate frames, not tiled, and LitiHolo's published input specification asks for 23 of them per hogel -- a prime count, so no columns × rows grid can express it. LITIHOLO_SWEEP is that single-row spec, and the camera sweep behind it is the same off-axis geometry a quilt is built from:

from quiltwright import LITIHOLO_SWEEP, format_depth_budget, render_pov_views

print(format_depth_budget(LITIHOLO_SWEEP, camera, {"near": 31, "far": 96}))

paths = render_pov_views("pov-scenes/museum/museum.pov", LITIHOLO_SWEEP,
                         camera, "sweep/",
                         include_paths=["pov-scenes/myinclude", "pov-scenes"])
# -> sweep/view000.png ... sweep/view022.png, view 0 leftmost

Print the budget first rather than after. 23 views over 45° is 2.05° between adjacent views against a Portrait quilt's 0.74° -- about 2.75× coarser sampling, so a sweep has less margin than a quilt, not more. The museum, framed as above, reports ~43 px of adjacent-view disparity at that cone: far past the ~8 px ghosting threshold, and exactly the sort of thing worth learning before the ray-tracer starts rather than after.

This path is POV-Ray only for now, and no file has been through the printer's software: what it emits is a sweep matching the published specification, which is a narrower claim than compatibility. The two open questions -- whether a hogel slicer expects off-axis frusta or a toe-in arc, and whether 2.05° is too coarse -- are written up in docs/lfd.md.


Driving it from the shell

Everything above is the library, which is where a scene arrives from whichever pipeline built it. Two things sit around that: a Makefile for the scenes this repository happens to ship, and a CLI for the stage after the assembler, which does not care what produced the quilt.

make -- the bundled archive

The repository ships the 1993-99 POV-Ray scenes, and a Makefile that renders them with their measured depth budgets already dialled in, so a hologram from a clean clone is one command rather than a script you have to write. This covers the bundled scenes only. A WaveRider manifold or a freshly converted structure does not come through here -- it comes through the library, or through scripts/render_pyvista_hologram.py for the PyVista subjects -- and it lands in the same renders/quilts/, where the CLI below picks it up regardless of origin.

make                      # the default goal is help; rendering is always explicit
make help                 # every target, and the still names

Stills

One full-quality frame per scene, into renders/stills/. These are committed: they are the diffable record of what each scene looks like.

make stills                       # all of them
make still-bell_jar_bj_holo       # just one

Each renders at its own declared aspect -- POV-Ray maps right to image width whatever pixel dimensions you ask for, so a mismatched frame stretches silently. The Makefile carries the correct size per scene; the table is in docs/pov-workflow.md.

Quilts

Into renders/quilts/, through the render scripts, which inject a device camera and place the focal plane from measured near/far depths rather than from the scene's own aim point:

make quilts                       # bell jar, porin, lambda, museum
make quilt-bell-jar-holo          # one, 16:9
make quilt-bell-jar-portrait      # the 9:16 companion, for tall panels
make preview-museum               # quarter-size, for iterating on composition

Preview first when you are changing a composition -- a preview is seconds per view where a full quilt is minutes, and the depth budget it prints is the same one the full render will use.

Two knobs worth knowing:

make quilt-porin EXTRA_ARGS="--cast"          # send it to the panel when done
make quilt-museum EXTRA_ARGS="--antialias 0.1"
make quilts RENDER_THREADS=$(sysctl -n hw.ncpu)   # use the whole box

RENDER_THREADS defaults to ncpu - 2, leaving two cores for the rest of the machine so a multi-minute render does not make the desktop unusable. It reaches POV-Ray through a generated POVINI, because the render scripts invoke povray themselves and a command-line +WT would override them. JOBS stays at 1 on purpose: POV-Ray already threads one render across every core, so extra processes only split it.

The same two cores are held back when you call a render script directly, where there is no POVINI to carry the Makefile's value -- --threads N sets it explicitly, and --threads 0 lets POV-Ray take everything, which is its own default. A Work_Threads line in POVINI always wins over the courtesy cap, so make quilts RENDER_THREADS=... keeps working.

Run reports

Every full quilt writes a Markdown provenance record to renders/reports/. A quilt is a 25-40 MB gitignored release asset; the report is the committed record of how it was made -- scene file and its SHA-256, repository commit and whether the tree was dirty, camera and measured depths, the depth budget verbatim, the parallelism actually used, timings, and the output's own digest. Pass --report to either render script to get one outside make.

quiltwright -- the CLI, downstream of both backends

Installed as quiltwright, core-only (numpy, pillow, click). Everything here operates on a quilt, which is where the two scene sources have already met: a manifold swept out of PyVista and a molecular scene ray-traced from POV-Ray produce the same artifact, and these commands treat them identically.

quiltwright bridge status       # is Bridge actually able to draw?
quiltwright bridge reset        # kill and relaunch a wedged daemon

quiltwright cast renders/quilts/bell-jar-holo_qs8x6a1.77778.png
quiltwright cast --check        # which displays can Bridge see?

quiltwright weave renders/quilts/bell-jar-holo_qs8x6a1.77778.png --cal visual.json
quiltwright wallpaper bell-jar-holo_native_LKG-J00332.png

cast recovers the tiling from the _qs<cols>x<rows>a<aspect> filename suffix that save_quilt() writes, so it usually needs no flags whatever produced the views. weave then wallpaper is the no-Bridge path: a woven frame is already interleaved for one panel, so setting it as that panel's desktop picture makes the desktop a hologram with nothing running. wallpaper matches the frame to the right display by the panel serial both carry.

When the glass stays black, bridge status is the first thing to run. Bridge keeps its HTTP port open and keeps issuing session tokens after crashing internally, so a cast can report success at every step against a daemon that will never draw -- status checks the port, the session, the device list and whether any device is actually a Looking Glass, then gives a verdict.


The depth budget

Whether a hologram fuses comes down to adjacent-view disparity: how far a feature moves between neighbouring views. Roughly 4-5 px is the practical ceiling; past ~8 px, hard edges ghost.

from quiltwright import QUILT_PRESETS, focal_distance_for_range, view_disparity

# Put the focal plane where near and far content are equally penalised.
focal = focal_distance_for_range(near=31, far=96)       # harmonic mean, not midpoint
view_disparity(QUILT_PRESETS["16-landscape"], fov=53.13,
               focal_distance=focal, depth=31)          # -> px between adjacent views

Those two depths are measured, not guessed -- scripts/measure_depth_range.py sweeps an opaque plane along the view axis and reports where a scene's content actually begins and ends.

Three results worth knowing before you frame a shot:

  • Content at the focal plane has zero disparity -- it is welded to the glass.
  • The focal plane belongs at the harmonic mean of the depth range, not the midpoint. Disparity is asymmetric in depth, and near content is the expensive side.
  • A narrower field of view increases disparity. Zooming in magnifies the scene and the parallax with it. The widely repeated "use ~14° FOV" advice is specific to object-centric scenes; applied to an interior it makes ghosting worse.

For interiors there is a fourth trap that no arithmetic will warn you about: the camera sweep physically travels focal_distance × tan(cone/2) sideways, and in a room that path can run through a wall. See docs/povray.md.


Supported devices

QUILT_PRESETS carries the official quilt settings for Portrait, Go, and the 16"/27"/32"/65" panels in both orientations. The 16" Gen3 Landscape entry is verified against what Bridge reports for real hardware.

from quiltwright import QUILT_PRESETS
QUILT_PRESETS["16-landscape"]      # 8x6 views, 7680x4320, aspect 1.7778

Documentation

Document Contents
docs/install.md Installing the full stack: package extras, POV-Ray, ffmpeg, Bridge, pdb2pov
docs/lfd.md Light-field output, Bridge/Studio setup, device presets, the PyVista path, view sweeps for hologram printers
docs/pyvista-datasets.md PyVista dataset ideas for holograms: topography, the Allen mouse brain atlas, other strong-depth candidates
docs/tvb-data.md Brain geometry from The Virtual Brain: cortical surfaces, connectomes, parcellations, downloaded on demand
docs/povray.md The POV-Ray backend: off-axis camera derivation, depth budget, sweep clearance, a worked case study
docs/povgen.md Writing POV-Ray scenes from analytic primitives, so a scene composed in Python can be ray-traced rather than rasterised
docs/pov-workflow.md The procedure: taking an archive scene from "won't parse" to a quilt that fuses, step by step
docs/pdb2pov.md Rendering molecular structures from PDB and mmCIF files as holograms, from the shell or in-process
docs/hld.md Hololuminescent Displays, which play ordinary 2-D video rather than quilts
docs/about-the-image.md The museum scene: what is on display, and the thirty-year pipeline behind it
docs/gallery.md The reference stills every quilt is swept from, one per scene, and how to regenerate them

Testing

pip install -e ".[viz]" && pip install pytest
pytest

Rendering tests skip cleanly on machines with no OpenGL stack, and the POV-Ray tests skip when no povray binary is present. Under a headless CI runner, use xvfb-run -a pytest to exercise them.


The pipelines this serves

  • WaveRider -- manifold-aware geometric ML. Its voxel and manifold visualiser builds the PyVista scenes that render_quilt() sweeps.
  • pdb2pov -- PDB and mmCIF to POV-Ray converter, written in C in 1993 and still building from a fresh clone. It produced the molecular models in the image above, and still feeds the POV-Ray backend. pypdb2pov, its Python port, writes byte-identical scenes and imports, so a conversion and a quilt render fit in one script.
  • proteusPy -- protein disulfide bond analysis and rendering.

Citation

If you use Quiltwright in your work, please cite it. Citation metadata is in CITATION.cff; GitHub's "Cite this repository" button generates BibTeX/APA from it, and the DOI badge above resolves to the archived release on Zenodo.

@software{suchanek_quiltwright,
  author  = {Suchanek, Eric G.},
  title   = {Quiltwright: Holographic Output for Looking Glass Displays},
  url     = {https://github.com/suchanek/quiltwright},
  doi     = {10.5281/zenodo.21798503},
  version = {0.7.0},
  year    = {2026}
}

License

BSD 3-Clause. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quiltwright-0.7.0.tar.gz (97.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quiltwright-0.7.0-py3-none-any.whl (100.4 kB view details)

Uploaded Python 3

File details

Details for the file quiltwright-0.7.0.tar.gz.

File metadata

  • Download URL: quiltwright-0.7.0.tar.gz
  • Upload date:
  • Size: 97.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for quiltwright-0.7.0.tar.gz
Algorithm Hash digest
SHA256 532110b204cb822df686d05a37baa8502d8dddadf80a84b22ce0c689d49f23f0
MD5 9b343f118c95c4a14350f79b76c0466b
BLAKE2b-256 12d7326040dc4f607a725e45705c02b94a8569d79161d3c953f455b3c55a46f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for quiltwright-0.7.0.tar.gz:

Publisher: release.yml on suchanek/quiltwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quiltwright-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: quiltwright-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 100.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for quiltwright-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 10c9e11d609ca1ab988648da776c0f4964b37d32dda4b4964cf4366d9c6674cb
MD5 55062ff6009bc050cd421846d5c7b700
BLAKE2b-256 2d3087718acf22b4f5e7ffe0f148be139bce0e59677ee916cd71a30395a1f352

See more details on using hashes here.

Provenance

The following attestation bundles were made for quiltwright-0.7.0-py3-none-any.whl:

Publisher: release.yml on suchanek/quiltwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.8.0

2 files

This release

0.7.0 This release

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page