Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .github/workflows/message-codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ on:
- dimos/navigation/spec.py
- dimos/navigation/local_planner/**
- dimos/navigation/trajectory_follower/**
- dimos/navigation/global_planner/viz.py
- dimos/navigation/global_planner/test_viz.py
- dimos/navigation/test_tf_pose.py
- dimos/navigation/movement_manager/**
- dimos/navigation/go2/replanning_a_star/**
Expand Down Expand Up @@ -329,3 +331,8 @@ jobs:
export DIMOS_NATIVE_FOLLOWER_BINARY="$PWD/dimos/navigation/trajectory_follower/fancy/rust/target/debug/trajectory_follower"
.native-review/bin/python -I -c 'from dimos.navigation.trajectory_follower.fancy import native; import pytest; import sys; assert "site-packages" in str(native.__file__); raise SystemExit(pytest.main(sys.argv[1:]))' --noconftest --import-mode=importlib -o addopts='' -q "$PWD/dimos/navigation/trajectory_follower/fancy/test_generated_native.py"
MYPYPATH="$PWD/build/message-source/python" .native-review/bin/python -m mypy --follow-imports=silent dimos/navigation/trajectory_follower/fancy/native.py
- name: Verify generated navigation path and goal rendering
if: runner.os == 'Linux'
run: |
.native-review/bin/python -I -c 'from dimos.navigation.local_planner import viz as local; from dimos.navigation.global_planner import viz as global_; import pytest; import sys; assert all("site-packages" in str(value.__file__) for value in (local,global_)); raise SystemExit(pytest.main(sys.argv[1:]))' --noconftest --import-mode=importlib -o addopts='' -q "$PWD/dimos/navigation/local_planner/test_viz.py" "$PWD/dimos/navigation/global_planner/test_viz.py"
MYPYPATH="$PWD/build/message-source/python" .native-review/bin/python -m mypy --follow-imports=silent dimos/navigation/local_planner/viz.py dimos/navigation/global_planner/viz.py
2 changes: 1 addition & 1 deletion dimos/models/embedding/clip.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
from functools import cached_property
from typing import overload

from dimos.messages.sensor_msgs.msg import Image
from PIL import Image as PILImage
import torch
import torch.nn.functional as functional
from transformers import CLIPModel as HFCLIPModel, CLIPProcessor

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import HuggingFaceModel
from dimos.models.embedding.base import Embedding, EmbeddingModel, HuggingFaceEmbeddingModelConfig
from dimos.msgs.image import image_to_rgb
Expand Down
2 changes: 1 addition & 1 deletion dimos/models/embedding/dino.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
from functools import cached_property
from typing import Any, overload

from dimos.messages.sensor_msgs.msg import Image
from PIL import Image as PILImage
import torch
import torch.nn.functional as functional
from transformers import AutoImageProcessor, AutoModel

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import HuggingFaceModel
from dimos.models.embedding.base import Embedding, EmbeddingModel, HuggingFaceEmbeddingModelConfig
from dimos.msgs.image import image_to_rgb
Expand Down
2 changes: 1 addition & 1 deletion dimos/models/embedding/siglip.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
from functools import cached_property
from typing import overload

from dimos.messages.sensor_msgs.msg import Image
from PIL import Image as PILImage
import torch
import torch.nn.functional as functional
from transformers import SiglipModel as HFSiglipModel, SiglipProcessor

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import HuggingFaceModel
from dimos.models.embedding.base import Embedding, EmbeddingModel, HuggingFaceEmbeddingModelConfig
from dimos.msgs.image import image_to_rgb
Expand Down
2 changes: 1 addition & 1 deletion dimos/models/embedding/siglip2.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
from functools import cached_property
from typing import overload

from dimos.messages.sensor_msgs.msg import Image
from PIL import Image as PILImage
import torch
import torch.nn.functional as functional
from transformers import SiglipModel as HFSiglipModel, SiglipProcessor

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import HuggingFaceModel
from dimos.models.embedding.base import Embedding, EmbeddingModel, HuggingFaceEmbeddingModelConfig
from dimos.msgs.image import image_to_rgb
Expand Down
37 changes: 21 additions & 16 deletions dimos/models/embedding/test_generated_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,25 @@
from dimos.msgs.image import image_from_array, image_view


@pytest.mark.parametrize("model_type", [CLIPModel,DINOModel,SigLIPModel,SigLIP2Model])
def test_generated_embeddings_keep_zero_source_time_and_convert_rgb(mocker,model_type):
processor,network=mocker.Mock(),mocker.Mock()
features=torch.tensor([[3.0,4.0]])
processor.return_value=BatchFeature(data={"pixel_values":torch.zeros(1,3,4,8)})
network.get_image_features.return_value=features
network.return_value=BaseModelOutputWithPooling(last_hidden_state=features[:,None,:],pooler_output=features)
mocker.patch.object(model_type,"_processor",new_callable=PropertyMock,return_value=processor)
mocker.patch.object(model_type,"_model",new_callable=PropertyMock,return_value=network)
image=image_from_array(np.full((4,8,3),[0,0,255],dtype=np.uint8),encoding="bgr8",
header=Header(frame_id="camera",stamp=Time(sec=0,nanosec=0)))
model=model_type(device="cpu")
result=model.embed(image)
@pytest.mark.parametrize("model_type", [CLIPModel, DINOModel, SigLIPModel, SigLIP2Model])
def test_generated_embeddings_keep_zero_source_time_and_convert_rgb(mocker, model_type):
processor, network = mocker.Mock(), mocker.Mock()
features = torch.tensor([[3.0, 4.0]])
processor.return_value = BatchFeature(data={"pixel_values": torch.zeros(1, 3, 4, 8)})
network.get_image_features.return_value = features
network.return_value = BaseModelOutputWithPooling(
last_hidden_state=features[:, None, :], pooler_output=features
)
mocker.patch.object(model_type, "_processor", new_callable=PropertyMock, return_value=processor)
mocker.patch.object(model_type, "_model", new_callable=PropertyMock, return_value=network)
image = image_from_array(
np.full((4, 8, 3), [0, 0, 255], dtype=np.uint8),
encoding="bgr8",
header=Header(frame_id="camera", stamp=Time(sec=0, nanosec=0)),
)
model = model_type(device="cpu")
result = model.embed(image)
assert result.timestamp == 0.0
torch.testing.assert_close(result.vector,torch.tensor([0.6,0.8]))
assert processor.call_args.kwargs["images"][0].getpixel((0,0)) == (255,0,0)
np.testing.assert_array_equal(image_view(image)[0,0],[0,0,255])
torch.testing.assert_close(result.vector, torch.tensor([0.6, 0.8]))
assert processor.call_args.kwargs["images"][0].getpixel((0, 0)) == (255, 0, 0)
np.testing.assert_array_equal(image_view(image)[0, 0], [0, 0, 255])
2 changes: 1 addition & 1 deletion dimos/models/embedding/treid.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@

from functools import cached_property

from dimos.messages.sensor_msgs.msg import Image
import torch
import torch.nn.functional as functional
from torchreid import utils as torchreid_utils

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import LocalModel
from dimos.models.embedding.base import Embedding, EmbeddingModel, EmbeddingModelConfig
from dimos.msgs.image import image_to_bgr
Expand Down
2 changes: 1 addition & 1 deletion dimos/models/segmentation/edge_tam.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
import tempfile
from typing import TYPE_CHECKING, Any, Protocol, TypedDict

from dimos.messages.sensor_msgs.msg import Image
from hydra.utils import instantiate
import numpy as np
from numpy.typing import NDArray
from omegaconf import OmegaConf
from PIL import Image as PILImage
import torch

from dimos.messages.sensor_msgs.msg import Image
from dimos.models.base import default_torch_device
from dimos.msgs.image import image_to_bgr, image_to_jpeg
from dimos.perception.detection.detectors.base import Detector
Expand Down
4 changes: 2 additions & 2 deletions dimos/models/segmentation/test_yoloe.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
from typing import Any
from unittest.mock import MagicMock

from dimos.messages.sensor_msgs.msg import Image
from dimos.messages.std_msgs.msg import Header
import numpy as np
from ultralytics.models.yolo.yoloe import YOLOEVPSegPredictor

from dimos.messages.sensor_msgs.msg import Image
from dimos.messages.std_msgs.msg import Header
from dimos.models.segmentation.yoloe import YoloeBoxSegmenter
from dimos.msgs.image import image_from_array, image_to_bgr
from dimos.msgs.time import time_from_seconds, to_seconds
Expand Down
2 changes: 1 addition & 1 deletion dimos/models/segmentation/yoloe.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
from dataclasses import replace
from typing import Protocol

from dimos.messages.sensor_msgs.msg import Image
import numpy as np
from numpy.typing import NDArray
from ultralytics.models.yolo.yoloe import YOLOEVPSegPredictor

from dimos.messages.sensor_msgs.msg import Image
from dimos.msgs.image import image_to_bgr
from dimos.perception.detection.detectors.yoloe import Yoloe2DDetector, YoloePromptMode
from dimos.perception.detection.type.detection2d.bbox import Bbox, Detection2DBBox
Expand Down
47 changes: 40 additions & 7 deletions dimos/navigation/global_planner/test_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,58 @@
import rerun as rr

from dimos.mapping.ray_tracing.viz import render_map_region
from dimos.msgs.geometry_msgs.PointStamped import PointStamped
from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped
from dimos.msgs.nav_msgs.Path import Path
from dimos.messages.geometry_msgs.msg import Point, PointStamped, Pose, PoseStamped
from dimos.messages.nav_msgs.msg import Path
from dimos.messages.std_msgs.msg import Header
from dimos.msgs.geometry import quaternion_from_euler
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
from dimos.msgs.time import time_from_nanoseconds
from dimos.navigation.global_planner import viz
from dimos.navigation.global_planner.mls_planner.viz import render_surface_region
from dimos.visualization.rerun.bridge import is_keyed_by_seq


def test_goal_placeholder_is_not_drawn() -> None:
assert viz.render_goal(PointStamped(x=float("nan"), y=0.0, z=0.0)) is None
goal = viz.render_goal(PointStamped(x=1.0, y=2.0, z=0.0))
assert (
viz.render_goal(
PointStamped(
header=Header(frame_id="odom", stamp=time_from_nanoseconds(0)),
point=Point(x=float("nan"), y=0.0, z=0.0),
)
)
is None
)
goal = viz.render_goal(
PointStamped(
header=Header(frame_id="odom", stamp=time_from_nanoseconds(0)),
point=Point(x=1.0, y=2.0, z=0.0),
)
)
assert goal is not None
assert goal.positions.as_arrow_array().to_pylist() == [[1.0, 2.0, 0.0]]


def test_empty_path_keeps_the_last_one_drawn() -> None:
assert viz.render_path(Path(poses=[])) is None
path = viz.render_path(Path(poses=[PoseStamped(1.0, 0.0, 0.0), PoseStamped(2.0, 0.0, 0.0)]))
assert (
viz.render_path(
Path(header=Header(frame_id="odom", stamp=time_from_nanoseconds(0)), poses=[])
)
is None
)
path = viz.render_path(
Path(
header=Header(frame_id="odom", stamp=time_from_nanoseconds(0)),
poses=[
PoseStamped(
header=Header(frame_id="odom", stamp=time_from_nanoseconds(0)),
pose=Pose(
position=Point(x=x, y=0, z=0), orientation=quaternion_from_euler(0, 0, 0)
),
)
for x in [1.0, 2.0]
],
)
)
assert path is not None
lift = viz.PATH_Z_LIFT
np.testing.assert_allclose(
Expand Down
15 changes: 10 additions & 5 deletions dimos/navigation/global_planner/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
from numpy.typing import NDArray
from rerun._baseclasses import Archetype

from dimos.msgs.geometry_msgs.PointStamped import PointStamped
from dimos.msgs.nav_msgs.Path import Path
from dimos.messages.geometry_msgs.msg import PointStamped
from dimos.messages.nav_msgs.msg import Path
from dimos.visualization.rerun.bridge import VisualOverride

# rerun is imported inside the renderers: it is heavy and only viewers load it.
Expand Down Expand Up @@ -66,7 +66,12 @@ def render_path(msg: Path) -> Archetype | None:
"""The planned route. An empty path means no route, so the last one stays drawn."""
if len(msg.poses) == 0:
return None
return path_strip(np.array([[p.x, p.y, p.z] for p in msg.poses], dtype=np.float32))
return path_strip(
np.array(
[[p.pose.position.x, p.pose.position.y, p.pose.position.z] for p in msg.poses],
dtype=np.float32,
)
)


def goal_point(xyz: tuple[float, float, float]) -> Archetype:
Expand All @@ -77,9 +82,9 @@ def goal_point(xyz: tuple[float, float, float]) -> Archetype:

def render_goal(msg: PointStamped) -> Archetype | None:
"""The active goal. NaN is the movement manager's placeholder for none."""
if any(math.isnan(v) for v in (msg.x, msg.y, msg.z)):
if any(math.isnan(v) for v in (msg.point.x, msg.point.y, msg.point.z)):
return None
return goal_point((msg.x, msg.y, msg.z))
return goal_point((msg.point.x, msg.point.y, msg.point.z))


def robot_body_box(length: float, width: float, height: float) -> Archetype:
Expand Down
Loading
Loading