← Back to portfolio Français

Self-observing volumetric installation

Architecture overview — documented subsystem: aida-stream-llm-analysis

Purpose

Spectators stand around a 1-meter sphere. A hidden projector below the table renders a live point cloud of them onto its surface, reconstructed from three depth cameras. That same point cloud also conditions a generative visual pipeline, so the spectators' movement is literally shaping the imagery appearing on the sphere. A separate camera watches the whole installation from outside, and a vision-language model describes what it sees — in two languages — projecting that description as text over the sphere, while a condensed version of it is fed back in as a prompt that steers how the generated imagery evolves next. The installation observes itself, explains itself, and changes because of what it said — while the people around it are doing the same thing to it from the other direction.

Volumetric capture and spherical projection

Volumetric capture and spherical projection Three Orbbec depth cameras feed Carto, a Godot-based volumetric reconstruction program, which both drives the spherical projection that spectators see and conditions a generative Stable Diffusion visual pipeline with the reconstructed point cloud. 3x Orbbec depth cameras Volumetric capture of spectators Carto (Godot engine) Reconstructs the scene as a point cloud Spherical projection 1m sphere, hidden table projector Generative visual pipeline Point cloud conditionsStable Diffusion Spectators moving around the sphere become the generative model's live input
Depth capture branches into the sphere's display and the generative pipeline's input

Three Orbbec depth cameras capture the spectators around the table. Carto, a custom volumetric capture and reconstruction program built on the Godot engine, turns that into a live point cloud. That point cloud drives two things at once: the image projected onto the 1-meter sphere (via a projector hidden below the installation table), and the conditioning input to a generative Stable Diffusion visual pipeline — so the people physically present are, in effect, the model's live input.

The LLM self-observation loop

LLM self-observation feedback loop An AIDA fisheye camera observes the whole installation. A vision-language model in LM Studio describes the scene in English and French. That description is both projected as text over the sphere and reduced to keywords, which steer the generative visual pipeline. The new visuals appear on the sphere and are observed again by the same camera, closing the loop. AIDA fisheye camera Observes the whole installation LM Studio vision-language model Describes the scene, English and French Text overlay on sphere Over the point cloud and visuals Keyword extraction 1-3 concrete keywords, English only Generative visual pipeline Keywords steer the evolving imagery ↻ new visuals appear on the sphere and are observed again by the same camera — the installation watching itself evolve
A closed loop: the installation's own output becomes its next input

An AIDA N200 camera, mounted to see the whole installation (not just the sphere), streams over SRT. A vision-language model running locally in LM Studio periodically describes what it sees, once in English and once in French, each sent out as its own OSC message and projected as text over the sphere — layered on top of both the volumetric point cloud and the generated visuals. The English description is additionally reduced to one to three concrete keywords, which are fed back into the generative visual pipeline as a short prompt, nudging how the imagery evolves. Because the AIDA camera also sees whatever the sphere is currently displaying, the new visuals it produces get observed, described, and distilled into keywords again on the next cycle — a closed loop where the installation's own output becomes its own next input, independent of (but running alongside) the spectators' direct volumetric influence on the same generative pipeline.

aida-stream-llm-analysis: pipeline internals

The observation-and-feedback subsystem is a small set of scripts orchestrating a periodic capture → analyze → summarize → broadcast cycle, all coordinating through a shared image file and OSC messages.

aida-stream-llm-analysis pipeline internals capture_loop_srt.sh refreshes a frame in the background. manage_lmstudio.sh ensures the vision-language model is loaded CPU-only. analyze_latest_frame.py runs an English then a French pass. summarize_keywords.py reduces the English text to keywords. All three results are sent out as separate OSC messages. A separate overnight_monitor.sh process independently logs GPU, process, and disk health. capture_loop_srt.sh Refreshes a JPEGevery ~10s via SRT manage_lmstudio.sh Ensures server up, model loaded CPU-only analyze_latest_frame.py English pass, then French pass summarize_keywords.py Reduces English text to keywords OSC out, 3 ports Descriptions and keywords to the visuals overnight_monitor.sh runs independently, logging GPU, process, and disk health every 60s for unattended overnight operation
The capture → analyze → summarize → broadcast cycle, plus independent health monitoring

Capture — capture_loop_srt.sh

Runs continuously in the background: connects to the AIDA camera's SRT sub-stream (srt://<camera_ip>:<port>?streamid=<id>), grabs exactly one frame with ffmpeg, writes it to a fixed path (/tmp/latest_frame.jpg), disconnects, and sleeps for the configured interval (10 s by default) before repeating. Each connection is short-lived by design — the analysis side never touches the camera directly, only the file this script maintains.

Analysis — analyze_latest_frame.py

Reads whatever frame currently sits at that path, base64-encodes it into a chat-completions request to LM Studio's local OpenAI-compatible API, and sends the model's response out as a single OSC message. It has no camera dependency at all — everything camera-related is entirely the capture script's job. Notable design details:

Keyword extraction — summarize_keywords.py

Pure text-in/text-out, no camera or image involved: takes a description (the English output above), asks the same LM Studio model to reduce it to one to three concrete, real-world-object keywords — explicitly instructed to avoid abstract or emotional language — appends a fixed suffix ("fisheye 250 degrees", naming the AIDA camera's own lens geometry) and sends the combined result out as OSC. The suffix is a plain string, not model output, so it's always exact regardless of what the model returns.

Orchestration — run_pipeline.sh

Ties the above together into one continuously-running cycle:

  1. Confirms LM Studio is ready (via manage_lmstudio.sh) before touching the camera at all.
  2. Starts the capture loop in the background, tracking its PID for clean shutdown.
  3. Loops indefinitely: English description → (pause) → French description → (pause) → keyword summary of the English text → (pause) → repeat. With the default 5 s stagger, a full cycle is 15 s plus however long the two LM Studio calls themselves take.
  4. On Ctrl+C, kills the background capture process (and any ffmpeg child it spawned) but deliberately leaves LM Studio running, since it's a persistent local service rather than something owned by this pipeline run.

The English and French prompts are independently tunable "moods" for how the installation talks about what it sees — the default pipeline uses a "humble, eutopic and existentialist" framing, while the lighter alternating-analysis variant below uses a "dystopic and nihilist" one, making the installation's interpretive stance itself an adjustable artistic parameter rather than a fixed behavior.

LM Studio process management — manage_lmstudio.sh

Ensures the local inference server is running and the target vision-language model is loaded, idempotently (safe to call every run). Two things worth noting:

Operational variant — run_alternating_analysis.sh

A lighter-weight mode: just the English/French alternation, 5 s apart, with no capture-loop management and no keyword-summary step — useful when the capture loop is already running independently (e.g. during development, or a performance configuration that doesn't need the keyword-driven visual steering).

Unattended operation — overnight_monitor.sh

Runs independently of the analysis pipeline, logging one line every 60 s: per-GPU utilization, memory, temperature (flagging anything ≥ 85°C, ahead of the card's throttling point), and power draw; whether each key process (ossia-score, Godot_v4, the capture ffmpeg, and a Node-based process) is still alive; whether LM Studio's API still responds; and free disk space (flagging under 3 GB free). Each line is tagged ALL-OK or PROBLEM, so checking on an overnight run the next morning is a single grep -c "ALL-OK" or grep -v "ALL-OK" away.

OSC reference

AddressPortSent byContent
/camera/description9000analyze_latest_frame.py (English)English scene description
/camera/description9001analyze_latest_frame.py (French)French scene description
/camera/keywords9002summarize_keywords.py1–3 keywords + fixed suffix, feeds the generative visual pipeline

All three are sent to the same OSC host (the machine running the generative visual pipeline / Ossia Score patch), just on different ports and, for the two descriptions, the same address distinguished by port.

Components referenced but not detailed here

What this demonstrates

Beyond the artistic result, this subsystem required real-time multi-process orchestration across a GPU-constrained machine (deliberately keeping an LLM off the GPU so rendering and generation aren't starved), clean process lifecycle management (background capture with proper teardown, an idempotent service-readiness check safe to re-run mid-show), a small but real multilingual inference pipeline, and unattended-operation tooling (structured health logging) built specifically so an overnight run can be checked in seconds rather than by reading raw logs.