Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2aa8644
make dimos pip-installable
paul-nechifor Nov 12, 2025
10aab26
Merge branch 'dev' into pip-install
paul-nechifor Nov 22, 2025
36e8adc
log which dir is used
paul-nechifor Nov 22, 2025
29f8aa3
check for git too
paul-nechifor Nov 22, 2025
0a3eb65
use GIT_LFS_SKIP_SMUDGE=1
paul-nechifor Nov 22, 2025
39a1fd2
Merge branch 'dev' into pip-install
paul-nechifor Nov 22, 2025
1d16306
Merge branch 'dev' into pip-install
paul-nechifor Nov 27, 2025
d1fc28b
Merge branch 'dev' into pip-install
paul-nechifor Dec 1, 2025
778fcc3
Merge branch 'dev' into pip-install
paul-nechifor Dec 23, 2025
38ff65b
fix logger
paul-nechifor Dec 23, 2025
ff0d760
CI code cleanup
paul-nechifor Dec 23, 2025
a415c03
make it work on python 3.12
paul-nechifor Dec 24, 2025
4117321
use dev for now
paul-nechifor Dec 24, 2025
e5de611
increase steps per frame
paul-nechifor Dec 24, 2025
101dd2e
inline backpressure
paul-nechifor Dec 24, 2025
458120b
fix imports
paul-nechifor Dec 24, 2025
9b484e5
remove go2-webrtc-connect clone
paul-nechifor Dec 24, 2025
4db2fd3
fix rxpy backpressure
paul-nechifor Dec 24, 2025
1d06d45
ignore rxpy_backpressure
paul-nechifor Dec 25, 2025
0ecec75
skip rxpy_backpressure errors
paul-nechifor Dec 25, 2025
06d4ac7
Merge branch 'dev' into pip-install
paul-nechifor Dec 25, 2025
766a746
add new readme
paul-nechifor Dec 25, 2025
54f1005
fix linting
paul-nechifor Dec 25, 2025
628f1d5
update readme
paul-nechifor Dec 25, 2025
74f6d53
Merge branch 'dev' into pip-install
paul-nechifor Dec 26, 2025
d65b7a8
Merge branch 'dev' into pip-install
paul-nechifor Dec 28, 2025
627d320
Merge branch 'dev' into pip-install
paul-nechifor Dec 30, 2025
d94d65e
use unitree-webrtc-connect-leshy
paul-nechifor Dec 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: remove-crlf
- id: insert-license
files: \.py$
exclude: __init__\.py$
exclude: (__init__\.py$)|(dimos/rxpy_backpressure/)
args:
# use if you want to remove licences from all files
# (for globally changing wording or something)
Expand Down
20 changes: 20 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
global-exclude *.pyc
global-exclude __pycache__
global-exclude .DS_Store

# Exclude test directories
prune tests

# Exclude web development directories
recursive-exclude dimos/web/command-center-extension *
recursive-exclude dimos/web/websocket_vis/node_modules *

# Exclude development files
exclude .gitignore
exclude .gitattributes
prune .git
prune .github
prune .mypy_cache
prune .pytest_cache
prune .ruff_cache
prune .vscode
136 changes: 136 additions & 0 deletions README_installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# DimOS

## Installation

Clone the repo:

```bash
git clone -b main --single-branch git@github.com:dimensionalOS/dimos.git
cd dimos
```

### System dependencies

Tested on Ubuntu 22.04/24.04.

```bash
sudo apt update
sudo apt install git-lfs python3-venv python3-pyaudio portaudio19-dev libturbojpeg0-dev
```

### Python dependencies

Install `uv` by [following their instructions](https://docs.astral.sh/uv/getting-started/installation/) or just run:

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Install Python dependencies:

```bash
uv sync
```

Depending on what you want to test you might want to install more optional dependencies as well (recommended):

```bash
uv sync --extra dev --extra cpu --extra sim --extra drone
```

### Install Foxglove Studio (robot visualization and control)

> **Note:** This will be obsolete once we finish our migration to open source [Rerun](https://rerun.io/).

Download and install [Foxglove Studio](https://foxglove.dev/download):

```bash
wget https://get.foxglove.dev/desktop/latest/foxglove-studio-latest-linux-amd64.deb
sudo apt install ./foxglove-studio-*.deb
```

[Register an account](https://app.foxglove.dev/signup) to use it.

Open Foxglove Studio:

```bash
foxglove-studio
```

To connect and load our dashboard:

1. Click on "Open connection"
2. In the popup window, leave the WebSocket URL as `ws://localhost:8765` and click "Open"
3. In the top right, click on the "Default" dropdown, then "Import from file..."
4. Navigate to the `dimos` repo and select `assets/foxglove_dashboards/unitree.json`

### Test the install

Run the Python tests:

```bash
uv run pytest dimos
```

They should all pass in about 3 minutes.

### Test a robot replay

Run the system by playing back recorded data from a robot (the replay data is automatically downloaded via Git LFS):

```bash
uv run dimos --replay run unitree-go2-basic
```

You can visualize the robot data in Foxglove Studio.

### Run a simulation

```bash
uv run dimos --simulation run unitree-go2-basic
```

This will open a MuJoCo simulation window. You can also visualize data in Foxglove.

If you want to also teleoperate the simulated robot run:

```bash
uv run dimos --simulation run unitree-go2-basic --extra-module keyboard_teleop
```

This will also open a Keyboard Teleop window. Focus on the window and use WASD to control the robot.

### Command center

You can also control the robot from the `command-center` extension to Foxglove.

First, pull the LFS file:

```bash
git lfs pull --include="assets/dimensional.command-center-extension-0.0.1.foxe"
```

To install it, drag that file over the Foxglove Studio window. The extension will be installed automatically. Then, click on the "Add panel" icon on the top right and add "command-center".

You can now click on the map to give it a travel goal, or click on "Start Keyboard Control" to teleoperate it.

### Using `dimos` in your code

If you want to use dimos in your own project (not the cloned repo), you can install it as a dependency:

```bash
uv add dimos
```

Note, a few dependencies do not have PyPI packages and need to be installed from their Git repositories. These are only required for specific features:

- **CLIP** and **detectron2**: Required for the Detic open-vocabulary object detector
- **contact_graspnet_pytorch**: Required for robotic grasp prediction

You can install them with:

```bash
uv add git+https://github.com/openai/CLIP.git
uv add git+https://github.com/dimensionalOS/contact_graspnet_pytorch.git
uv add git+https://github.com/facebookresearch/detectron2.git
```
25 changes: 12 additions & 13 deletions dimos/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,21 @@ def deploy( # type: ignore[no-untyped-def]
*args,
**kwargs,
):
console = Console()
with console.status(f"deploying [green]{actor_class.__name__}\n", spinner="arc"):
actor = dask_client.submit( # type: ignore[no-untyped-call]
actor_class,
*args,
**kwargs,
actor=True,
).result()
logger.info("Deploying module.", module=actor_class.__name__)
actor = dask_client.submit( # type: ignore[no-untyped-call]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the spinner because it messes with the download progress bar (for Git LFS, mujoco, etc) which also redraws the line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks I worked hard on that

actor_class,
*args,
**kwargs,
actor=True,
).result()

worker = actor.set_ref(actor).result()
logger.info("Deployed module.", module=actor._cls.__name__, worker_id=worker)
worker = actor.set_ref(actor).result()
logger.info("Deployed module.", module=actor._cls.__name__, worker_id=worker)

# Register actor deployment in shared memory
ActorRegistry.update(str(actor), str(worker))
# Register actor deployment in shared memory
ActorRegistry.update(str(actor), str(worker))

return RPCClient(actor, actor_class)
return RPCClient(actor, actor_class)

def check_worker_memory() -> None:
"""Check memory usage of all workers."""
Expand Down
2 changes: 2 additions & 0 deletions dimos/protocol/rpc/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def call(self, name: str, arguments: Args, cb: Callable | None) -> Callable[[],
def call_sync(
self, name: str, arguments: Args, rpc_timeout: float | None = 120.0
) -> tuple[Any, Callable[[], None]]:
if name == "start":
rpc_timeout = 1200.0 # starting modules can take longer
event = threading.Event()

def receive_value(val) -> None: # type: ignore[no-untyped-def]
Expand Down
21 changes: 21 additions & 0 deletions dimos/rxpy_backpressure/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Mark Haynes

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3 changes: 3 additions & 0 deletions dimos/rxpy_backpressure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from dimos.rxpy_backpressure.backpressure import BackPressure

__all__ = [BackPressure]
29 changes: 29 additions & 0 deletions dimos/rxpy_backpressure/backpressure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (c) rxpy_backpressure
from dimos.rxpy_backpressure.drop import (
wrap_observer_with_buffer_strategy,
wrap_observer_with_drop_strategy,
)
from dimos.rxpy_backpressure.latest import wrap_observer_with_latest_strategy


class BackPressure:
"""
Latest strategy will remember the next most recent message to process and will call the observer with it when
the observer has finished processing its current message.
"""

LATEST = wrap_observer_with_latest_strategy

"""
Drop strategy accepts a cache size, the strategy will remember the most recent messages and remove older
messages from the cache. The strategy guarantees that the oldest messages in the cache are passed to the
observer first.
:param cache_size: int = 10 is default
"""
DROP = wrap_observer_with_drop_strategy

"""
Buffer strategy has a unbounded cache and will pass all messages to its consumer in the order it received them
beware of Memory leaks due to a build up of messages.
"""
BUFFER = wrap_observer_with_buffer_strategy
67 changes: 67 additions & 0 deletions dimos/rxpy_backpressure/drop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright (c) rxpy_backpressure
from typing import Any

from dimos.rxpy_backpressure.function_runner import thread_function_runner
from dimos.rxpy_backpressure.locks import BooleanLock, Lock
from dimos.rxpy_backpressure.observer import Observer


class DropBackPressureStrategy(Observer):
def __init__(self, wrapped_observer: Observer, cache_size: int):
self.wrapped_observer: Observer = wrapped_observer
self.__function_runner = thread_function_runner
self.__lock: Lock = BooleanLock()
self.__cache_size: int | None = cache_size
self.__message_cache: list = []
self.__error_cache: list = []

def on_next(self, message):
if self.__lock.is_locked():
self.__update_cache(self.__message_cache, message)
else:
self.__lock.lock()
self.__function_runner(self, self.__on_next, message)

@staticmethod
def __on_next(self, message: any):
self.wrapped_observer.on_next(message)
if len(self.__message_cache) > 0:
self.__function_runner(self, self.__on_next, self.__message_cache.pop(0))
else:
self.__lock.unlock()

def on_error(self, error: any):
if self.__lock.is_locked():
self.__update_cache(self.__error_cache, error)
else:
self.__lock.lock()
self.__function_runner(self, self.__on_error, error)

@staticmethod
def __on_error(self, error: any):
self.wrapped_observer.on_error(error)
if len(self.__error_cache) > 0:
self.__function_runner(self, self.__on_error, self.__error_cache.pop(0))
else:
self.__lock.unlock()

def __update_cache(self, cache: list, item: Any):
if self.__cache_size is None or len(cache) < self.__cache_size:
cache.append(item)
else:
cache.pop(0)
cache.append(item)

def on_completed(self):
self.wrapped_observer.on_completed()

def is_locked(self):
return self.__lock.is_locked()


def wrap_observer_with_drop_strategy(observer: Observer, cache_size: int = 10) -> Observer:
return DropBackPressureStrategy(observer, cache_size=cache_size)


def wrap_observer_with_buffer_strategy(observer: Observer) -> Observer:
return DropBackPressureStrategy(observer, cache_size=None)
6 changes: 6 additions & 0 deletions dimos/rxpy_backpressure/function_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) rxpy_backpressure
from threading import Thread


def thread_function_runner(self, func, message):
Thread(target=func, args=(self, message)).start()
57 changes: 57 additions & 0 deletions dimos/rxpy_backpressure/latest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright (c) rxpy_backpressure
from typing import Optional

from dimos.rxpy_backpressure.function_runner import thread_function_runner
from dimos.rxpy_backpressure.locks import BooleanLock, Lock
from dimos.rxpy_backpressure.observer import Observer


class LatestBackPressureStrategy(Observer):
def __init__(self, wrapped_observer: Observer):
self.wrapped_observer: Observer = wrapped_observer
self.__function_runner = thread_function_runner
self.__lock: Lock = BooleanLock()
self.__message_cache: Optional = None
self.__error_cache: Optional = None

def on_next(self, message):
if self.__lock.is_locked():
self.__message_cache = message
else:
self.__lock.lock()
self.__function_runner(self, self.__on_next, message)

@staticmethod
def __on_next(self, message: any):
self.wrapped_observer.on_next(message)
if self.__message_cache is not None:
self.__function_runner(self, self.__on_next, self.__message_cache)
self.__message_cache = None
else:
self.__lock.unlock()

def on_error(self, error: any):
if self.__lock.is_locked():
self.__error_cache = error
else:
self.__lock.lock()
self.__function_runner(self, self.__on_error, error)

@staticmethod
def __on_error(self, error: any):
self.wrapped_observer.on_error(error)
if self.__error_cache:
self.__function_runner(self, self.__on_error, self.__error_cache)
self.__error_cache = None
else:
self.__lock.unlock()

def on_completed(self):
self.wrapped_observer.on_completed()

def is_locked(self):
return self.__lock.is_locked()


def wrap_observer_with_latest_strategy(observer: Observer) -> Observer:
return LatestBackPressureStrategy(observer)
Loading