Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ yolo11n.pt
/.mypy_cache*

*mobileclip*
/results
6 changes: 6 additions & 0 deletions bin/pytest-fast
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail

. .venv/bin/activate
exec pytest "$@" dimos
6 changes: 6 additions & 0 deletions bin/pytest-mujoco
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail

. .venv/bin/activate
exec pytest "$@" -m mujoco dimos
2 changes: 1 addition & 1 deletion bin/pytest-slow
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
set -euo pipefail

. .venv/bin/activate
exec pytest "$@" -m 'not (tool or cuda or gpu or module or temporal)' dimos
exec pytest "$@" -m 'not (tool or module or neverending or mujoco)' dimos
2 changes: 1 addition & 1 deletion dimos/agents/vlm_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from typing import Any

from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.messages import AIMessage, HumanMessage

from dimos.agents.llm_init import build_llm, build_system_message
from dimos.agents.spec import AgentSpec, AnyMessage
Expand Down
1 change: 0 additions & 1 deletion dimos/control/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from __future__ import annotations

from dimos.control.orchestrator import (
ControlOrchestrator,
HardwareConfig,
TaskConfig,
control_orchestrator,
Expand Down
2 changes: 1 addition & 1 deletion dimos/control/hardware_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import logging
import time
from typing import TYPE_CHECKING, Protocol, runtime_checkable
from typing import Protocol, runtime_checkable

from dimos.hardware.manipulators.spec import ControlMode, ManipulatorBackend

Expand Down
1 change: 0 additions & 1 deletion dimos/control/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field
import threading
import time
Expand Down
6 changes: 5 additions & 1 deletion dimos/e2e_tests/dimos_cli_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ def start(self) -> None:
if self.demo_args is None:
raise ValueError("Demo args must be set before starting the process.")

self.process = subprocess.Popen(["dimos", "--simulation", *self.demo_args])
args = list(self.demo_args)
if len(args) == 1:
args = ["run", *args]

self.process = subprocess.Popen(["dimos", "--simulation", *args])

def stop(self) -> None:
if self.process is None:
Expand Down
8 changes: 4 additions & 4 deletions dimos/e2e_tests/test_control_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def test_orchestrator_executes_trajectory(self, lcm_spy, start_blueprint) -> Non

while time.time() - start_time < timeout:
status = client.get_trajectory_status("traj_arm")
if status is not None and status.get("state") == TrajectoryState.COMPLETED.name:
if status is not None and status.state == TrajectoryState.COMPLETED.name:
completed = True
break
time.sleep(0.1)
Expand Down Expand Up @@ -205,7 +205,7 @@ def test_orchestrator_cancel_trajectory(self, lcm_spy, start_blueprint) -> None:
# Check status is ABORTED
status = client.get_trajectory_status("traj_arm")
assert status is not None
assert status.get("state") == TrajectoryState.ABORTED.name
assert status.state == TrajectoryState.ABORTED.name
finally:
client.stop_rpc_client()

Expand Down Expand Up @@ -258,7 +258,7 @@ def test_dual_arm_orchestrator(self, lcm_spy, start_blueprint) -> None:
left_status = client.get_trajectory_status("traj_left")
right_status = client.get_trajectory_status("traj_right")

assert left_status.get("state") == TrajectoryState.COMPLETED.name
assert right_status.get("state") == TrajectoryState.COMPLETED.name
assert left_status is not None and left_status.state == TrajectoryState.COMPLETED.name
assert right_status is not None and right_status.state == TrajectoryState.COMPLETED.name
finally:
client.stop_rpc_client()
2 changes: 1 addition & 1 deletion dimos/e2e_tests/test_person_follow.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def run_person_track() -> None:

@pytest.mark.skipif(bool(os.getenv("CI")), reason="LCM spy doesn't work in CI.")
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set.")
@pytest.mark.e2e
@pytest.mark.mujoco
def test_person_follow(
lcm_spy: LcmSpy,
start_blueprint: Callable[[str], DimosCliCall],
Expand Down
2 changes: 1 addition & 1 deletion dimos/e2e_tests/test_spatial_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

@pytest.mark.skipif(bool(os.getenv("CI")), reason="LCM spy doesn't work in CI.")
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set.")
@pytest.mark.e2e
@pytest.mark.mujoco
def test_spatial_memory_navigation(
lcm_spy: LcmSpy,
start_blueprint: Callable[[str], DimosCliCall],
Expand Down
11 changes: 4 additions & 7 deletions dimos/models/manipulation/contact_graspnet_pytorch/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import os

from contact_graspnet_pytorch import config_utils # type: ignore[import-not-found]
from contact_graspnet_pytorch.checkpoints import CheckpointIO # type: ignore[import-not-found]
from contact_graspnet_pytorch.contact_grasp_estimator import ( # type: ignore[import-not-found]
GraspEstimator,
)
from contact_graspnet_pytorch.data import ( # type: ignore[import-not-found]
load_available_input_data,
)
import numpy as np
import torch

from dimos.utils.data import get_data

Expand Down Expand Up @@ -45,12 +45,9 @@ def inference(global_config, # type: ignore[no-untyped-def]

# Load the weights
model_checkpoint_dir = get_data(ckpt_dir)
checkpoint_io = CheckpointIO(checkpoint_dir=model_checkpoint_dir, model=grasp_estimator.model)
try:
checkpoint_io.load('model.pt')
except FileExistsError:
print('No model checkpoint found')

checkpoint_path = os.path.join(model_checkpoint_dir, 'model.pt')
state_dict = torch.load(checkpoint_path, weights_only=False)
grasp_estimator.model.load_state_dict(state_dict['model'])

os.makedirs('results', exist_ok=True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def is_manipulation_installed() -> bool:
except ImportError:
return False

@pytest.mark.integration
@pytest.mark.skipif(not is_manipulation_installed(),
reason="This test requires 'pip install .[manipulation]' to be run")
def test_contact_graspnet_inference() -> None:
Expand Down
8 changes: 8 additions & 0 deletions dimos/models/vl/test_vlm.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import time
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -33,6 +34,9 @@
)
@pytest.mark.gpu
def test_vlm_bbox_detections(model_class: "type[VlModel]", model_name: str) -> None:
if model_class is MoondreamHostedVlModel and 'MOONDREAM_API_KEY' not in os.environ:
pytest.skip("Need MOONDREAM_API_KEY to run")

image = Image.from_file(get_data("cafe.jpg")).to_rgb()

print(f"Testing {model_name}")
Expand Down Expand Up @@ -103,6 +107,10 @@ def test_vlm_bbox_detections(model_class: "type[VlModel]", model_name: str) -> N
@pytest.mark.gpu
def test_vlm_point_detections(model_class: "type[VlModel]", model_name: str) -> None:
"""Test VLM point detection capabilities."""

if model_class is MoondreamHostedVlModel and 'MOONDREAM_API_KEY' not in os.environ:
pytest.skip("Need MOONDREAM_API_KEY to run")

image = Image.from_file(get_data("cafe.jpg")).to_rgb()

print(f"Testing {model_name} point detection")
Expand Down
25 changes: 25 additions & 0 deletions dimos/msgs/sensor_msgs/image_impls/AbstractImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@
cp = None
HAS_CUDA = False

# NVRTC defaults to C++11; libcu++ in recent CUDA requires at least C++17.
if HAS_CUDA:
try:
import cupy.cuda.compiler as _cupy_compiler # type: ignore[import-not-found]

if not getattr(_cupy_compiler, "_dimos_force_cxx17", False):
_orig_compile_using_nvrtc = _cupy_compiler.compile_using_nvrtc

def _compile_using_nvrtc( # type: ignore[no-untyped-def]
source, options=(), *args, **kwargs
):
filtered = tuple(
opt
for opt in options
if opt not in ("-std=c++11", "--std=c++11", "-std=c++14", "--std=c++14")
)
if "--std=c++17" not in filtered and "-std=c++17" not in filtered:
filtered = (*filtered, "--std=c++17")
return _orig_compile_using_nvrtc(source, filtered, *args, **kwargs)

_cupy_compiler.compile_using_nvrtc = _compile_using_nvrtc
_cupy_compiler._dimos_force_cxx17 = True
except Exception:
pass

# Optional nvImageCodec (preferred GPU codec)
USE_NVIMGCODEC = os.environ.get("USE_NVIMGCODEC", "0") == "1"
NVIMGCODEC_LAST_USED = False
Expand Down
15 changes: 11 additions & 4 deletions dimos/msgs/sensor_msgs/image_impls/CudaImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,20 @@
} // extern "C"
"""

_pnp_kernel = None
if cp is not None:
_mod = cp.RawModule(code=_CUDA_SRC, options=("-std=c++14",), name_expressions=("pnp_gn_batch",))
_pnp_kernel = _mod.get_function("pnp_gn_batch")
try:
_mod = cp.RawModule(
code=_CUDA_SRC, options=("-std=c++17",), name_expressions=("pnp_gn_batch",)
)
_pnp_kernel = _mod.get_function("pnp_gn_batch")
except Exception:
# CUDA not available at runtime (e.g., no GPU or driver issues)
pass


def _solve_pnp_cuda_kernel(obj, img, K, iterations: int = 15, damping: float = 1e-6): # type: ignore[no-untyped-def]
if cp is None:
if cp is None or _pnp_kernel is None:
raise RuntimeError("CuPy/CUDA not available")

obj_cu = cp.asarray(obj, dtype=cp.float32)
Expand Down Expand Up @@ -709,7 +716,7 @@ def sharpness(self) -> float:
magnitude = cp.hypot(gx, gy)
mean_mag = float(cp.asnumpy(magnitude.mean()))
except Exception:
return 0.0
raise
if mean_mag <= 0:
return 0.0
return float(np.clip((np.log10(mean_mag + 1) - 1.7) / 2.0, 0.0, 1.0))
Expand Down
8 changes: 1 addition & 7 deletions dimos/msgs/sensor_msgs/image_impls/test_image_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,19 +221,13 @@ def test_perf_alloc(alloc_timer) -> None:
def test_sharpness(alloc_timer) -> None:
"""Test sharpness computation with NumpyImage always, add CudaImage parity when available."""
arr = _prepare_image(ImageFormat.BGR, (64, 64, 3))
cpu, gpu, _, _ = alloc_timer(arr, ImageFormat.BGR)
cpu = alloc_timer(arr, ImageFormat.BGR)[0]

# Always test CPU backend
s_cpu = cpu.sharpness
assert s_cpu >= 0 # Sharpness should be non-negative
assert s_cpu < 1000 # Reasonable upper bound

# Optionally test GPU parity when CUDA is available
if gpu is not None:
s_gpu = gpu.sharpness
# Values should be very close; minor border/rounding differences allowed
assert abs(s_cpu - s_gpu) < 0.6


def test_to_opencv(alloc_timer) -> None:
"""Test to_opencv conversion with NumpyImage always, add CudaImage parity when available."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
import time

import numpy as np
from PIL import ImageDraw
import pytest

from dimos.msgs.geometry_msgs import Vector3
from dimos.msgs.nav_msgs import CostValues, OccupancyGrid
from dimos.navigation.frontier_exploration.utils import costmap_to_pil_image
from dimos.navigation.frontier_exploration.wavefront_frontier_goal_selector import (
WavefrontFrontierExplorer,
)
Expand Down Expand Up @@ -312,92 +310,6 @@ def test_exploration_with_no_gain_detection() -> None:
explorer.stop()


@pytest.mark.vis
def test_frontier_detection_visualization() -> None:
"""Test frontier detection with visualization (marked with @pytest.mark.vis)."""
# Get test costmap
costmap, first_lidar = create_test_costmap()

# Initialize frontier explorer with default parameters
explorer = WavefrontFrontierExplorer()

try:
# Use lidar origin as robot position
robot_pose = first_lidar.origin

# Detect all frontiers for visualization
all_frontiers = explorer.detect_frontiers(robot_pose, costmap)

# Get selected goal
selected_goal = explorer.get_exploration_goal(robot_pose, costmap)

print(f"Visualizing {len(all_frontiers)} frontier candidates")
if selected_goal:
print(f"Selected goal: ({selected_goal.x:.2f}, {selected_goal.y:.2f})")

# Create visualization
image_scale_factor = 4
base_image = costmap_to_pil_image(costmap, image_scale_factor)

# Helper function to convert world coordinates to image coordinates
def world_to_image_coords(world_pos: Vector3) -> tuple[int, int]:
grid_pos = costmap.world_to_grid(world_pos)
img_x = int(grid_pos.x * image_scale_factor)
img_y = int((costmap.height - grid_pos.y) * image_scale_factor) # Flip Y
return img_x, img_y

# Draw visualization
draw = ImageDraw.Draw(base_image)

# Draw frontier candidates as gray dots
for frontier in all_frontiers[:20]: # Limit to top 20
x, y = world_to_image_coords(frontier)
radius = 6
draw.ellipse(
[x - radius, y - radius, x + radius, y + radius],
fill=(128, 128, 128), # Gray
outline=(64, 64, 64),
width=1,
)

# Draw robot position as blue dot
robot_x, robot_y = world_to_image_coords(robot_pose)
robot_radius = 10
draw.ellipse(
[
robot_x - robot_radius,
robot_y - robot_radius,
robot_x + robot_radius,
robot_y + robot_radius,
],
fill=(0, 0, 255), # Blue
outline=(0, 0, 128),
width=3,
)

# Draw selected goal as red dot
if selected_goal:
goal_x, goal_y = world_to_image_coords(selected_goal)
goal_radius = 12
draw.ellipse(
[
goal_x - goal_radius,
goal_y - goal_radius,
goal_x + goal_radius,
goal_y + goal_radius,
],
fill=(255, 0, 0), # Red
outline=(128, 0, 0),
width=3,
)

# Display the image
base_image.show(title="Frontier Detection - Office Lidar")
print("Visualization displayed. Close the image window to continue.")
finally:
explorer.stop()


def test_performance_timing() -> None:
"""Test performance by timing frontier detection operations."""
import time
Expand Down
Loading
Loading