Skip to content

Commit

Permalink
Standard input/output support 3: Python SDK stdout impl/examples/docs (
Browse files Browse the repository at this point in the history
…#4513)

Allow the Python SDK to stream RRD data to stdout.

Checks:
- [x] `just py-build && echo 'hello from stdin!' | python
examples/python/stdio/main.py | rerun -`

---

Part of a small PR series to add stdio streaming support to our Viewer
and SDKs:
- #4511
- #4512 
- #4513
- #4514
  • Loading branch information
teh-cmc authored Dec 14, 2023
1 parent 6e49d84 commit da466ee
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 2 deletions.
6 changes: 6 additions & 0 deletions docs/content/reference/sdk-operating-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ Use [`RecordingStream::save`](https://docs.rs/rerun/latest/rerun/struct.Recordin

Streams all logging data to standard output, which can then be loaded by the Rerun Viewer by streaming it from standard input.

#### Python

Use [`rr.stdout`](https://ref.rerun.io/docs/python/stable/common/initialization_functions/#rerun.stdout?speculative-link).

Check out our [dedicated example](https://github.com/rerun-io/rerun/tree/latest/examples/python/stdio/main.py?speculative-link).

#### Rust

Use [`RecordingStream::stdout`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.stdout?speculative-link).
Expand Down
1 change: 1 addition & 0 deletions examples/python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
-r segment_anything_model/requirements.txt
-r shared_recording/requirements.txt
-r signed_distance_fields/requirements.txt
-r stdio/requirements.txt
-r structure_from_motion/requirements.txt
-r template/requirements.txt
22 changes: 22 additions & 0 deletions examples/python/stdio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Standard Input/Output example
python: https://github.com/rerun-io/rerun/tree/latest/examples/python/stdio/main.py?speculative-link
rust: https://github.com/rerun-io/rerun/tree/latest/examples/rust/stdio/src/main.rs?speculative-link
cpp: https://github.com/rerun-io/rerun/tree/latest/examples/cpp/stdio/main.cpp?speculative-link
thumbnail: https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/480w.png
thumbnail_dimensions: [480, 298]
---

<picture>
<img src="https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/stdio/25c5aba992d4c8b3861386d8d9539a4823dca117/1200w.png">
</picture>

Demonstrates how to log data to standard output with the Rerun SDK, and then visualize it from standard input with the Rerun Viewer.

```bash
echo 'hello from stdin!' | python main.py | rerun -
```
21 changes: 21 additions & 0 deletions examples/python/stdio/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""
Demonstrates how to use standard input/output with the Rerun SDK/Viewer.
Usage: `echo 'hello from stdin!' | python main.py | rerun -`
"""
from __future__ import annotations

import sys

import rerun as rr # pip install rerun-sdk

# sanity-check since all other example scripts take arguments:
assert len(sys.argv) == 1, f"{sys.argv[0]} does not take any arguments"

rr.init("rerun_example_stdio")
rr.stdout()

input = sys.stdin.buffer.read()

rr.log("stdin", rr.TextDocument(input.decode("utf-8")))
1 change: 1 addition & 0 deletions examples/python/stdio/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rerun-sdk
4 changes: 2 additions & 2 deletions rerun_py/rerun_sdk/rerun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
set_thread_local_data_recording,
)
from .script_helpers import script_add_args, script_setup, script_teardown
from .sinks import connect, disconnect, memory_recording, save, serve, spawn
from .sinks import connect, disconnect, memory_recording, save, serve, spawn, stdout
from .time import (
disable_timeline,
reset_time,
Expand Down Expand Up @@ -192,7 +192,7 @@ def _init_recording_stream() -> None:
from rerun.recording_stream import _patch as recording_stream_patch

recording_stream_patch(
[connect, save, disconnect, memory_recording, serve, spawn]
[connect, save, stdout, disconnect, memory_recording, serve, spawn]
+ [
set_time_sequence,
set_time_seconds,
Expand Down
28 changes: 28 additions & 0 deletions rerun_py/rerun_sdk/rerun/sinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,34 @@ def save(path: str | pathlib.Path, recording: RecordingStream | None = None) ->
bindings.save(path=str(path), recording=recording)


def stdout(recording: RecordingStream | None = None) -> None:
"""
Stream all log-data to stdout.
Pipe it into a Rerun Viewer to visualize it.
Call this _before_ you log any data!
If there isn't any listener at the other end of the pipe, the `RecordingStream` will
default back to `buffered` mode, in order not to break the user's terminal.
Parameters
----------
recording:
Specifies the [`rerun.RecordingStream`][] to use.
If left unspecified, defaults to the current active data recording, if there is one.
See also: [`rerun.init`][], [`rerun.set_global_data_recording`][].
"""

if not bindings.is_enabled():
logging.warning("Rerun is disabled - save() call ignored. You must call rerun.init before saving a recording.")
return

recording = RecordingStream.to_native(recording)
bindings.stdout(recording=recording)


def disconnect(recording: RecordingStream | None = None) -> None:
"""
Closes all TCP connections, servers, and files.
Expand Down
17 changes: 17 additions & 0 deletions rerun_py/src/python_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ fn rerun_bindings(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(is_enabled, m)?)?;
m.add_function(wrap_pyfunction!(connect, m)?)?;
m.add_function(wrap_pyfunction!(save, m)?)?;
m.add_function(wrap_pyfunction!(stdout, m)?)?;
m.add_function(wrap_pyfunction!(memory_recording, m)?)?;
m.add_function(wrap_pyfunction!(serve, m)?)?;
m.add_function(wrap_pyfunction!(disconnect, m)?)?;
Expand Down Expand Up @@ -525,6 +526,22 @@ fn save(path: &str, recording: Option<&PyRecordingStream>, py: Python<'_>) -> Py
})
}

#[pyfunction]
#[pyo3(signature = (recording = None))]
fn stdout(recording: Option<&PyRecordingStream>, py: Python<'_>) -> PyResult<()> {
let Some(recording) = get_data_recording(recording) else {
return Ok(());
};

// The call to stdout may internally flush.
// Release the GIL in case any flushing behavior needs to cleanup a python object.
py.allow_threads(|| {
recording
.stdout()
.map_err(|err| PyRuntimeError::new_err(err.to_string()))
})
}

/// Create an in-memory rrd file
#[pyfunction]
#[pyo3(signature = (recording = None))]
Expand Down

0 comments on commit da466ee

Please sign in to comment.