From 17afc7d7fb5ea5743c9b30ebe56022d6fe11999c Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 4 Jan 2026 18:55:42 +0800 Subject: [PATCH 1/4] advanced streams fold fix --- docs/api/sensor_streams/advanced_streams.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 02015f3329..c7db7c98bd 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -8,8 +8,8 @@ In robotics, we deal with hardware that produces data at its own pace - a camera **The problem:** A fast producer can overwhelm a slow consumer, causing memory buildup or dropped frames. We might have multiple subscribers to the same hardware that operate at different speeds. -
-diagram source + +
Pikchr ```pikchr fold output=assets/backpressure.svg color = white @@ -24,10 +24,11 @@ Slow: box "ML Model" "2 fps" rad 5px fit wid 130% ht 130% text "items pile up!" at (Queue.x, Queue.y - 0.45in) ``` +
+ ![output](assets/backpressure.svg) -
**The solution:** The `backpressure()` wrapper handles this by: @@ -74,8 +75,8 @@ slow got 7 items (skipped 13) ### How it works -
-diagram source + +
Pikchr ```pikchr fold output=assets/backpressure_solution.svg color = white @@ -93,11 +94,11 @@ arrow Slow: box "Slow Sub" rad 5px fit wid 170% ht 170% ``` +
+ ![output](assets/backpressure_solution.svg) -
- The `LATEST` strategy means: when the slow subscriber finishes processing, it gets whatever the most recent value is, skipping any values that arrived while it was busy. ### Usage in modules @@ -123,6 +124,9 @@ class MLModel(Module): + + + ## Getting Values Synchronously Sometimes you don't want a stream - you just want to call a function and get the latest value. We provide two approaches: From de183f955f437c51d80223a5d9bc7a03a439f60e Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 4 Jan 2026 19:09:32 +0800 Subject: [PATCH 2/4] data.md fixes, goal selector bugfix --- .../test_wavefront_frontier_goal_selector.py | 2 +- docs/api/assets/get_data_flow.svg | 25 +++ docs/api/data.md | 206 ++++++++++++++++++ docs/data.md | 5 - 4 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 docs/api/assets/get_data_flow.svg create mode 100644 docs/api/data.md diff --git a/dimos/navigation/frontier_exploration/test_wavefront_frontier_goal_selector.py b/dimos/navigation/frontier_exploration/test_wavefront_frontier_goal_selector.py index 7d8c0adf4c..aca154a6dd 100644 --- a/dimos/navigation/frontier_exploration/test_wavefront_frontier_goal_selector.py +++ b/dimos/navigation/frontier_exploration/test_wavefront_frontier_goal_selector.py @@ -450,7 +450,7 @@ def test_performance_timing() -> None: # Check that larger maps take more time (expected behavior) for result in results: - assert result["detect_time"] < 2.0, f"Detection too slow: {result['detect_time']}s" + assert result["detect_time"] < 3.0, f"Detection too slow: {result['detect_time']}s" assert result["goal_time"] < 1.5, f"Goal selection too slow: {result['goal_time']}s" print("\nPerformance test passed - all operations completed within time limits") diff --git a/docs/api/assets/get_data_flow.svg b/docs/api/assets/get_data_flow.svg new file mode 100644 index 0000000000..d875e1dadb --- /dev/null +++ b/docs/api/assets/get_data_flow.svg @@ -0,0 +1,25 @@ + + +get_data(name) + + + +Check +data/{name} + + + +Return path + + + +Pull LFS + + + +Decompress + + + +Return path + diff --git a/docs/api/data.md b/docs/api/data.md new file mode 100644 index 0000000000..a30a0e3328 --- /dev/null +++ b/docs/api/data.md @@ -0,0 +1,206 @@ +# Data Loading + +The [`get_data`](/dimos/utils/data.py) function provides access to test data and model files, handling Git LFS downloads automatically. + +## Basic Usage + +```python +from dimos.utils.data import get_data + +# Get path to a data file/directory +data_path = get_data("cafe.jpg") +print(f"Path: {data_path}") +print(f"Exists: {data_path.exists()}") +``` + + +``` +Path: /home/lesh/coding/dimos/data/cafe.jpg +Exists: True +``` + +## How It Works + +
Pikchr + +```pikchr fold output=assets/get_data_flow.svg +color = white +fill = none + +A: box "get_data(name)" rad 5px fit wid 170% ht 170% +arrow right 0.4in +B: box "Check" "data/{name}" rad 5px fit wid 170% ht 170% + +# Branch: exists +arrow from B.e right 0.3in then up 0.4in then right 0.3in +C: box "Return path" rad 5px fit wid 170% ht 170% + +# Branch: missing +arrow from B.e right 0.3in then down 0.4in then right 0.3in +D: box "Pull LFS" rad 5px fit wid 170% ht 170% +arrow right 0.3in +E: box "Decompress" rad 5px fit wid 170% ht 170% +arrow right 0.3in +F: box "Return path" rad 5px fit wid 170% ht 170% +``` + +
+ + +![output](assets/get_data_flow.svg) + +1. Checks if `data/{name}` already exists locally +2. If missing, pulls the `.tar.gz` archive from Git LFS +3. Decompresses the archive to `data/` +4. Returns the `Path` to the extracted file/directory + +## Common Patterns + +### Loading Images + +```python +from dimos.utils.data import get_data +from dimos.msgs.sensor_msgs import Image + +image = Image.from_file(get_data("cafe.jpg")) +print(f"Image shape: {image.data.shape}") +``` + + +``` +Image shape: (771, 1024, 3) +``` + +### Loading Model Checkpoints + +```python +from dimos.utils.data import get_data + +model_dir = get_data("models_yolo") +checkpoint = model_dir / "yolo11n.pt" +print(f"Checkpoint: {checkpoint.name} ({checkpoint.stat().st_size // 1024}KB)") +``` + + +``` +Checkpoint: yolo11n.pt (5482KB) +``` + +### Loading Recorded Data for Replay + +```python +from dimos.utils.data import get_data +from dimos.utils.testing.replay import TimedSensorReplay + +data_dir = get_data("unitree_office_walk") +replay = TimedSensorReplay(data_dir / "lidar") +print(f"Replay {replay} loaded from: {data_dir.name}") +print(replay.find_closest_seek(1)) +``` + + +``` +Replay loaded from: unitree_office_walk +{'type': 'msg', 'topic': 'rt/utlidar/voxel_map_compressed', 'data': {'stamp': 1751591000.0, 'frame_id': 'odom', 'resolution': 0.05, 'src_size': 77824, 'origin': [-3.625, -3.275, -0.575], 'width': [128, 128, 38], 'data': {'points': array([[ 2.725, -1.025, -0.575], + [ 2.525, -0.275, -0.575], + [ 2.575, -0.275, -0.575], + ..., + [ 2.675, -0.525, 0.775], + [ 2.375, 1.175, 0.775], + [ 2.325, 1.225, 0.775]], shape=(22730, 3))}}} +``` + +### Loading Point Clouds + +```python +from dimos.utils.data import get_data +from dimos.mapping.pointclouds.util import read_pointcloud + +pointcloud = read_pointcloud(get_data("apartment") / "sum.ply") +print(f"Loaded pointcloud with {len(pointcloud.points)} points") +``` + + +``` +Loaded pointcloud with 63672 points +``` + +## Data Directory Structure + +Data files live in `data/` at the repo root. Large files are stored in `data/.lfs/` as `.tar.gz` archives tracked by Git LFS. + +
Diagon + +```diagon fold mode=Tree +data/ + cafe.jpg + apartment/ + sum.ply + .lfs/ + cafe.jpg.tar.gz + apartment.tar.gz +``` + +
+ + +``` +data/ + ├──cafe.jpg + ├──apartment/ + │ └──sum.ply + └──.lfs/ + ├──cafe.jpg.tar.gz + └──apartment.tar.gz +``` + + +## Adding New Data + +### Small Files (< 1MB) + +Commit directly to `data/`: + +```sh skip +cp my_image.jpg data/ + +# 2. Compress and upload to LFS +./bin/lfs_push + +git add data/.lfs/my_image.jpg.tar.gz + +git commit -m "Add test image" +``` + +### Large Files or Directories + +Use the LFS workflow: + +```sh skip +# 1. Copy data to data/ +cp -r my_dataset/ data/ + +# 2. Compress and upload to LFS +./bin/lfs_push + +git add data/.lfs/my_dataset.tar.gz + +# 3. Commit the .tar.gz reference +git commit -m "Add my_dataset test data" +``` + +The [`lfs_push`](/bin/lfs_push) script: +1. Compresses `data/my_dataset/` → `data/.lfs/my_dataset.tar.gz` +2. Uploads to Git LFS +3. Stages the compressed file + +A pre-commit hook ([`bin/lfs_check`](/bin/lfs_check#L26)) blocks commits if you have uncompressed directories in `data/` without a corresponding `.tar.gz` in `data/.lfs/`. + +## Location Resolution + +When running from: +- **Git repo**: Uses `{repo}/data/` +- **Installed package**: Clones repo to user data dir: + - Linux: `~/.local/share/dimos/repo/data/` + - macOS: `~/Library/Application Support/dimos/repo/data/` + - Fallback: `/tmp/dimos/repo/data/` diff --git a/docs/data.md b/docs/data.md index 802e1b4ec4..17ade063f4 100644 --- a/docs/data.md +++ b/docs/data.md @@ -21,9 +21,6 @@ Exists: True ## How It Works -
-diagram source -
Pikchr ```pikchr fold output=assets/get_data_flow.svg @@ -52,8 +49,6 @@ F: box "Return path" rad 5px fit wid 170% ht 170% ![output](assets/get_data_flow.svg) -
- 1. Checks if `data/{name}` already exists locally 2. If missing, pulls the `.tar.gz` archive from Git LFS 3. Decompresses the archive to `data/` From 1dff4ba13f3e50f15383486cb086f8aba94e873d Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 4 Jan 2026 19:11:07 +0800 Subject: [PATCH 3/4] data.md location fix --- docs/api/assets/get_data_flow.svg | 25 ---- docs/api/data.md | 206 ------------------------------ docs/data.md | 69 ++++++++-- 3 files changed, 56 insertions(+), 244 deletions(-) delete mode 100644 docs/api/assets/get_data_flow.svg delete mode 100644 docs/api/data.md diff --git a/docs/api/assets/get_data_flow.svg b/docs/api/assets/get_data_flow.svg deleted file mode 100644 index d875e1dadb..0000000000 --- a/docs/api/assets/get_data_flow.svg +++ /dev/null @@ -1,25 +0,0 @@ - - -get_data(name) - - - -Check -data/{name} - - - -Return path - - - -Pull LFS - - - -Decompress - - - -Return path - diff --git a/docs/api/data.md b/docs/api/data.md deleted file mode 100644 index a30a0e3328..0000000000 --- a/docs/api/data.md +++ /dev/null @@ -1,206 +0,0 @@ -# Data Loading - -The [`get_data`](/dimos/utils/data.py) function provides access to test data and model files, handling Git LFS downloads automatically. - -## Basic Usage - -```python -from dimos.utils.data import get_data - -# Get path to a data file/directory -data_path = get_data("cafe.jpg") -print(f"Path: {data_path}") -print(f"Exists: {data_path.exists()}") -``` - - -``` -Path: /home/lesh/coding/dimos/data/cafe.jpg -Exists: True -``` - -## How It Works - -
Pikchr - -```pikchr fold output=assets/get_data_flow.svg -color = white -fill = none - -A: box "get_data(name)" rad 5px fit wid 170% ht 170% -arrow right 0.4in -B: box "Check" "data/{name}" rad 5px fit wid 170% ht 170% - -# Branch: exists -arrow from B.e right 0.3in then up 0.4in then right 0.3in -C: box "Return path" rad 5px fit wid 170% ht 170% - -# Branch: missing -arrow from B.e right 0.3in then down 0.4in then right 0.3in -D: box "Pull LFS" rad 5px fit wid 170% ht 170% -arrow right 0.3in -E: box "Decompress" rad 5px fit wid 170% ht 170% -arrow right 0.3in -F: box "Return path" rad 5px fit wid 170% ht 170% -``` - -
- - -![output](assets/get_data_flow.svg) - -1. Checks if `data/{name}` already exists locally -2. If missing, pulls the `.tar.gz` archive from Git LFS -3. Decompresses the archive to `data/` -4. Returns the `Path` to the extracted file/directory - -## Common Patterns - -### Loading Images - -```python -from dimos.utils.data import get_data -from dimos.msgs.sensor_msgs import Image - -image = Image.from_file(get_data("cafe.jpg")) -print(f"Image shape: {image.data.shape}") -``` - - -``` -Image shape: (771, 1024, 3) -``` - -### Loading Model Checkpoints - -```python -from dimos.utils.data import get_data - -model_dir = get_data("models_yolo") -checkpoint = model_dir / "yolo11n.pt" -print(f"Checkpoint: {checkpoint.name} ({checkpoint.stat().st_size // 1024}KB)") -``` - - -``` -Checkpoint: yolo11n.pt (5482KB) -``` - -### Loading Recorded Data for Replay - -```python -from dimos.utils.data import get_data -from dimos.utils.testing.replay import TimedSensorReplay - -data_dir = get_data("unitree_office_walk") -replay = TimedSensorReplay(data_dir / "lidar") -print(f"Replay {replay} loaded from: {data_dir.name}") -print(replay.find_closest_seek(1)) -``` - - -``` -Replay loaded from: unitree_office_walk -{'type': 'msg', 'topic': 'rt/utlidar/voxel_map_compressed', 'data': {'stamp': 1751591000.0, 'frame_id': 'odom', 'resolution': 0.05, 'src_size': 77824, 'origin': [-3.625, -3.275, -0.575], 'width': [128, 128, 38], 'data': {'points': array([[ 2.725, -1.025, -0.575], - [ 2.525, -0.275, -0.575], - [ 2.575, -0.275, -0.575], - ..., - [ 2.675, -0.525, 0.775], - [ 2.375, 1.175, 0.775], - [ 2.325, 1.225, 0.775]], shape=(22730, 3))}}} -``` - -### Loading Point Clouds - -```python -from dimos.utils.data import get_data -from dimos.mapping.pointclouds.util import read_pointcloud - -pointcloud = read_pointcloud(get_data("apartment") / "sum.ply") -print(f"Loaded pointcloud with {len(pointcloud.points)} points") -``` - - -``` -Loaded pointcloud with 63672 points -``` - -## Data Directory Structure - -Data files live in `data/` at the repo root. Large files are stored in `data/.lfs/` as `.tar.gz` archives tracked by Git LFS. - -
Diagon - -```diagon fold mode=Tree -data/ - cafe.jpg - apartment/ - sum.ply - .lfs/ - cafe.jpg.tar.gz - apartment.tar.gz -``` - -
- - -``` -data/ - ├──cafe.jpg - ├──apartment/ - │ └──sum.ply - └──.lfs/ - ├──cafe.jpg.tar.gz - └──apartment.tar.gz -``` - - -## Adding New Data - -### Small Files (< 1MB) - -Commit directly to `data/`: - -```sh skip -cp my_image.jpg data/ - -# 2. Compress and upload to LFS -./bin/lfs_push - -git add data/.lfs/my_image.jpg.tar.gz - -git commit -m "Add test image" -``` - -### Large Files or Directories - -Use the LFS workflow: - -```sh skip -# 1. Copy data to data/ -cp -r my_dataset/ data/ - -# 2. Compress and upload to LFS -./bin/lfs_push - -git add data/.lfs/my_dataset.tar.gz - -# 3. Commit the .tar.gz reference -git commit -m "Add my_dataset test data" -``` - -The [`lfs_push`](/bin/lfs_push) script: -1. Compresses `data/my_dataset/` → `data/.lfs/my_dataset.tar.gz` -2. Uploads to Git LFS -3. Stages the compressed file - -A pre-commit hook ([`bin/lfs_check`](/bin/lfs_check#L26)) blocks commits if you have uncompressed directories in `data/` without a corresponding `.tar.gz` in `data/.lfs/`. - -## Location Resolution - -When running from: -- **Git repo**: Uses `{repo}/data/` -- **Installed package**: Clones repo to user data dir: - - Linux: `~/.local/share/dimos/repo/data/` - - macOS: `~/Library/Application Support/dimos/repo/data/` - - Fallback: `/tmp/dimos/repo/data/` diff --git a/docs/data.md b/docs/data.md index 17ade063f4..a30a0e3328 100644 --- a/docs/data.md +++ b/docs/data.md @@ -73,45 +73,88 @@ Image shape: (771, 1024, 3) ### Loading Model Checkpoints -```python skip +```python from dimos.utils.data import get_data -model_dir = get_data("models_mobileclip") -checkpoint = model_dir / "mobileclip2_s0.pt" +model_dir = get_data("models_yolo") +checkpoint = model_dir / "yolo11n.pt" +print(f"Checkpoint: {checkpoint.name} ({checkpoint.stat().st_size // 1024}KB)") +``` + + +``` +Checkpoint: yolo11n.pt (5482KB) ``` ### Loading Recorded Data for Replay -```python skip +```python from dimos.utils.data import get_data -from dimos.utils.testing.replay import Replay +from dimos.utils.testing.replay import TimedSensorReplay data_dir = get_data("unitree_office_walk") -replay = Replay(data_dir) +replay = TimedSensorReplay(data_dir / "lidar") +print(f"Replay {replay} loaded from: {data_dir.name}") +print(replay.find_closest_seek(1)) +``` + + +``` +Replay loaded from: unitree_office_walk +{'type': 'msg', 'topic': 'rt/utlidar/voxel_map_compressed', 'data': {'stamp': 1751591000.0, 'frame_id': 'odom', 'resolution': 0.05, 'src_size': 77824, 'origin': [-3.625, -3.275, -0.575], 'width': [128, 128, 38], 'data': {'points': array([[ 2.725, -1.025, -0.575], + [ 2.525, -0.275, -0.575], + [ 2.575, -0.275, -0.575], + ..., + [ 2.675, -0.525, 0.775], + [ 2.375, 1.175, 0.775], + [ 2.325, 1.225, 0.775]], shape=(22730, 3))}}} ``` ### Loading Point Clouds -```python skip +```python from dimos.utils.data import get_data -from dimos.mapping.pointclouds import read_pointcloud +from dimos.mapping.pointclouds.util import read_pointcloud pointcloud = read_pointcloud(get_data("apartment") / "sum.ply") +print(f"Loaded pointcloud with {len(pointcloud.points)} points") +``` + + +``` +Loaded pointcloud with 63672 points ``` ## Data Directory Structure Data files live in `data/` at the repo root. Large files are stored in `data/.lfs/` as `.tar.gz` archives tracked by Git LFS. +
Diagon + +```diagon fold mode=Tree +data/ + cafe.jpg + apartment/ + sum.ply + .lfs/ + cafe.jpg.tar.gz + apartment.tar.gz +``` + +
+ + ``` data/ -├── cafe.jpg # Small files: committed directly -├── apartment/ # Directories: extracted from LFS -│ └── sum.ply -└── .lfs/ - └── apartment.tar.gz # LFS-tracked archive + ├──cafe.jpg + ├──apartment/ + │ └──sum.ply + └──.lfs/ + ├──cafe.jpg.tar.gz + └──apartment.tar.gz ``` + ## Adding New Data ### Small Files (< 1MB) From 20f7340b483c4dfb3295f2abe4d94ea568e6eddc Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 4 Jan 2026 21:53:53 +0800 Subject: [PATCH 4/4] rxpy details fixed --- docs/api/sensor_streams/reactivex.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/sensor_streams/reactivex.md b/docs/api/sensor_streams/reactivex.md index e8f39afee5..1dcbdfe046 100644 --- a/docs/api/sensor_streams/reactivex.md +++ b/docs/api/sensor_streams/reactivex.md @@ -226,10 +226,11 @@ arrow right 0.3in Handler: box "callback" rad 5px fit wid 170% ht 170% ``` +
+ ![output](assets/observable_flow.svg) - **Key property: Observables are lazy.** Nothing happens until you call `.subscribe()`. This means you can build up complex pipelines without any work being done, then start the flow when ready.