| @@ -4017,7 +4017,7 @@ dependencies = [ | |||
| "httpdate", | |||
| "itoa", | |||
| "pin-project-lite", | |||
| "socket2 0.4.10", | |||
| "socket2 0.5.7", | |||
| "tokio", | |||
| "tower-service", | |||
| "tracing", | |||
| @@ -9356,6 +9356,14 @@ dependencies = [ | |||
| "winapi-util", | |||
| ] | |||
| [[package]] | |||
| name = "terminal-print" | |||
| version = "0.3.5" | |||
| dependencies = [ | |||
| "dora-node-api", | |||
| "eyre", | |||
| ] | |||
| [[package]] | |||
| name = "terminal_size" | |||
| version = "0.3.0" | |||
| @@ -9838,7 +9846,7 @@ version = "1.6.3" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" | |||
| dependencies = [ | |||
| "cfg-if 0.1.10", | |||
| "cfg-if 1.0.0", | |||
| "static_assertions", | |||
| ] | |||
| @@ -32,6 +32,7 @@ members = [ | |||
| "libraries/extensions/telemetry/*", | |||
| "node-hub/dora-record", | |||
| "node-hub/dora-rerun", | |||
| "node-hub/terminal-print", | |||
| "libraries/extensions/ros2-bridge", | |||
| "libraries/extensions/ros2-bridge/msg-gen", | |||
| "libraries/extensions/ros2-bridge/python", | |||
| @@ -14,3 +14,9 @@ nodes: | |||
| input: terminal-input/data | |||
| outputs: | |||
| - echo | |||
| - id: terminal-print | |||
| build: cargo build -p terminal-print | |||
| path: dynamic | |||
| inputs: | |||
| echo: dora-echo/echo | |||
| @@ -0,0 +1,13 @@ | |||
| [package] | |||
| name = "terminal-print" | |||
| edition = "2021" | |||
| version.workspace = true | |||
| description.workspace = true | |||
| documentation.workspace = true | |||
| license.workspace = true | |||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |||
| [dependencies] | |||
| dora-node-api = { workspace = true, features = ["tracing"] } | |||
| eyre = "0.6.8" | |||
| @@ -1,89 +0,0 @@ | |||
| # Dora Node for plotting data with OpenCV | |||
| This node is used to plot a text and a list of bbox on a base image (ideal for object detection). | |||
| # YAML | |||
| ```yaml | |||
| - id: opencv-plot | |||
| build: pip install ../../node-hub/opencv-plot | |||
| path: opencv-plot | |||
| inputs: | |||
| # image: Arrow array of size 1 containing the base image | |||
| # bbox: Arrow array of bbox | |||
| # text: Arrow array of size 1 containing the text to be plotted | |||
| env: | |||
| PLOT_WIDTH: 640 # optional, default is image input width | |||
| PLOT_HEIGHT: 480 # optional, default is image input height | |||
| ``` | |||
| # Inputs | |||
| - `image`: Arrow array containing the base image | |||
| ```python | |||
| ## Image data | |||
| image_data: UInt8Array # Example: pa.array(img.ravel()) | |||
| metadata = { | |||
| "width": 640, | |||
| "height": 480, | |||
| "encoding": str, # bgr8, rgb8 | |||
| } | |||
| ## Example | |||
| node.send_output( | |||
| image_data, {"width": 640, "height": 480, "encoding": "bgr8"} | |||
| ) | |||
| ## Decoding | |||
| storage = event["value"] | |||
| metadata = event["metadata"] | |||
| encoding = metadata["encoding"] | |||
| width = metadata["width"] | |||
| height = metadata["height"] | |||
| if encoding == "bgr8": | |||
| channels = 3 | |||
| storage_type = np.uint8 | |||
| frame = ( | |||
| storage.to_numpy() | |||
| .astype(storage_type) | |||
| .reshape((height, width, channels)) | |||
| ) | |||
| ``` | |||
| - `bbox`: an arrow array containing the bounding boxes, confidence scores, and class names of the detected objects | |||
| ```Python | |||
| bbox: { | |||
| "bbox": np.array, # flattened array of bounding boxes | |||
| "conf": np.array, # flat array of confidence scores | |||
| "labels": np.array, # flat array of class names | |||
| } | |||
| encoded_bbox = pa.array([bbox], {"format": "xyxy"}) | |||
| decoded_bbox = { | |||
| "bbox": encoded_bbox[0]["bbox"].values.to_numpy().reshape(-1, 4), | |||
| "conf": encoded_bbox[0]["conf"].values.to_numpy(), | |||
| "labels": encoded_bbox[0]["labels"].values.to_numpy(zero_copy_only=False), | |||
| } | |||
| ``` | |||
| - `text`: Arrow array containing the text to be plotted | |||
| ```python | |||
| text: str | |||
| encoded_text = pa.array([text]) | |||
| decoded_text = encoded_text[0].as_py() | |||
| ``` | |||
| ## License | |||
| This project is licensed under Apache-2.0. Check out [NOTICE.md](../../NOTICE.md) for more information. | |||
| @@ -1,27 +0,0 @@ | |||
| [tool.poetry] | |||
| name = "terminal-print" | |||
| version = "0.3.5" | |||
| authors = [ | |||
| "Haixuan Xavier Tao <tao.xavier@outlook.com>", | |||
| "Enzo Le Van <dev@enzo-le-van.fr>", | |||
| ] | |||
| description = "Dora Terminal Print" | |||
| license = "MIT License" | |||
| homepage = "https://github.com/dora-rs/dora.git" | |||
| readme = "README.md" | |||
| packages = [{ include = "terminal_print" }] | |||
| [tool.poetry.dependencies] | |||
| dora-rs = "0.3.5" | |||
| numpy = "< 2.0.0" | |||
| pyarrow = ">= 5.0.0" | |||
| [tool.poetry.scripts] | |||
| terminal-print = "terminal_print.main:main" | |||
| [build-system] | |||
| requires = ["poetry-core>=1.8.0"] | |||
| build-backend = "poetry.core.masonry.api" | |||
| [project] | |||
| readme = "README.md" | |||
| @@ -0,0 +1,40 @@ | |||
| use dora_node_api::{self, dora_core::config::NodeId, DoraNode, Event}; | |||
| use eyre::Context; | |||
| fn main() -> eyre::Result<()> { | |||
| let mut printed = false; | |||
| loop { | |||
| if let Ok((node, mut events)) = | |||
| DoraNode::init_from_node_id(NodeId::from("terminal-print".to_string())) | |||
| { | |||
| printed = false; | |||
| println!("🔥 `terminal-print` connected to: {}", node.dataflow_id()); | |||
| while let Some(event) = events.recv() { | |||
| match event { | |||
| Event::Input { | |||
| id, | |||
| metadata: _, | |||
| data, | |||
| } => match data.data_type() { | |||
| dora_node_api::arrow::datatypes::DataType::Utf8 => { | |||
| let received_string: &str = | |||
| TryFrom::try_from(&data).context("expected string message")?; | |||
| println!("Received id: {}, data: {}", id, received_string); | |||
| } | |||
| _other => { | |||
| println!("Received id: {}, data: {:#?}", id, data); | |||
| } | |||
| }, | |||
| _other => {} | |||
| } | |||
| } | |||
| } else { | |||
| if !printed { | |||
| println!("🕐 waiting for node `terminal-print` to be available..."); | |||
| printed = true; | |||
| } | |||
| std::thread::sleep(std::time::Duration::from_secs(1)); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,11 +0,0 @@ | |||
| import os | |||
| # Define the path to the README file relative to the package directory | |||
| readme_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "README.md") | |||
| # Read the content of the README file | |||
| try: | |||
| with open(readme_path, "r", encoding="utf-8") as f: | |||
| __doc__ = f.read() | |||
| except FileNotFoundError: | |||
| __doc__ = "README file not found." | |||
| @@ -1,35 +0,0 @@ | |||
| import argparse | |||
| import os | |||
| import pyarrow as pa | |||
| from dora import Node | |||
| RUNNER_CI = True if os.getenv("CI") == "true" else False | |||
| def main(): | |||
| # Handle dynamic nodes, ask for the name of the node in the dataflow, and the same values as the ENV variables. | |||
| parser = argparse.ArgumentParser(description="Simple arrow sender") | |||
| parser.add_argument( | |||
| "--name", | |||
| type=str, | |||
| required=False, | |||
| help="The name of the node in the dataflow.", | |||
| default="terminal-print", | |||
| ) | |||
| args = parser.parse_args() | |||
| node = Node( | |||
| args.name | |||
| ) # provide the name to connect to the dataflow if dynamic node | |||
| for event in node: | |||
| if event["type"] == "INPUT": | |||
| print(event["value"].to_pylist()) | |||
| if __name__ == "__main__": | |||
| main() | |||
| @@ -1,2 +0,0 @@ | |||
| def test_placeholder(): | |||
| pass | |||