Architecture and Core Concepts

This guide explains the fundamental ideas, design philosophy, and architectural decisions behind Luxar.

Big Picture: What is Luxar?

Luxar is a high-performance system for compiling and visualizing arbitrary-sized n-dimensional scenes containing points, lines, Gaussian splats, and triangle meshes. It enables:

  • Interactive exploration of billion-primitive datasets (points, lines, splats; meshes to millions of triangles) at 60 FPS

  • Arbitrary dimensionality (3D, 4D, 5D, nD) with intuitive navigation

  • Memory efficiency through progressive loading and intelligent caching

  • High quality rendering with HDR support and post-processing effects

  • Multi-primitive scenes: Points, lines, Gaussian splats, and triangle meshes (the meshes are shaded; the other three are emissive)

Key Innovation: Combine spatial indexing with nD hypersphere slicing to enable interactive exploration of datasets that don’t fit in memory.

Luxar delivers visualization performance limited only by your graphics card, display resolution, and network bandwidth—not by software constraints.

Design Philosophy

Luxar is built on several core principles:

  1. Progressive Everything

    Motivation: Billion-point datasets cannot fit in memory.

    Solution: Write data progressively (no caching), load data lazily (chunk-based), render incrementally (spatial culling).

    Benefit: Handle TB-scale datasets on GB-scale machines.

  2. Spatial Locality is Sacred

    Motivation: Adjacent points in space should be adjacent in memory for compression and cache efficiency.

    Technique: Morton/Hilbert space-filling curves map nD coordinates to 1D ordering while preserving locality.

    Impact: 2-10× better compression, 5-20× faster queries.

  3. nD First, 3D Second

    Philosophy: Don’t reduce nD data to 3D - visualize it natively.

    Approach: Display 3 dimensions, slice through others with hypersphere visibility.

    Result: Time-series, multi-channel, hyperspectral data visualized naturally.

  4. Zero-Copy When Possible

    Motivation: Memory copies are expensive for large datasets.

    Implementation: Use views, references, broadcasting instead of duplication.

    Example: Uniform color stored once, broadcasted to 1M points = 99.9% memory savings.

  5. Semantic Awareness

    Idea: Know what data represents (positions, colors, radii) to make smart encoding decisions.

    Benefit: Automatic quantization with appropriate precision for each data type.

Architectural Layers

Luxar consists of five distinct layers:

Server Layer (FastAPI)

Purpose: Serve Zarr datasets over HTTP for browser-based visualization

Key Components:

  • create_server_app(): Factory function for FastAPI applications

  • DirectoryListingStaticFiles: Custom static file handler with Zarr support

  • NetworkSimulationMiddleware: Test performance under realistic network conditions

  • CORS middleware: Enable cross-origin requests from viewer

Architecture:

The server layer sits between storage and viewer, providing:

  1. Static File Serving: Zarr chunks and metadata served as static files

  2. Directory Listing: JSON directory listing for Zarr structure discovery

  3. Health Checks: /health endpoint for monitoring

  4. Network Simulation: Optional middleware to simulate bandwidth, latency, packet loss

Design Decisions:

Why FastAPI?

  • Modern async framework (handles many concurrent requests efficiently)

  • Automatic OpenAPI documentation

  • Type hints for request/response validation

  • Easy middleware integration

Why static file serving?

  • Zarr is designed for HTTP range requests

  • No need for complex query API - chunks are addressed directly

  • Browser can cache chunks efficiently

  • CDN-friendly for production deployments

Why directory listing?

  • Viewer needs to discover Zarr structure

  • Standard HTTP directory indices do not work for all browsers

  • JSON format enables programmatic access

Server Creation Example:

from luxar.cli.main import create_server_app
import uvicorn

# Create configured FastAPI app
app = create_server_app("/path/to/data.luxar.zarr")

# Run server
uvicorn.run(app, host="127.0.0.1", port=8000)

The server automatically:

  • Serves consolidated metadata for fast initialization

  • Provides directory listings for Zarr groups

  • Handles CORS for cross-origin viewer access

  • Includes health check at /health

Integration Testing:

The server is designed for testing without mocking:

  • Create real server instances in tests

  • Make actual HTTP requests

  • Verify end-to-end behavior

  • Test with real Zarr data

See Programmatic Server Creation for detailed examples.

Python Layer (Data Creation)

Purpose: Create and encode visualization data

Packages:

  • luxar.core - Scene graph data structures

  • luxar.validation - Input validation with helpful errors

  • luxar.encoding - Semantic type-aware array encoding

  • luxar.io - Progressive writing with spatial ordering

Key Concepts:

  • Scene Graph: Hierarchical organization (Scene → Groups → DataNodes)

  • Dimensions: Define nD space with units, ranges, navigation

  • Transforms: 4×4 matrices compose hierarchically (parent × child)

  • Semantic Types: COORDINATE, COLOR, POSITIVE_SCALAR, BOUNDED_SCALAR

  • Encoding Modes: AUTO (smart), PRECISION (lossless), MEMORY (aggressive compression)

Data Flow:

User Data (numpy arrays)
    ↓
Validation (check shapes, ranges, types)
    ↓
Semantic Typing (understand what data represents)
    ↓
Encoding (quantize based on semantic type)
    ↓
Spatial Ordering (Morton/Hilbert curves)
    ↓
Progressive Writing (no memory cache, stream to disk)
    ↓
Zarr Store (compressed, indexed, ready to serve)

Storage Layer (Zarr Format)

Purpose: Efficient storage and streaming

Key Innovations:

  • Chunk-based spatial index: Precomputed bounding boxes enable O(chunks × dims) queries

  • Compound ordering: Discrete dimensions (time, channel) → spatial dimensions (x, y, z)

  • Compression pipeline: Quantization (2-4×) + blosc/zstd (2-10×) = 4-40× total

  • Metadata consolidation: Root metadata document enables fast initialization

Design Decisions:

Why Zarr?

  • Cloud-native format (HTTP range requests)

  • Chunked storage (load only what’s visible)

  • Compression support (built-in)

  • Python and JavaScript libraries available

Why Morton/Hilbert ordering?

  • Space-filling curves preserve spatial locality

  • Adjacent points in nD space → adjacent in 1D storage

  • Better compression ratios (similar values group together)

  • Faster queries (scan fewer chunks)

TypeScript Layer (Viewer)

Purpose: Interactive visualization in the browser

Packages:

  • cache - Three-level caching (Memory, OPFS, HTTP)

  • data - Scene loading, spatial queries, nD slicing

  • rendering - WebGL materials, post-processing

  • scene - Scene graph management, animation

  • controls - Camera controls (orbit, fly)

  • input - Keyboard/mouse handling

  • ui - GUI components (sliders, monitors)

Key Algorithms:

  1. Chunk Query (O(chunks × dims)):

    Test each chunk’s bounding box for AABB intersection with query region. Load only chunks whose bounds overlap the view.

  2. nD Hypersphere Slicing:

    Points with radius R are visible if their distance in non-displayed dimensions ≤ R. Effective radius shrinks as you navigate away:

    effective_radius = √(radius² - distance_in_nondisplayed_dims²)

  3. Intelligent Prefetching:

    When loading chunk N, predict user will navigate to adjacent chunks (±1 in each dimension). Prefetch in background for instant loading.

  4. Array Decoding:

    Reverse Python encoding: dequantize integers → floats, expand broadcasted values, resolve LUT indices.

Design Decisions:

Why multiple cache levels?

  • L0 (decompressed in-memory): Ready-to-use decoded chunks, no re-decompress

  • L1 (memory, segmented LRU): Ultra-fast (~1μs) compressed chunks

  • L2 (OPFS): Persistent (~1ms) across page reloads

  • HTTP: Unlimited (~100ms) source of truth

(An additional “S-cache” — the SliceCache — sits above L0 and LRU-caches fully decoded per-slice geometry ladders, so revisiting a slice skips the whole query→fetch→decode pipeline.)

Why nD slicing vs dimension reduction?

  • Preserves all data dimensions

  • Intuitive keyboard navigation (1-9 select a non-displayed dimension, [ ] navigate)

  • No information loss from projection

WebGL Layer (Rendering)

Purpose: GPU-accelerated rendering

Key Techniques:

  1. World-Space Point Sizing:

    Points are specified in world units (micrometers, etc.) and automatically sized based on camera distance using angular diameter.

  2. Gaussian Splatting:

    Smooth point rendering using 2D Gaussian kernel in fragment shader. Creates soft, anti-aliased points.

  3. HDR Rendering:

    Float16 framebuffers support colors > 1.0 for scientific accuracy. Tone mapping converts to display range.

  4. Multi-Pass Effects:

    Post-processing effects (bloom, detector noise) composed dynamically based on enabled features.

Core Concepts Deep Dive

nD Visualization Paradigm

The Problem: How do you visualize 5D data (x, y, z, time, channel) on a 2D screen?

Traditional Approach (Dimension Reduction):

  • Project 5D → 3D using PCA, t-SNE, UMAP

  • Loss: Lose actual spatial relationships, lose 2 dimensions of information

Luxar Approach (nD Slicing):

  • Display 3 dimensions (x, y, z)

  • Slice through 2 dimensions (time, channel)

  • Points visible based on hypersphere intersection

Benefits:

  • No information loss - all 5 dimensions preserved

  • Intuitive - navigate time like any other dimension

  • Fast - spatial index makes queries O(chunks) not O(points)

Spatial Indexing Strategy

The Problem: Loading all points to find which are visible is too slow for billion-point datasets.

Solution: Chunk-based AABB spatial index

How it Works:

  1. Preprocessing (Python, one-time):

    • Divide points into chunks (e.g., 2,048 points per chunk)

    • Calculate bounding box for each chunk

    • Store chunk_bounds array: [xmin, xmax, ymin, ymax, ...] per chunk

  2. Query (TypeScript, real-time):

    • User navigates to position P with tolerance T

    • Test each chunk: does its AABB intersect the query box?

    • Load only matching chunks (typically 1-10 out of 100-1000 chunks)

  3. Point Filtering (TypeScript, real-time):

    • Within loaded chunks, test each point for visibility

    • Display points within hypersphere

Complexity:

  • Preprocessing: O(N) to calculate bounds

  • Query: O(chunks × dims) - typically 1000 chunks × 5 dims = 5000 ops

  • vs. Naive: O(N) - would be 1,000,000,000 ops for billion points!

  • Speedup: ~200,000× faster

Compound Ordering for nD Data

The Problem: How to organize nD data for efficient time-series animation AND spatial queries?

Naive Approach: Morton order on all dimensions

  • Pros: Simple, preserves locality

  • Cons: Time frames scattered throughout file, poor compression

Luxar Approach: Two-level compound ordering

  1. Primary: Group by discrete dimensions (time, channel)

    • All time=0 points together

    • All time=1 points together

    • etc.

  2. Secondary: Morton order within each group (x, y, z)

    • Spatial locality within each time frame

    • Better compression (similar time frames compress together)

Result:

  • Time-series animation: Load contiguous chunks (excellent I/O)

  • Spatial queries: Morton order within groups (excellent locality)

  • Compression: Grouping similar data (2-3× better ratios)

Gaussian Splatting Integration

The Idea: Fit smooth Gaussian “splats” to images instead of discrete pixels

Motivation:

  • Scientific images are inherently continuous (microscopy, astronomy)

  • Gaussian splats are a natural representation (PSF = Gaussian)

  • Enables denoising, compression, and continuous rendering at any zoom (no voxel interpolation)

Luxar Integration:

  • Fit splats using luxar.gsplats.fit_gaussian_splats()

  • Save as a GSplats node in the scene graph

  • Render using GaussianSplatModel (differentiable PyTorch model)

  • Visualize in viewer with same spatial indexing as points

Use Cases:

  • Compress microscopy images 10-100×

  • Denoise while preserving features

  • Render continuously at any zoom, free of voxel-interpolation artefacts

  • Multi-scale image analysis

Transform Composition

The Problem: Matrix multiplication order is confusing (left vs right multiply)

Luxar Convention: Right-multiply with explicit compose() function

from luxar.core.transforms import translate, rotate, compose

# Create transforms
T1 = translate(10, 0, 0)   # Move right
T2 = rotate(90, 'z')       # Rotate 90° around Z
T3 = translate(0, 5, 0)    # Move up

# Compose: applies T1 first, T3 last
result = compose(T1, T2, T3)

# Mathematically: result = I @ T1 @ T2 @ T3
# Applied to point: (T1 @ T2 @ T3) @ point

Key Insight: compose(A, B, C) reads left-to-right but uses right-multiplication internally.

Why This Matters:

  • Transform composition order affects results (not commutative)

  • Parent transforms apply after child transforms (hierarchical)

  • NumPy (row-major) vs THREE.js (column-major) requires transposition

Encoding Strategy Selection

The Problem: One encoding doesn’t fit all data types

Semantic Type System:

Each array is classified by what it represents:

  • COORDINATE: Positions in space (bounds can vary, quantize with tolerance)

  • COLOR: RGB values (bounded [0,1] for SDR, unbounded for HDR)

  • POSITIVE_SCALAR: Radii, amplitudes (always ≥0, relative precision matters)

  • BOUNDED_SCALAR: Sharpness, opacity (fixed range, absolute precision)

Encoding Decision Tree:

Is data uniform (all values identical)?
├─ YES → Broadcast (store 1 value, 99.9% savings)
└─ NO ↓

Does exact duplicate exist in registry?
├─ YES → Array Reference (store pointer, 100% dedup)
└─ NO ↓

Does data have ≤256 unique values?
├─ YES → LUT Encoding (palette + indices, 4-8× savings)
└─ NO ↓

Dtype Encoding based on semantic type:
- COORDINATE → uint16 (±32K range, ~0.1% precision)
- COLOR (SDR) → uint8 (256 levels per channel)
- COLOR (HDR) → geolog_perchannel_u16 (per-channel true-log; decoded to float32)
- POSITIVE_SCALAR → uint16 (log quantization for range)
- BOUNDED_SCALAR → uint8 (linear quantization)

Result: Automatic optimal encoding with 4-40× compression while preserving visual quality.

Performance Model

Understanding Luxar’s performance characteristics.

Note

The numbers below are approximate order-of-magnitude estimates based on typical hardware (modern laptop/desktop with dedicated GPU, SSD storage, broadband network). Actual performance varies with hardware, dataset characteristics, and network conditions.

Write Performance (Python)

1M points, 3D:
- Without spatial ordering: ~5M points/sec
- With Morton ordering: ~1M points/sec (sorting overhead)
- With chunk bounds: ~800K points/sec (AABB calculation)

Tradeoff: 5× slower write, 200,000× faster queries (well worth it!)

Read Performance (TypeScript)

Query for visible points:
- Chunk query: ~1ms (O(chunks × dims), typically 1000 × 5)
- HTTP fetch: ~100ms per chunk (network latency)
- Decode: ~5ms per chunk (dequantization)
- Slice: ~10ms per chunk (visibility filtering)

Total: ~115ms for first load, ~16ms for cached
With prefetching: ~16ms average (cache hit rate >80%)

Rendering Performance (WebGL)

GPU rendering (60 FPS target):
- 100K points: Vertex shader ~2ms, Fragment ~4ms = ~6ms ✓
- 1M points: Vertex ~8ms, Fragment ~8ms = ~16ms ✓
- 10M points: Vertex ~60ms, Fragment ~60ms = ~120ms (~8 FPS) ⚠

Optimization: Spatial culling reduces points to render by 10-100×

When to Use Luxar

Luxar is Ideal For:

  • ✅ Large datasets (>100K primitives, up to billions of points/lines/splats, or millions of triangles)

  • ✅ nD scientific data (microscopy time-series, multi-channel imaging)

  • ✅ Interactive exploration (need to navigate/inspect data)

  • ✅ Remote visualization (data on server, view in browser)

  • ✅ Publication figures (HDR, high quality rendering)

Consider Alternatives For:

  • ❌ Small datasets (<10K points) - overhead not worth it, use matplotlib/plotly

  • ❌ Real-time streaming - Luxar is for static datasets, not live data streams

  • ❌ Mesh authoring / CAD - Luxar renders triangle meshes (add_mesh, luxar mesh import), but it does not edit them, and a mesh has no spatial index (see docs/specs/MESH_NODE_SPEC.md §9), so one level always loads whole. Spatial partitioning is available (add_mesh(partition=…)), which splits a large surface into frustum-cullable parts — but each part still loads whole

  • ❌ 2D plots - Use specialized 2D libraries (bokeh, plotly)

Common Workflows

Workflow 1: Microscopy Time-Series

# You have: 4D microscopy data (X, Y, Z, Time)
# Goal: Visualize cell tracking over time

from luxar.core import Scene, Dimensions, Dimension
from luxar.io import LuxarZarrCompiler
import numpy as np

# Define 4D dimensions
dims = Dimensions([
    Dimension("X", unit="um", spatial=True, display=True),
    Dimension("Y", unit="um", spatial=True, display=True),
    Dimension("Z", unit="um", spatial=True, display=True),
    Dimension("Time", discrete=True, display=False, step=0.5),
])

# Write with compound ordering for efficient time navigation
with LuxarZarrCompiler('cells.luxar.zarr') as c:  # Hilbert ordering by default
    scene = c.create_scene(dimensions=dims)

    # Track cell positions over time
    for t, positions_t in enumerate(cell_positions_by_time):
        # Add time coordinate
        positions_4d = np.column_stack([positions_t, np.full(len(positions_t), t * 0.5)])
        scene.add_points(f"cells_t{t}", positions_4d, colors=track_colors[t])

# Result: Navigate time with [ ] keys, see cells move

Workflow 2: Multi-Channel Imaging

# You have: 5D data (X, Y, Z, Channel, Wavelength)
# Goal: Explore different fluorescence markers

dims = Dimensions([
    Dimension("X", unit="um", spatial=True),
    Dimension("Y", unit="um", spatial=True),
    Dimension("Z", unit="um", spatial=True),
    Dimension("Channel", discrete=True, display=False, categories=["DAPI", "GFP", "mCherry"]),
    Dimension("Wavelength", unit="nm", display=False, discrete=True),
])

# At most 3 dimensions can be displayed, so Channel and Wavelength are
# non-displayed (navigated with the [ ] keys). A non-spatial, non-displayed
# dimension must be discrete, so Wavelength is marked discrete=True
# explicitly (otherwise Luxar sets it automatically and emits a warning).

# Categorical dimension allows channel selection by name
# Discrete + spatial ordering groups all DAPI points together

Workflow 3: Gaussian Splat Fitting

# You have: 2D/3D microscopy image
# Goal: Fit Gaussian splats for compression/denoising

from luxar.gsplats import fit_gaussian_splats

# Fit splats to image
result = fit_gaussian_splats(
    image,
    n_iters=300,
    lr=0.01,
    loss_type="l1",
    seed_method="edges",  # Edge-based seeding
)

# Save for visualization
result.save("fitted.gsplats.zarr")

# Reconstruction (render back to image/volume)
reconstructed = result.render_to_volume(shape=image.shape)
compression_ratio = image.nbytes / result.centers.nbytes
print(f"Compression: {compression_ratio:.1f}×")

Key Takeaways

  1. Luxar is designed for scale: Billion points, TB datasets, interactive exploration

  2. Spatial indexing is the foundation: Makes everything fast

  3. nD is first-class: Not an afterthought, built-in from the start

  4. Semantic awareness: Understanding data types enables smart encoding

  5. Progressive architecture: No memory bottlenecks, handle any size data

  6. Browser-based: No installation, works on any device, shareable URLs

Next Steps

  • Try it: Start with a Quick Start from any package README

  • Explore code: Well-documented with extensive examples

  • Ask questions: GitHub issues/discussions