# Image Streams > [!note] Tato stránka ještě není přeložena do češtiny a zobrazuje se anglicky. Image streams expose a continuous, low-latency video-like feed from a device's camera. They are intended for live preview, alignment and focus assistance — not for archival capture (use [measurements](rhopoint-elements-hub-endpoints-functionality-measurement-triggers.md) for that). A stream is configured once via `POST .../start`, then consumed either as a Server-Sent-Events feed for real-time delivery or polled frame-by-frame via the snapshot endpoint. ### Endpoints Overview | Method | Endpoint | Description | | --- | --- | --- | | POST | `/v1/devices/{deviceId}/image-streams/{sourceKey}/start` | Start a stream for a specific image source | | POST | `/v1/devices/{deviceId}/image-streams/{sourceKey}/configure` | Change exposure / scale / encoding while running | | GET | `/v1/devices/{deviceId}/image-streams/{sourceKey}/stream` | Subscribe to live frames via SSE (`text/event-stream`) | | GET | `/v1/devices/{deviceId}/image-streams/{sourceKey}/snapshot` | Pull a single frame (binary image) | | GET | `/v1/devices/{deviceId}/image-streams` | List all active streams on a connected device | | DELETE | `/v1/devices/{deviceId}/image-streams/{sourceKey}` | Stop and tear down the stream | ### Source Keys A device may expose more than one camera or imaging path. Each is addressed by a `sourceKey`. For Aesthetix devices the available keys are: | `sourceKey` | Camera | | --- | --- | | `spec` | Specular path | | `aspec` | Aspecular path | Other device classes may expose different keys; consult the device's documentation. ### Lifecycle ``` ┌──────────────────────────────┐ │ POST .../{sourceKey}/start │ ← configure exposure, scale, encoding └──────────────┬───────────────┘ │ ┌─────────┴─────────┐ │ │ ▼ ▼ GET .../stream GET .../snapshot (SSE feed) (single frame) │ │ │ POST .../configure (optional, while running) │ │ └─────────┬─────────┘ ▼ ┌──────────────────────────────┐ │ DELETE .../{sourceKey} │ └──────────────────────────────┘ ``` A stream remains active until explicitly stopped via `DELETE` or until the device is disconnected. Reconnecting to the device does not automatically restore previously running streams. ### Starting a Stream ```http POST /v1/devices/{deviceId}/image-streams/{sourceKey}/start Content-Type: application/json { "exposureTimeMilliseconds": 50, "scaleFactor": 0.5, "mimeType": "image/jpeg", "quality": 0.85 } ``` ```bash curl -X POST http://localhost:42042/v1/devices/12f1d7dd07ac42a088c8f961b39d68ff/image-streams/aspec/start \ -H "Content-Type: application/json" \ -d '{ "exposureTimeMilliseconds": 50, "scaleFactor": 0.5, "mimeType": "image/jpeg", "quality": 0.85 }' ``` #### Request Body All fields are optional — omit them to use the server defaults. | Field | Type | Default | Description | | --- | --- | --- | --- | | `exposureTimeMilliseconds` | double | device-dependent | Sensor exposure per frame. Typical range 1–1000 ms. Higher values yield brighter frames but lower frame rate. | | `scaleFactor` | double | `1.0` | Downscaling factor applied server-side before encoding. Lower values reduce bandwidth and CPU at the cost of resolution. Typical range 0.1–1.0. | | `mimeType` | string | `"image/png"` | Per-frame encoding. One of `image/png` or `image/jpeg`. PNG is lossless and larger, JPEG smaller and lossy. | | `quality` | double | `0.85` | JPEG quality (0.1–1.0). Ignored when `mimeType` is `image/png`. | #### Response ```json { "deviceId": "12f1d7dd07ac42a088c8f961b39d68ff", "sourceKey": "aspec", "exposureTimeMilliseconds": 50, "scaleFactor": 0.5, "mimeType": "image/jpeg", "quality": 0.85, "startedAt": "2025-11-12T08:20:15.4567890+00:00", "status": "active" } ``` ### Consuming the Stream (SSE) ```http GET /v1/devices/{deviceId}/image-streams/{sourceKey}/stream Accept: text/event-stream ``` The response is a Server-Sent-Events stream (RFC 8895). The hub sets `Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`. Each frame arrives as one SSE event: ``` data: {"mimeType":"image/jpeg","image":"","timestamp":"2025-11-12T08:20:15.5123456+00:00"} ``` #### Event Payload | Field | Type | Description | | --- | --- | --- | | `mimeType` | string | MIME type of the encoded frame (matches the stream's current `mimeType`). | | `image` | string | Base64-encoded image bytes. Decode and feed directly to your image viewer. | | `timestamp` | string | ISO-8601 capture time of the frame (with `+00:00` UTC offset). Useful for measuring end-to-end latency. | #### Error Events If the device errors out mid-stream, an SSE event with `event: error` is sent before the connection closes: ``` event: error data: {"errorCode":"","message":""} ``` Treat this as a terminal event — re-subscribe by issuing `GET .../stream` again, or stop and recreate the stream if the underlying error was caused by a configuration mismatch. #### Following the Stream from the Shell ```bash curl -N http://localhost:42042/v1/devices/12f1d7dd07ac42a088c8f961b39d68ff/image-streams/aspec/stream ``` `-N` disables curl's output buffering so each event prints as it arrives. Useful for smoke-testing the stream without writing a client. #### Consuming the Stream from a Browser `EventSource` is the standard Web API for SSE and the easiest way to consume the feed. The hub's bundled demo app (`/app/`) uses exactly this pattern: ```js const url = `http://localhost:42042/v1/devices/${deviceId}/image-streams/${sourceKey}/stream`; const source = new EventSource(url); source.onmessage = (event) => { const { mimeType, image, timestamp } = JSON.parse(event.data); document.getElementById('preview').src = `data:${mimeType};base64,${image}`; }; source.addEventListener('error', () => { source.close(); // terminal — re-subscribe by creating a new EventSource }); ``` The `data:` URL form works directly in `` tags, `.drawImage()` and `createImageBitmap()` without an explicit Base64 decode step. ### Snapshot — Single Frame ```http GET /v1/devices/{deviceId}/image-streams/{sourceKey}/snapshot ``` ```bash curl -o frame.jpg \ http://localhost:42042/v1/devices/12f1d7dd07ac42a088c8f961b39d68ff/image-streams/aspec/snapshot ``` Returns one frame as a raw binary image with the `Content-Type` set to the stream's current `mimeType` (`image/png`, `image/jpeg`, or `image/bmp`). Use this when you need a single frame on demand and do not want the overhead of opening an SSE connection. > [!note] > Snapshots and SSE both read from the same underlying camera. Pulling a snapshot while an SSE feed is active competes for the next frame and is rarely useful. The Elements Hub demo app disables the snapshot button while SSE is running for this reason. ### Reconfiguring a Running Stream ```http POST /v1/devices/{deviceId}/image-streams/{sourceKey}/configure Content-Type: application/json { "exposureTimeMilliseconds": 100, "scaleFactor": 0.75 } ``` The same field set as `start`; only the fields you include are changed. Already-subscribed SSE clients keep their connection — subsequent frames simply use the new configuration. Use this for live UI sliders that adjust exposure or scaling without forcing the user to disconnect and reconnect. ### Listing Active Streams ```http GET /v1/devices/{deviceId}/image-streams ``` Returns an array of `ImageStreamResponse` records (same shape as the `start` response), one per `sourceKey` that currently has an active stream on the device. An empty array means no stream is currently running. ### Stopping a Stream ```http DELETE /v1/devices/{deviceId}/image-streams/{sourceKey} ``` ```bash curl -X DELETE http://localhost:42042/v1/devices/12f1d7dd07ac42a088c8f961b39d68ff/image-streams/aspec ``` Stops the stream and releases the camera-side resources. Any SSE consumers see the connection close immediately after the next pending frame. `DELETE` is idempotent at the API level — calling it a second time returns `404` (`E37`), which most clients can ignore safely on shutdown paths. ### Best Practices - **Start before subscribe**: always issue `POST .../start` before `GET .../stream`. Subscribing without a started stream returns `404` (`E37`). - **Set `scaleFactor` for the consumer**: a 0.5 factor cuts bandwidth and CPU to roughly a quarter without a noticeable difference for live-preview use cases. Full-resolution streaming is rarely the right default. - **Prefer JPEG for live preview**: at quality 0.85 the file size is typically 5–10× smaller than PNG, and the artefacts are invisible at preview resolutions. - **Close `EventSource` on tab hide**: browsers throttle background tabs, which can starve a hot SSE connection. Listen for `visibilitychange` and close/reopen the stream as needed. - **Reconfigure rather than restart**: `POST .../configure` is faster than `stop` + `start` because the camera does not reinitialise. - **Stop on disconnect**: explicit `DELETE` on shutdown frees the camera immediately. `POST /v1/lifecycle/shutdown` will also stop all streams, but explicit cleanup surfaces errors earlier. ### Error Cases | Condition | HTTP | Error Code | | --- | --- | --- | | Connected device not found | 404 | `E5` | | Stream not started (subscribe/snapshot/configure/stop without a prior `start`) | 404 | `E37` | | Unsupported `sourceKey` for the device | 404 | device-class–specific | See [Error Handling](rhopoint-elements-hub-error-handling.md) for the response format.