From f8bef48cf85f129b87d4be7380d9474afce6ff71 Mon Sep 17 00:00:00 2001 From: lesh Date: Mon, 29 Sep 2025 18:13:58 -0700 Subject: [PATCH 1/7] nav/pointcloud spec proposal --- dimos/map/spec.py | 31 +++++++++++++++++++++++++++++++ dimos/navigation/spec.py | 32 ++++++++++++++++++++++++++++++++ dimos/perception/spec.py | 22 ++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 dimos/map/spec.py create mode 100644 dimos/navigation/spec.py create mode 100644 dimos/perception/spec.py diff --git a/dimos/map/spec.py b/dimos/map/spec.py new file mode 100644 index 0000000000..0733b8ce33 --- /dev/null +++ b/dimos/map/spec.py @@ -0,0 +1,31 @@ +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC + +from dimos.core import Out +from dimos.msgs.nav_msgs import OccupancyGrid +from dimos.msgs.sensor_msgs import PointCloud2 + + +class GlobalPointcloudSpec(ABC): + global_pointcloud: Out[PointCloud2] = None + + +class GlobalMapSpec(ABC): + global_map: Out[OccupancyGrid] = None + + +class GlobalCostmapSpec(ABC): + global_costmap: Out[OccupancyGrid] = None diff --git a/dimos/navigation/spec.py b/dimos/navigation/spec.py new file mode 100644 index 0000000000..14370ab53d --- /dev/null +++ b/dimos/navigation/spec.py @@ -0,0 +1,32 @@ +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC + +from dimos.core import In, Out +from dimos.msgs.geometry_msgs import Path, PoseStamped, Twist + + +class NavSpec(ABC): + goal_req: In[PoseStamped] = None # type: ignore + goal_active: Out[PoseStamped] = None # type: ignore + path_active: Out[Path] = None # type: ignore + ctrl: Out[Twist] = None # type: ignore + + # identity quaternion (Quaternion(0,0,0,1)) represents "no rotation requested" + def goto(self, target: PoseStamped) -> None: + pass + + def stop(self) -> None: + pass diff --git a/dimos/perception/spec.py b/dimos/perception/spec.py new file mode 100644 index 0000000000..0b73750d53 --- /dev/null +++ b/dimos/perception/spec.py @@ -0,0 +1,22 @@ +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC + +from dimos.core import Out +from dimos.msgs.sensor_msgs import PointCloud2 + + +class PointcloudPerception(ABC): + pointcloud: Out[PointCloud2] = None # type: ignore From 6a669b69ad0c338fc53bfa8c6238be0c6a946e2e Mon Sep 17 00:00:00 2001 From: lesh Date: Mon, 29 Sep 2025 18:19:12 -0700 Subject: [PATCH 2/7] rosnav spec --- dimos/navigation/rosnav.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 dimos/navigation/rosnav.py diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py new file mode 100644 index 0000000000..86b1558895 --- /dev/null +++ b/dimos/navigation/rosnav.py @@ -0,0 +1,40 @@ +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dimos.core import In, Module, Out +from dimos.msgs.geometry_msgs import Path, PoseStamped, Twist +from dimos.msgs.sensor_msgs import PointCloud2 +from dimos.navigation.spec import NavSpec +from dimos.perception.pointcloud.spec import GlobalPointcloudPerception, PointcloudPerception + + +class Config: + global_frame_id: str = "world" + + +class RosNav(Module, PointcloudPerception, GlobalPointcloudPerception, NavSpec): + goal_req: In[PoseStamped] = None # type: ignore + goal_active: Out[PoseStamped] = None # type: ignore + path_active: Out[Path] = None # type: ignore + + ctrl: Out[Twist] = None # type: ignore + + pointcloud: Out[PointCloud2] = None # type: ignore + global_pointcloud: Out[PointCloud2] = None # type: ignore + + config: Config + + def __init__(self, *args, **kwargs): + self.config = Config(**kwargs) + super().__init__() From e5571276bfa27308f986458e5602fb5a3b44ef50 Mon Sep 17 00:00:00 2001 From: lesh Date: Mon, 29 Sep 2025 18:22:08 -0700 Subject: [PATCH 3/7] cleaner nav spec --- dimos/{map => mapping}/spec.py | 2 +- dimos/navigation/rosnav.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) rename dimos/{map => mapping}/spec.py (96%) diff --git a/dimos/map/spec.py b/dimos/mapping/spec.py similarity index 96% rename from dimos/map/spec.py rename to dimos/mapping/spec.py index 0733b8ce33..c8675df3f9 100644 --- a/dimos/map/spec.py +++ b/dimos/mapping/spec.py @@ -19,7 +19,7 @@ from dimos.msgs.sensor_msgs import PointCloud2 -class GlobalPointcloudSpec(ABC): +class Global3DMapSpec(ABC): global_pointcloud: Out[PointCloud2] = None diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index 86b1558895..09be7c5096 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -13,17 +13,18 @@ # limitations under the License. from dimos.core import In, Module, Out +from dimos.mapping.spec import Global3DMapSpec from dimos.msgs.geometry_msgs import Path, PoseStamped, Twist from dimos.msgs.sensor_msgs import PointCloud2 from dimos.navigation.spec import NavSpec -from dimos.perception.pointcloud.spec import GlobalPointcloudPerception, PointcloudPerception +from dimos.perception.pointcloud.spec import PointcloudPerception class Config: global_frame_id: str = "world" -class RosNav(Module, PointcloudPerception, GlobalPointcloudPerception, NavSpec): +class RosNav(Module, PointcloudPerception, Global3DMapSpec, NavSpec): goal_req: In[PoseStamped] = None # type: ignore goal_active: Out[PoseStamped] = None # type: ignore path_active: Out[Path] = None # type: ignore From 37c84e56394db81e32dc923a6c08d4a792dae12f Mon Sep 17 00:00:00 2001 From: lesh Date: Tue, 30 Sep 2025 14:41:50 -0700 Subject: [PATCH 4/7] PR comments --- dimos/navigation/rosnav.py | 10 ---------- dimos/navigation/spec.py | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index 09be7c5096..b3104eee5b 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -20,10 +20,6 @@ from dimos.perception.pointcloud.spec import PointcloudPerception -class Config: - global_frame_id: str = "world" - - class RosNav(Module, PointcloudPerception, Global3DMapSpec, NavSpec): goal_req: In[PoseStamped] = None # type: ignore goal_active: Out[PoseStamped] = None # type: ignore @@ -33,9 +29,3 @@ class RosNav(Module, PointcloudPerception, Global3DMapSpec, NavSpec): pointcloud: Out[PointCloud2] = None # type: ignore global_pointcloud: Out[PointCloud2] = None # type: ignore - - config: Config - - def __init__(self, *args, **kwargs): - self.config = Config(**kwargs) - super().__init__() diff --git a/dimos/navigation/spec.py b/dimos/navigation/spec.py index 14370ab53d..8c752c8af1 100644 --- a/dimos/navigation/spec.py +++ b/dimos/navigation/spec.py @@ -25,8 +25,8 @@ class NavSpec(ABC): ctrl: Out[Twist] = None # type: ignore # identity quaternion (Quaternion(0,0,0,1)) represents "no rotation requested" - def goto(self, target: PoseStamped) -> None: + def navigate_to_target(self, target: PoseStamped) -> None: pass - def stop(self) -> None: + def stop_navigating(self) -> None: pass From 0e4740f39e8d32843f04132cc630aa5c98cf9d1b Mon Sep 17 00:00:00 2001 From: lesh Date: Tue, 30 Sep 2025 14:45:03 -0700 Subject: [PATCH 5/7] type check --- dimos/navigation/rosnav.py | 3 ++- dimos/navigation/spec.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index b3104eee5b..bea3a7d542 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -14,7 +14,8 @@ from dimos.core import In, Module, Out from dimos.mapping.spec import Global3DMapSpec -from dimos.msgs.geometry_msgs import Path, PoseStamped, Twist +from dimos.msgs.geometry_msgs import PoseStamped, Twist +from dimos.msgs.nav_msgs import Path from dimos.msgs.sensor_msgs import PointCloud2 from dimos.navigation.spec import NavSpec from dimos.perception.pointcloud.spec import PointcloudPerception diff --git a/dimos/navigation/spec.py b/dimos/navigation/spec.py index 8c752c8af1..69aa7b2409 100644 --- a/dimos/navigation/spec.py +++ b/dimos/navigation/spec.py @@ -15,7 +15,8 @@ from abc import ABC from dimos.core import In, Out -from dimos.msgs.geometry_msgs import Path, PoseStamped, Twist +from dimos.msgs.geometry_msgs import PoseStamped, Twist +from dimos.msgs.nav_msgs import Path class NavSpec(ABC): From e9f5057911029725b6f7f1ebfa58e1a5cbd0afdd Mon Sep 17 00:00:00 2001 From: lesh Date: Tue, 30 Sep 2025 15:11:24 -0700 Subject: [PATCH 6/7] switched the spec to protocols --- dimos/mapping/spec.py | 14 +++++++------- dimos/navigation/rosnav.py | 17 ++++++++++++----- dimos/navigation/spec.py | 18 ++++++++---------- dimos/perception/spec.py | 6 +++--- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/dimos/mapping/spec.py b/dimos/mapping/spec.py index c8675df3f9..3d82cea0cc 100644 --- a/dimos/mapping/spec.py +++ b/dimos/mapping/spec.py @@ -12,20 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC +from typing import Protocol from dimos.core import Out from dimos.msgs.nav_msgs import OccupancyGrid from dimos.msgs.sensor_msgs import PointCloud2 -class Global3DMapSpec(ABC): - global_pointcloud: Out[PointCloud2] = None +class Global3DMapSpec(Protocol): + global_pointcloud: Out[PointCloud2] -class GlobalMapSpec(ABC): - global_map: Out[OccupancyGrid] = None +class GlobalMapSpec(Protocol): + global_map: Out[OccupancyGrid] -class GlobalCostmapSpec(ABC): - global_costmap: Out[OccupancyGrid] = None +class GlobalCostmapSpec(Protocol): + global_costmap: Out[OccupancyGrid] diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index bea3a7d542..9bdee3fe3a 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -13,20 +13,27 @@ # limitations under the License. from dimos.core import In, Module, Out -from dimos.mapping.spec import Global3DMapSpec from dimos.msgs.geometry_msgs import PoseStamped, Twist from dimos.msgs.nav_msgs import Path from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.navigation.spec import NavSpec -from dimos.perception.pointcloud.spec import PointcloudPerception -class RosNav(Module, PointcloudPerception, Global3DMapSpec, NavSpec): +class ROSNav(Module): goal_req: In[PoseStamped] = None # type: ignore goal_active: Out[PoseStamped] = None # type: ignore path_active: Out[Path] = None # type: ignore - ctrl: Out[Twist] = None # type: ignore + # PointcloudPerception attributes pointcloud: Out[PointCloud2] = None # type: ignore + + # Global3DMapSpec attributes global_pointcloud: Out[PointCloud2] = None # type: ignore + + def navigate_to_target(self, target: PoseStamped) -> None: + # TODO: Implement navigation logic + pass + + def stop_navigating(self) -> None: + # TODO: Implement stop logic + pass diff --git a/dimos/navigation/spec.py b/dimos/navigation/spec.py index 69aa7b2409..69bfdac262 100644 --- a/dimos/navigation/spec.py +++ b/dimos/navigation/spec.py @@ -12,22 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC +from typing import Protocol from dimos.core import In, Out from dimos.msgs.geometry_msgs import PoseStamped, Twist from dimos.msgs.nav_msgs import Path -class NavSpec(ABC): - goal_req: In[PoseStamped] = None # type: ignore - goal_active: Out[PoseStamped] = None # type: ignore - path_active: Out[Path] = None # type: ignore - ctrl: Out[Twist] = None # type: ignore +class NavSpec(Protocol): + goal_req: In[PoseStamped] + goal_active: Out[PoseStamped] + path_active: Out[Path] + ctrl: Out[Twist] # identity quaternion (Quaternion(0,0,0,1)) represents "no rotation requested" - def navigate_to_target(self, target: PoseStamped) -> None: - pass + def navigate_to_target(self, target: PoseStamped) -> None: ... - def stop_navigating(self) -> None: - pass + def stop_navigating(self) -> None: ... diff --git a/dimos/perception/spec.py b/dimos/perception/spec.py index 0b73750d53..de53ce9bd7 100644 --- a/dimos/perception/spec.py +++ b/dimos/perception/spec.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC +from typing import Protocol from dimos.core import Out from dimos.msgs.sensor_msgs import PointCloud2 -class PointcloudPerception(ABC): - pointcloud: Out[PointCloud2] = None # type: ignore +class PointcloudPerception(Protocol): + pointcloud: Out[PointCloud2] From f913f57a09a2d869dbc22b2afbae14e142cfca5b Mon Sep 17 00:00:00 2001 From: lesh Date: Tue, 30 Sep 2025 15:13:19 -0700 Subject: [PATCH 7/7] rosnav type check --- dimos/navigation/test_rosnav.py | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 dimos/navigation/test_rosnav.py diff --git a/dimos/navigation/test_rosnav.py b/dimos/navigation/test_rosnav.py new file mode 100644 index 0000000000..5de1c0e6ab --- /dev/null +++ b/dimos/navigation/test_rosnav.py @@ -0,0 +1,37 @@ +# Copyright 2025 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Protocol + +from dimos.mapping.spec import Global3DMapSpec +from dimos.navigation.rosnav import ROSNav +from dimos.navigation.spec import NavSpec +from dimos.perception.spec import PointcloudPerception + + +class RosNavSpec(NavSpec, PointcloudPerception, Global3DMapSpec, Protocol): + """Combined protocol for navigation components.""" + + pass + + +def accepts_combined_protocol(nav: RosNavSpec) -> None: + """Function that accepts all navigation protocols at once.""" + pass + + +def test_typing_prototypes(): + """Test that ROSNav correctly implements all required protocols.""" + rosnav = ROSNav() + accepts_combined_protocol(rosnav)