PyVista backend (lfd)¶
quiltwright.lfd
¶
Looking Glass Quilt Renderer¶
Renders any PyVista scene into a quilt -- the tiled multi-view image format used by Looking Glass holographic light-field displays.
A quilt packs N renders of the same scene, captured from camera positions
swept horizontally across a viewing cone, into a single image. Views are
tiled left-to-right, bottom-to-top: view 0 (leftmost camera) sits at the
bottom-left tile and view N-1 (rightmost camera) at the top-right. Looking
Glass software (Bridge, Studio) detects quilt settings from the filename
suffix _qs<cols>x<rows>a<aspect>.png, so files saved through
:func:save_quilt are recognised automatically.
Each view uses an off-axis (asymmetric-frustum) projection rather than a "toe-in" rotation: the camera translates along its horizontal axis while the frustum is sheared back toward the focal plane. This keeps the focal plane identical across views -- the geometric requirement for the display's lenticular optics to fuse the views into a stable hologram. Content at the focal plane appears at the physical screen surface; content nearer/farther floats in front of / behind the glass.
Quilt geometry (:class:QuiltSpec, :data:QUILT_PRESETS,
:func:assemble_quilt, :func:save_quilt) lives in :mod:quiltwright.quilt.
Bridge control (:func:cast_quilt) lives in :mod:quiltwright.bridge. Both
are re-exported from here so existing from quiltwright.lfd import ...
callers keep working.
Optional dependencies -- install the viz extras group::
poetry install --with viz # pyvista, pillow, scipy, ...
Typical usage::
import pyvista as pv
from quiltwright.lfd import QUILT_PRESETS, render_quilt, save_quilt
p = pv.Plotter(off_screen=True)
p.add_mesh(pv.ParametricTorus())
spec = QUILT_PRESETS["portrait"]
quilt = render_quilt(p, spec)
save_quilt(quilt, "torus", spec) # -> torus_qs8x6a0.75.png
p.close()
The saved quilt can be displayed on the device by dragging it into Looking
Glass Studio, or cast directly from Python via :func:cast_quilt if Looking
Glass Bridge is running on the machine driving the display.
Part of Quiltwright -- https://github.com/Flux-Frontiers/quiltwright Author: Eric G. Suchanek, PhD
QuiltSpec(columns, rows, quilt_width, quilt_height, aspect, view_cone=35.0)
dataclass
¶
Geometry of a quilt: tiling grid, total pixel size, and view cone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
int
|
Number of view tiles per quilt row. |
required |
rows
|
int
|
Number of view tiles per quilt column. |
required |
quilt_width
|
int
|
Total quilt width in pixels. |
required |
quilt_height
|
int
|
Total quilt height in pixels. |
required |
aspect
|
float
|
Aspect ratio (width / height) of a single view, which matches the target display's aspect. Embedded in the quilt filename so Looking Glass software can configure playback correctly. |
required |
view_cone
|
float
|
Total horizontal sweep of the camera in degrees. Looking Glass documents 35° as the standard rendering cone (the physical display cone is wider, ~40-58° depending on model; rendering slightly narrower adds apparent depth). |
35.0
|
n_views
property
¶
Total number of views in the quilt.
tile_height
property
¶
Height of a single view tile in pixels.
tile_width
property
¶
Width of a single view tile in pixels.
filename(stem, ext='png')
¶
Quilt filename with the metadata suffix Looking Glass software parses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stem
|
str
|
Base name without extension (e.g. |
required |
ext
|
str
|
File extension without the dot. |
'png'
|
Returns:
| Type | Description |
|---|---|
str
|
e.g. |
Source code in src/quiltwright/quilt.py
95 96 97 98 99 100 101 102 | |
scaled(factor)
¶
Same view grid at a fraction of the pixel size.
Casting at full preset size is rarely what you want: rendering costs about a second, but the wait is Bridge loading the resulting PNG, and that scales with its area. Halving the linear size quarters it.
The scaled dimensions are rounded down to a multiple of the tile grid, which is the part that is easy to get wrong: scale naively and the quilt no longer divides evenly into tiles, so every view lands on a fractional pixel boundary and the whole light field smears.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
float
|
Linear scale factor, e.g. |
required |
Returns:
| Type | Description |
|---|---|
QuiltSpec
|
A new :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If factor is not positive, or scales the quilt below one pixel per tile. |
Source code in src/quiltwright/quilt.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
still(height=1100)
¶
The same view, once, as a flat image at this device's aspect.
A one-tile "quilt" is the cheapest way to check framing, lighting and
materials before paying for the whole sweep, and it is what the
gallery images are: :func:view_offsets returns a single zero offset
at n_views == 1, so the render is the centre view and nothing
else. The width follows :attr:aspect rather than being fixed, so
the still is framed like the panel it is standing in for -- a still
of a landscape device is a landscape image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
int
|
Image height in pixels. |
1100
|
Returns:
| Type | Description |
|---|---|
QuiltSpec
|
A new 1x1 :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If height is not positive. |
Source code in src/quiltwright/quilt.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
tile_origin(view_index)
¶
Pixel (x, y) of a view's top-left corner within the quilt image.
Quilt convention: view 0 at the bottom-left, advancing
left-to-right then bottom-to-top. The returned y is measured
from the image top (numpy/PIL row order).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view_index
|
int
|
View number in |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int]
|
|
Source code in src/quiltwright/quilt.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
with_grid(columns, rows)
¶
Same quilt at a different view-grid density.
Total quilt pixels stay fixed, so more views means fewer pixels per view: the device's lenticular optics interpolate between views, so extra views give smoother look-around at the cost of per-view sharpness. The official presets are the factory-calibrated balance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
int
|
New number of tile columns. |
required |
rows
|
int
|
New number of tile rows. |
required |
Returns:
| Type | Description |
|---|---|
QuiltSpec
|
A new :class: |
Source code in src/quiltwright/quilt.py
104 105 106 107 108 109 110 111 112 113 114 115 116 | |
assemble_quilt(views, spec)
¶
Tile per-view images into a single quilt image.
This is the renderer-agnostic half of quilt production: it takes views
that some backend already rendered -- VTK via :func:render_quilt, a
ray-tracer via :mod:quiltwright.povray -- and lays them out in quilt
order. Views are consumed lazily, so a backend can stream them without
holding all n_views frames in memory at once.
Views whose pixel size differs from the tile size are resampled, which is what makes anamorphic quilts (tile pixel aspect != view aspect, e.g. the 27" presets) come out correctly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
views
|
Iterable[ndarray]
|
Iterable of |
required |
spec
|
QuiltSpec
|
Quilt specification (grid, size, aspect). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the number of views does not match |
Source code in src/quiltwright/quilt.py
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 | |
available_output_devices(bridge_url, timeout, *, token=None)
¶
Ask Bridge which output devices it can see.
Every entry Bridge reports, panel or not -- an ordinary monitor shows up
with hardware_version: "thirdparty". Filter on that field yourself to
count panels only; :func:cast_quilt's own check does not distinguish
a panel from a monitor, since head_index=-1 deliberately accepts
either -- it only rejects an empty list, the state that means nothing
is registered at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
required |
timeout
|
float
|
HTTP timeout in seconds. |
required |
token
|
str | None
|
An existing orchestration token, to skip a redundant
|
None
|
Returns:
| Type | Description |
|---|---|
list[dict[str, str]]
|
One dict per head: |
Source code in src/quiltwright/bridge.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
camera_frame(camera)
¶
Decompose a vtkCamera into position, focal point, right/up basis, distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
camera
|
A |
required |
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray, ndarray, ndarray, float]
|
|
Source code in src/quiltwright/lfd.py
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | |
cast_quilt(quilt_path, spec, *, bridge_url=BRIDGE_URL, playlist=None, timeout=10.0, head_index=-1)
¶
Show a saved quilt on the connected Looking Glass via Bridge.
Requires Looking Glass Bridge <https://lookingglassfactory.com/software/looking-glass-bridge>_
(>= 2.2) running on the machine the display is plugged into. A one-quilt
:func:cast_playlist: see it for the orchestration sequence, for why
every call plays a playlist of its own, and for the full parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quilt_path
|
str | Path
|
Path to a quilt PNG on the Bridge host's filesystem. |
required |
spec
|
QuiltSpec
|
Quilt specification (tiling + aspect sent to Bridge). |
required |
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
BRIDGE_URL
|
playlist
|
str | None
|
Bridge playlist name; |
None
|
timeout
|
float
|
HTTP timeout in seconds per request. |
10.0
|
head_index
|
int
|
Which Bridge output device to play on; |
-1
|
Returns:
| Type | Description |
|---|---|
dict
|
Decoded JSON response of the final |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If Bridge answers but reports zero output devices. |
Source code in src/quiltwright/bridge.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
depth_report(plotter, spec, *, view_cone=None, fov=14.0, zoom=None, labels=DEPTH_LABELS, extra_depths=None, soft_px=5.5)
¶
Depth budget for a PyVista scene, as a report to print before rendering.
The PyVista counterpart to
:func:~quiltwright.povray.format_depth_budget. Pass the same
view_cone, fov and zoom you will pass to :func:render_quilt, so
the numbers describe the render you are about to make. Depths come from
the plotter's bounds, so report before adding a floor or backdrop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
Plotter with the scene composed and the camera framed. |
required | |
spec
|
QuiltSpec
|
Quilt specification. |
required |
view_cone
|
float | None
|
Override the spec's view cone in degrees; see
:func: |
None
|
fov
|
float | None
|
FOV that will be used for the render; see :func: |
14.0
|
zoom
|
float | None
|
Zoom that will be used for the render. |
None
|
labels
|
tuple[str, str, str]
|
Names for the near, focal and far depths. |
DEPTH_LABELS
|
extra_depths
|
Mapping[str, float] | None
|
Further labelled depths to include, e.g.
|
None
|
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/lfd.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
find_ffmpeg()
¶
Locate an ffmpeg binary: system PATH first, then imageio-ffmpeg's.
Returns:
| Type | Description |
|---|---|
str
|
Path to an ffmpeg executable. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If no ffmpeg can be found. |
Source code in src/quiltwright/runtime.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
focal_distance_for_range(near, far)
¶
Focal distance that balances disparity between the nearest and farthest content -- their harmonic mean.
Disparity grows with |1 - Z/depth|, which is asymmetric in depth:
placing the focal plane at the arithmetic midpoint leaves the near
content far worse off than the far content. Equalising the two,
Z/near - 1 = 1 - Z/far, gives Z = 2/(1/near + 1/far).
With far at infinity this reduces to 2 * near.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
near
|
float
|
Distance to the nearest content, in scene units. |
required |
far
|
float
|
Distance to the farthest content; may be |
required |
Returns:
| Type | Description |
|---|---|
float
|
Focal distance to aim the camera at. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If near is not positive or exceeds far. |
Source code in src/quiltwright/quilt.py
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | |
frame_and_focus(plotter, *, fov=14.0, margin=1.15, spec=None)
¶
Frame a PyVista scene tightly at its final view, and focus it.
The PyVista counterpart to
:func:~quiltwright.cycles.frame_camera, and the thing to call once the
camera is pointing where you want it. reset_camera() fits the
un-tilted bounds, so once the view is tilted -- by an orbit, or an
explicit camera_position -- that framing is too loose and the subject
reads as small with a lot of empty margin: ask for a mountain hologram
and get a speck.
This re-fits from scratch at the final view direction. The eight
bounding-box corners are projected onto the camera's own right/up/forward
axes, which accounts for foreshortening -- a flat, elongated terrain
viewed obliquely needs far less distance than its bounding sphere would
suggest -- giving the tightest distance that still keeps every corner in
frame at the target FOV and window aspect. The focal plane then goes at
the harmonic mean of the resulting near and far depths, the same balance
:func:focal_distance_for_range gives the POV-Ray path, measured from
exact geometry rather than a rendered plane sweep.
The camera is modified, unlike :func:scene_depths, which measures
copies: position, view angle and focal point are all overwritten. Only
the view direction survives. Having locked the camera here, pass
fov=None to :func:render_quilt so it does not frame the scene a
second time from scratch.
The fit uses the plotter's bounds, so call this before adding a floor, backdrop or anything else that should not be framed -- a floor that reaches past the camera is framed, and measured, as the subject.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
A |
required | |
fov
|
float
|
Vertical field of view to lock the camera to, in degrees. Must match what the render actually uses, or the depth budget describes a different camera than the one that renders. |
14.0
|
margin
|
float
|
Headroom beyond the tight corner-projected fit, as a
fraction -- |
1.15
|
spec
|
QuiltSpec | None
|
Quilt specification the scene will render with. When
given, |
None
|
Returns:
| Type | Description |
|---|---|
tuple[float, float, float]
|
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If PyVista is not installed. |
Source code in src/quiltwright/lfd.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | |
litiholo_names(spec, *, mode='HPO')
¶
Filenames LitiHolo's own capture tool gives a sweep, in view order.
The printer consumes a numbered sequence, so the names carry the view
ordering and nothing else may. Matching the vendor tool's convention
costs nothing and removes one variable from a first print: its
captureHPOSequence() writes render-{W}x{H}-HPO-01.jpg through
-23.jpg, one-indexed and zero-padded to two digits, leftmost
camera first. Quiltwright's own view000.png ordering is the same
sweep under a different name, which is exactly the kind of difference
that is invisible until a plate comes back mirrored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
QuiltSpec
|
Sweep specification; supplies the view count and the capture size that appears in the name. |
required |
mode
|
str
|
|
'HPO'
|
Returns:
| Type | Description |
|---|---|
list[str]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If mode is not |
Source code in src/quiltwright/quilt.py
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 602 603 604 | |
pack_litiholo_sweep(views, dest, spec, *, zip_output=False, jpeg_quality=95)
¶
Re-encode a rendered sweep into what the LitiHolo tool hands the printer.
The renderer-agnostic delivery step, the same role :func:assemble_quilt
plays for a panel: take views some backend already rendered -- POV-Ray
via :func:~quiltwright.povray.render_pov_views, anything else that
writes ordered frames -- and emit JPEGs named and ordered the way the
vendor's own tool emits them (see :func:litiholo_names).
Nothing here changes the geometry, which is set at render time and is
the part that actually decides whether a plate comes out right; render
with geometry="toe-in" if you are matching the tool rather than the
specification sheet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
views
|
Iterable[str | Path]
|
Rendered view images, in view order, view 0 leftmost. Any format pillow reads; each is converted to RGB, so a transparent background flattens to black -- which is the background the tool's own capture offers and the one a hologram wants. |
required |
dest
|
str | Path
|
Output directory; created if absent. |
required |
spec
|
QuiltSpec
|
Sweep specification. Its view count must match views, and its tile size names the files. |
required |
zip_output
|
bool
|
Also write |
False
|
jpeg_quality
|
int
|
Pillow JPEG quality, 1-95. The vendor tool takes whatever its browser's canvas encoder gives it; 95 is pillow's practical ceiling and errs toward not adding artefacts of our own to an image that is about to be sliced into hogels. |
95
|
Returns:
| Type | Description |
|---|---|
list[Path]
|
The JPEG paths written, in view order. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the number of views does not match spec. |
Source code in src/quiltwright/quilt.py
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 | |
pause_quilt(*, bridge_url=BRIDGE_URL, timeout=10.0)
¶
Pause playback on the connected Looking Glass.
Freezes the current frame; the playlist and its position are retained,
so :func:resume_quilt continues from where it left off. This is
Bridge's transport control group -- there is no stop_playlist or
pause_playlist endpoint (a guessed endpoint name doesn't 404: Bridge
answers with 200 OK and an empty body, indistinguishable from a slow
success unless you check that the response has no status field).
Confirmed against the endpoint list in the official
bridge.js <https://github.com/Looking-Glass/bridge.js>_ SDK source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
BRIDGE_URL
|
timeout
|
float
|
HTTP timeout in seconds. |
10.0
|
Returns:
| Type | Description |
|---|---|
dict
|
Decoded JSON response of the |
Source code in src/quiltwright/bridge.py
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | |
render_quilt(plotter, spec, *, view_cone=None, fov=14.0, zoom=None)
¶
Render the plotter's scene into a quilt image.
The plotter's current camera defines the centre view; its focal point
becomes the holographic focal plane (the physical surface of the
display). Position the camera before calling -- e.g. via
plotter.camera_position or plotter.reset_camera() -- exactly as
you would for a normal screenshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
An off-screen |
required | |
spec
|
QuiltSpec
|
Quilt specification (grid, size, aspect, cone). |
required |
view_cone
|
float | None
|
Override the spec's view cone in degrees. |
None
|
fov
|
float | None
|
Vertical field of view in degrees for the quilt cameras.
Looking Glass recommends ~14° (matches real-world parallax at
typical viewing distance); the camera is dollied back so the scene
stays the same size in frame. Pass |
14.0
|
zoom
|
float | None
|
Optional camera zoom factor applied after framing, before the view sweep. Values > 1 make the subject fill more of each tile, which is what drives perceived depth -- parallax is proportional to on-screen size, so a subject occupying a third of the frame yields a third of the available look-around. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in src/quiltwright/lfd.py
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 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 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | |
render_quilt_video(plotter, spec, out_stem, *, n_frames=180, fps=24, orbit_degrees=360.0, view_cone=None, fov=14.0, zoom=None, crf=18, on_frame=None, progress=True)
¶
Render an animated quilt video (default: a full turntable orbit).
Renders one quilt per frame, rotating the camera about the focal point
between frames, then encodes the sequence to MP4 per the Looking Glass
quilt-video spec (yuv420p; H.264, or HEVC for 8K quilts). The
filename carries the _qs<cols>x<rows>a<aspect> suffix so Studio /
Bridge auto-detect playback settings.
Note the cost: a Portrait video renders n_frames x 48 views.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
An off-screen |
required | |
spec
|
QuiltSpec
|
Quilt specification (grid, size, aspect, cone). |
required |
out_stem
|
str | Path
|
Output path; quilt suffix + |
required |
n_frames
|
int
|
Number of video frames (with fps sets loop duration). |
180
|
orbit_degrees
|
float
|
Total camera orbit over the clip; 360 loops seamlessly. Pass 0 to disable the turntable (use on_frame). |
360.0
|
view_cone
|
float | None
|
Override the spec's view cone in degrees. |
None
|
fov
|
float | None
|
Per-view vertical FOV; see :func: |
14.0
|
zoom
|
float | None
|
Camera dolly factor; see :func: |
None
|
crf
|
int
|
x264/x265 quality (lower = better; 15-20 sensible). |
18
|
on_frame
|
Optional |
None
|
|
progress
|
bool
|
Print a progress line while rendering. |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
Path of the quilt MP4 written. |
Source code in src/quiltwright/lfd.py
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 552 553 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 602 603 604 605 606 607 608 | |
resume_quilt(*, bridge_url=BRIDGE_URL, timeout=10.0)
¶
Resume playback after :func:pause_quilt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
BRIDGE_URL
|
timeout
|
float
|
HTTP timeout in seconds. |
10.0
|
Returns:
| Type | Description |
|---|---|
dict
|
Decoded JSON response of the |
Source code in src/quiltwright/bridge.py
332 333 334 335 336 337 338 339 340 | |
save_and_cast_quilt(quilt, stem, spec, *, cast=True, bridge_url=BRIDGE_URL, timeout=10.0)
¶
Write a quilt to disk, then hand Bridge the path to it.
The two calls this composes take different argument types, and the mistake
is invisible until a panel is connected: :func:save_quilt takes the
array, :func:cast_quilt takes a path. Passing the array to the caster
raises argument should be a str or an os.PathLike object ... not
'ndarray' -- after the render, which for a ray-traced quilt is minutes
later and the worst possible moment to find out.
The file is confirmed on disk before Bridge is contacted, and a failed cast is returned rather than raised, so losing the display never costs the render.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quilt
|
ndarray
|
RGB array from :func: |
required |
stem
|
str | Path
|
Output path without the quilt suffix or extension. |
required |
spec
|
QuiltSpec
|
Quilt specification, used for both the filename and Bridge. |
required |
cast
|
bool
|
Whether to push the written file to the Looking Glass.
|
True
|
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
BRIDGE_URL
|
timeout
|
float
|
HTTP timeout in seconds per Bridge request. |
10.0
|
Returns:
| Type | Description |
|---|---|
tuple[Path, str | None]
|
|
Source code in src/quiltwright/bridge.py
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
save_quilt(quilt, stem, spec)
¶
Write a quilt to PNG using the Looking Glass filename convention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quilt
|
ndarray
|
RGB array from :func: |
required |
stem
|
str | Path
|
Output path without the quilt suffix or extension.
Any |
required |
spec
|
QuiltSpec
|
Quilt specification (encodes the suffix metadata). |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The path written, e.g. |
Source code in src/quiltwright/quilt.py
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 | |
scene_depths(plotter, *, fov=14.0, zoom=None, labels=DEPTH_LABELS)
¶
Near, focal and far distances for the scene, as :func:render_quilt will see them.
Measures the plotter's bounding box along the view axis, then applies the
same framing :func:render_quilt applies before sweeping -- narrowing the
FOV and dollying back, then the optional zoom dolly. Reading the camera
as-is instead is the tempting shortcut and it is wrong: the render's FOV
and focal distance are both different by then, so the disparity computed
from them describes a picture nobody is going to make. Nothing is
mutated; the arithmetic is done on copies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plotter
|
A |
required | |
fov
|
float | None
|
The vertical FOV that will be passed to :func: |
14.0
|
zoom
|
float | None
|
The zoom that will be passed to :func: |
None
|
labels
|
tuple[str, str, str]
|
Names for the near, focal and far entries, in that order. |
DEPTH_LABELS
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Labelled distances from the render camera, in scene units,
ready to hand to :func: |
Source code in src/quiltwright/lfd.py
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
stop_quilt(*, bridge_url=BRIDGE_URL, timeout=10.0)
¶
Stop playback: pause the current frame and hide the display window.
Bridge's own bridge.js <https://github.com/Looking-Glass/bridge.js>_
SDK documents delete_playlist as the way to stop a playlist, and
an earlier version of this function called it. In testing it reliably
left Bridge unresponsive to every further HTTP call -- reproduced twice,
once mid-video and once on a single still image, so it isn't a
large-file decode race. This function deliberately avoids
delete_playlist and reaches the same end state (nothing visible,
playback halted) through calls already proven safe: the playlist from
:func:cast_quilt is left instantiated but paused and hidden, rather
than deleted. The next :func:cast_quilt plays a playlist of its own
rather than reusing this one, so what is left behind does not block it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bridge_url
|
str
|
Base URL of the Bridge HTTP API. |
BRIDGE_URL
|
timeout
|
float
|
HTTP timeout in seconds. |
10.0
|
Returns:
| Type | Description |
|---|---|
dict
|
Decoded JSON response of the final |
Source code in src/quiltwright/bridge.py
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
sweep_spec(n_views, view_cone, tile_width, tile_height)
¶
Geometry for a plain ordered view sweep rather than a tiled quilt.
A quilt's view count is columns * rows, so a rectangular grid cannot
express a prime count. A single row can express any count at all, which
is what consumers that want the views as separate frames -- hologram
printers, lenticular interlacers -- actually ask for. The camera sweep is
identical either way; only the packing differs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_views
|
int
|
Number of views in the sweep. |
required |
view_cone
|
float
|
Total horizontal camera sweep in degrees. |
required |
tile_width
|
int
|
Pixel width of one view. |
required |
tile_height
|
int
|
Pixel height of one view. |
required |
Returns:
| Type | Description |
|---|---|
QuiltSpec
|
A single-row :class: |
Source code in src/quiltwright/quilt.py
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | |
view_angles(spec)
¶
Signed angle of every view about the focal point, in degrees.
The sweep's angular sampling, independent of how a backend realises it.
An off-axis sweep converts these to lateral eye offsets with
:func:view_offsets; a toe-in sweep
(:func:~quiltwright.povray.toe_in_cameras) swings the eye around the
focal point by these angles directly. Both sample the same angles, so
the sampling interval quoted in a depth budget means the same thing
either way.
Ordered to match quilt view order: view 0 is the leftmost camera, so
angles run from -view_cone/2 to +view_cone/2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
QuiltSpec
|
Quilt or sweep specification (view count + cone angle). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of shape |
Source code in src/quiltwright/quilt.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | |
view_disparity(spec, fov, focal_distance, depth)
¶
Pixel shift of a feature between adjacent quilt views.
This is the number that decides whether a hologram fuses. A lenticular display blends neighbouring views optically, so content that moves only a pixel or two between them reads as solid depth, while larger shifts read as ghosting or a visible stack of copies. Rendered scenes that "look fine" flat routinely blow this budget.
Derived from the off-axis projection: a point at depth along the view
axis lands at image coordinate (D/aspect)(x/depth + s(1/Z - 1/depth))
for eye offset s, so the shift across the whole cone is
[tan(cone/2)/tan(fov/2)] * (1 - Z/depth) * tile_height pixels, which
divided between n_views - 1 gaps gives this. The aspect ratio
cancels. Verified against ray-traced renders to within 0.5%.
Two consequences worth internalising: content at the focal plane has zero disparity, and a narrower FOV increases disparity, because it magnifies the scene and the parallax along with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
QuiltSpec
|
Quilt specification (view count + cone angle + tile size). |
required |
fov
|
float
|
Vertical field of view in degrees. |
required |
focal_distance
|
float
|
Camera-to-focal-plane distance, in scene units. |
required |
depth
|
float
|
Distance of the content of interest from the camera, in
scene units. Use |
required |
Returns:
| Type | Description |
|---|---|
float
|
Adjacent-view shift in pixels. Roughly 4-5 px is the practical ceiling; beyond ~8 px expect visible ghosting on hard edges. |
Source code in src/quiltwright/quilt.py
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | |
view_offsets(spec, distance)
¶
Horizontal camera offsets (world units) for every view in the quilt.
Cameras sweep a total angle of spec.view_cone centred on the base
camera position, at constant distance from the focal plane. Offsets are
ordered to match quilt view order: view 0 is the leftmost camera.
The eye travels along a straight line perpendicular to the view axis,
which is what an off-axis sweep wants -- the image planes stay parallel
and the frustum shear does the rest. A toe-in sweep moves the eye along
a circular arc instead; see :func:~quiltwright.povray.toe_in_cameras.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
QuiltSpec
|
Quilt specification (view count + cone angle). |
required |
distance
|
float
|
Distance from camera to the focal plane. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of shape |
Source code in src/quiltwright/quilt.py
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | |