Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Multiple Color Formats in Room Class #306

Merged
merged 13 commits into from
Dec 4, 2024
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
16 changes: 12 additions & 4 deletions pyrobosim/examples/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@ def create_world(multirobot=False):
world.add_room(
name="kitchen",
footprint=r1coords,
color=[1, 0, 0],
color="red",
nav_poses=[Pose(x=0.75, y=0.75, z=0.0, yaw=0.0)],
)
r2coords = [(1.75, 2.5), (3.5, 2.5), (3.5, 4), (1.75, 4)]
world.add_room(name="bedroom", footprint=r2coords, color=[0, 0.6, 0])
world.add_room(name="bedroom", footprint=r2coords, color="#009900")
r3coords = [(-1, 1), (-1, 3.5), (-3.0, 3.5), (-2.5, 1)]
world.add_room(name="bathroom", footprint=r3coords, color=[0, 0, 0.6])
world.add_room(
name="bathroom",
footprint=r3coords,
color=[0.0, 0.0, 0.6],
)

# Add hallways between the rooms
world.add_hallway(room_start="kitchen", room_end="bathroom", width=0.7)
world.add_hallway(
room_start="kitchen",
room_end="bathroom",
width=0.7,
)
world.add_hallway(
room_start="bathroom",
room_end="bedroom",
Expand Down
12 changes: 9 additions & 3 deletions pyrobosim/pyrobosim/core/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
from shapely.geometry import Polygon
from shapely.plotting import patch_from_polygon

from matplotlib.colors import CSS4_COLORS, to_rgb

from ..utils.pose import Pose
from ..utils.polygon import inflate_polygon, polygon_and_height_from_footprint
from ..utils.search_graph import Node
from ..utils.general import parse_color


class Room:
Expand All @@ -30,8 +33,11 @@ def __init__(
:type name: str, optional
:param footprint: Point list or Shapely polygon describing the room 2D footprint (required).
:type footprint: :class:`shapely.geometry.Polygon`/list[:class:`pyrobosim.utils.pose.Pose`]
:param color: Visualization color as an (R, G, B) tuple in the range (0.0, 1.0)
:type color: (float, float, float), optional
:param color: Visualization color. Input can be
- an (R, G, B) tuple, list in the range (0.0, 1.0),
- a string (e.g., "red")
- a hexadecimal (e.g., "#FF0000").
:type color: list[float] | tuple[float, float, float] | str
:param wall_width: Width of room walls, in meters.
:type wall_width: float, optional
:param nav_poses: List of navigation poses in the room. If not specified, defaults to the centroid.
Expand All @@ -41,7 +47,7 @@ def __init__(
"""
self.name = name
self.wall_width = wall_width
self.viz_color = color
self.viz_color = parse_color(color)

# Entities associated with the room
self.hallways = []
Expand Down
4 changes: 2 additions & 2 deletions pyrobosim/pyrobosim/data/test_world.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ rooms:
x: 0.75
y: 0.5
wall_width: 0.2
color: [1, 0, 0]
color: "red"

- name: bedroom
footprint:
type: box
dims: [1.75, 1.5]
offset: [2.625, 3.25]
wall_width: 0.2
color: [0, 0.6, 0]
color: "#009900"

- name: bathroom
footprint:
Expand Down
4 changes: 2 additions & 2 deletions pyrobosim/pyrobosim/data/test_world_multirobot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ rooms:
x: 0.75
y: 0.5
wall_width: 0.2
color: [1, 0, 0]
color: "red"

- name: bedroom
footprint:
type: box
dims: [1.75, 1.5]
offset: [2.625, 3.25]
wall_width: 0.2
color: [0, 0.6, 0]
color: "#009900"

- name: bathroom
footprint:
Expand Down
29 changes: 29 additions & 0 deletions pyrobosim/pyrobosim/utils/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import os
import yaml
import re
from matplotlib.colors import CSS4_COLORS, to_rgb


def get_data_folder():
Expand Down Expand Up @@ -99,3 +101,30 @@ def replace_special_yaml_tokens(in_text, root_dir=None):
out_text = out_text.replace("$PWD", root_dir)

return out_text


def parse_color(color):
"""
Parses a color input and returns an RGB tuple.

:param color: Input color as a list, tuple, string, or hexadecimal.
:type color: list[float] | tuple[float, float, float] | str
:return: RGB tuple in range (0.0, 1.0).
:rtype: tuple[float, float, float]
"""
if isinstance(color, (list, tuple)) and len(color) == 3:
return tuple(color)

if isinstance(color, str):
if color in CSS4_COLORS:
return to_rgb(CSS4_COLORS[color])

hex_pattern = r"^#(?:[0-9a-fA-F]{3}){1,2}$"
if re.match(hex_pattern, color):
return to_rgb(color)

raise ValueError(f"Invalid color string or hex: {color}")

raise ValueError(
f"Unsupported color format. Supported types are list[float] and string"
)
3 changes: 2 additions & 1 deletion pyrobosim/test/core/test_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import pytest

from pyrobosim.core import Room, World


Expand Down Expand Up @@ -32,7 +33,7 @@ def test_add_room_to_world_from_args(self):
world = World()

coords = [(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)]
color = [1.0, 0.0, 0.1]
color = (1.0, 0.0, 0.1)
result = world.add_room(footprint=coords, color=color)

assert isinstance(result, Room)
Expand Down
4 changes: 2 additions & 2 deletions pyrobosim/test/core/test_yaml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ def test_create_rooms_from_yaml():
poly, _ = polygon_and_height_from_footprint(rooms_dict["rooms"][0]["footprint"])
assert loader.world.rooms[0].polygon == poly
assert loader.world.rooms[0].wall_width == 0.2
assert loader.world.rooms[0].viz_color == [1, 0, 0]
assert loader.world.rooms[0].viz_color == (1, 0, 0)
assert loader.world.rooms[0].nav_poses == [Pose.from_list([0.75, 0.5, 0.0])]

assert loader.world.rooms[1].name == "bedroom"
poly, _ = polygon_and_height_from_footprint(rooms_dict["rooms"][1]["footprint"])
assert loader.world.rooms[1].polygon == poly
assert loader.world.rooms[1].wall_width == 0.2
assert loader.world.rooms[1].viz_color == [0, 1, 0]
assert loader.world.rooms[1].viz_color == (0, 1, 0)
assert loader.world.rooms[1].nav_poses == [
Pose.from_list(loader.world.rooms[1].centroid)
]
Expand Down
43 changes: 43 additions & 0 deletions pyrobosim/test/utils/test_general_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest
from matplotlib.colors import CSS4_COLORS, to_rgb

from pyrobosim.utils.general import parse_color


def test_parse_color():
"""Testing parse color with different input color formats"""
# Test with RGB list
color_rgb_list = (1.0, 0.0, 0.0)
assert parse_color([1.0, 0.0, 0.0]) == color_rgb_list

# Test with RGB tuple
color_rgb_tuple = (0.0, 1.0, 0.0)
assert parse_color((0.0, 1.0, 0.0)) == color_rgb_tuple

# Test with named color
color_by_name = "red"
assert parse_color("red") == to_rgb(CSS4_COLORS[color_by_name])

# Test with hexadecimal color format
color_hex = "#00FFFF"
assert parse_color("#00FFFF") == to_rgb(color_hex)

# Test with invalid RGB list
with pytest.raises(ValueError):
parse_color([1.0, 0.0])
sea-bass marked this conversation as resolved.
Show resolved Hide resolved

# Test with invalid RGB tuple
with pytest.raises(ValueError):
parse_color((1.0, 0.0))

# Test with invalid named color
with pytest.raises(ValueError):
parse_color("notavalidcolor")

# Test with invalid hexadecimal color format
with pytest.raises(ValueError):
parse_color("#ZZZ")

# Test with unsupported input type
with pytest.raises(ValueError):
parse_color(12345)
Loading