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

346 Physical Camera Sensor #482

Merged
merged 3 commits into from
Mar 20, 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
9 changes: 9 additions & 0 deletions modular_robot/revolve2/modular_robot/body/_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ def color(self) -> Color:
"""
return self._color

@color.setter
def color(self, color: Color) -> None:
"""
Set the color of a module.

:param color: The color
"""
self._color = color

@property
def attachment_points(self) -> dict[int, AttachmentPoint]:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PROTOCOL_VERSION = "1.0.0"
PROTOCOL_VERSION = "1.0.2"
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from abc import ABC, abstractmethod
from typing import Sequence

import numpy as np
from numpy.typing import NDArray
from pyrr import Vector3


Expand Down Expand Up @@ -75,3 +77,12 @@ def get_imu_specific_force(self) -> Vector3:
:returns: The specific force.
:raises NotImplementedError: If the IMU is not supported on this hardware.
"""

@abstractmethod
def get_camera_view(self) -> NDArray[np.uint8]:
"""
Get the current view from the camera.

:return: The camera view.
:raises NotImplementedError: If the Camera is not supported on this hardware.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import time
from typing import Sequence

import numpy as np
import pigpio
from numpy.typing import NDArray
from pyrr import Vector3

from .._physical_interface import PhysicalInterface
Expand Down Expand Up @@ -126,3 +128,11 @@ def get_imu_specific_force(self) -> Vector3:
:raises NotImplementedError: Always.
"""
raise NotImplementedError()

def get_camera_view(self) -> NDArray[np.uint8]:
"""
Get the current view from the camera.

:raises NotImplementedError: If the Camera is not supported on this hardware.
"""
raise NotImplementedError()
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import math
from typing import Sequence

import numpy as np
from numpy.typing import NDArray
from pyrr import Vector3
from robohatlib.hal.assemblyboard.PwmPlug import PwmPlug
from robohatlib.hal.assemblyboard.servo.ServoData import ServoData
Expand Down Expand Up @@ -173,3 +175,11 @@ def get_imu_specific_force(self) -> Vector3:
if accel is None:
raise RuntimeError("Could not get IMU acceleration reading!")
return Vector3(accel)

def get_camera_view(self) -> NDArray[np.uint8]:
"""
Get the current view from the camera.

:raises NotImplementedError: If the Camera is not supported on this hardware.
"""
raise NotImplementedError()
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import numpy as np
from numpy.typing import NDArray

from revolve2.modular_robot.sensor_state import CameraSensorState


class CameraSensorStateImpl(CameraSensorState):
"""CameraSensorState implementation for physical robots."""

_image: NDArray[np.uint8]

def __init__(self, image: NDArray[np.uint8]) -> None:
"""
Initialize this object.

:param image: The current image.
"""
self._image = image

@property
def image(self) -> NDArray[np.uint8]:
"""
Get the current image.

:returns: The image.
"""
return self._image
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .._uuid_key import UUIDKey
from ._active_hinge_sensor_state_impl import ActiveHingeSensorStateImpl
from ._camera_sensor_state_impl import CameraSensorStateImpl
from ._imu_sensor_state_impl import IMUSensorStateImpl


Expand All @@ -22,23 +23,27 @@ class ModularRobotSensorStateImplV2(ModularRobotSensorState):
_hinge_positions: dict[int, float]

_imu_sensor_states: dict[UUIDKey[IMUSensor], IMUSensorStateImpl]
_camera_sensor_states: dict[UUIDKey[CameraSensor], CameraSensorStateImpl]

def __init__(
self,
hinge_sensor_mapping: dict[UUIDKey[ActiveHingeSensor], int],
hinge_positions: dict[int, float],
imu_sensor_states: dict[UUIDKey[IMUSensor], IMUSensorStateImpl],
camera_sensor_states: dict[UUIDKey[CameraSensor], CameraSensorStateImpl],
) -> None:
"""
Initialize this object.

:param hinge_sensor_mapping: Mapping from active hinge sensors to pin ids.
:param hinge_positions: Position of hinges accessed by pin id.
:param imu_sensor_states: State of the IMU sensors.
:param camera_sensor_states: The states of the Camera sensors.
"""
self._hinge_sensor_mapping = hinge_sensor_mapping
self._hinge_positions = hinge_positions
self._imu_sensor_states = imu_sensor_states
self._camera_sensor_states = camera_sensor_states

def get_active_hinge_sensor_state(
self, sensor: ActiveHingeSensor
Expand Down Expand Up @@ -73,6 +78,12 @@ def get_camera_sensor_state(self, sensor: CameraSensor) -> CameraSensorState:
Get the state of the provided camera sensor.

:param sensor: The sensor.
:raises NotImplementedError: Always.
:raises ValueError: If sensor is not found on the robot.
:returns: The camera sensor state.
"""
raise NotImplementedError()
state = self._camera_sensor_states.get(UUIDKey(sensor))
if state is None:
raise ValueError(
"State for camera sensor not found. Does it exist in the robot definition?"
)
return state
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from typing import Callable

import capnp
import numpy as np
from numpy.typing import NDArray
from pyrr import Vector3

from revolve2.modular_robot.body.base import ActiveHinge
from revolve2.modular_robot.body.sensors import CameraSensor, IMUSensor
from revolve2.modular_robot.sensor_state import ModularRobotSensorState

from .._config import Config
Expand All @@ -14,6 +17,7 @@
from .._standard_port import STANDARD_PORT
from .._uuid_key import UUIDKey
from ..robot_daemon_api import robot_daemon_protocol_capnp
from ._camera_sensor_state_impl import CameraSensorStateImpl
from ._imu_sensor_state_impl import IMUSensorStateImpl
from ._modular_robot_control_interface_impl import ModularRobotControlInterfaceImpl
from ._modular_robot_sensor_state_impl_v1 import ModularRobotSensorStateImplV1
Expand Down Expand Up @@ -105,7 +109,7 @@ async def _run_remote_impl(
)
)
).response
print(f"Battery level is at {sensor_readings.battery*100.0}%.")
print(f"Battery level is at {sensor_readings.battery * 100.0}%.")

# Fire prepared callback
on_prepared()
Expand Down Expand Up @@ -154,25 +158,22 @@ async def _run_remote_impl(
robot_daemon_protocol_capnp.ReadSensorsArgs(readPins=pins)
)
).response
if config.modular_robot.body.core.sensors.imu_sensor is None:
imu_sensor_states = {}
else:
imu_sensor_states = {
UUIDKey(
config.modular_robot.body.core.sensors.imu_sensor
): IMUSensorStateImpl(
_capnp_to_vector3(sensor_readings.imuSpecificForce),
_capnp_to_vector3(sensor_readings.imuAngularRate),
_capnp_to_vector3(sensor_readings.imuOrientation),
)
}
imu_sensor_states = _get_imu_sensor_state(
config.modular_robot.body.core.sensors.imu_sensor, sensor_readings
)
camera_sensor_states = _get_camera_sensor_state(
config.modular_robot.body.core.sensors.camera_sensor,
sensor_readings,
)

sensor_state = ModularRobotSensorStateImplV2(
hinge_sensor_mapping=active_hinge_sensor_to_pin,
hinge_positions={
pin: position
for pin, position in zip(pins, sensor_readings.pins)
},
imu_sensor_states=imu_sensor_states,
camera_sensor_states=camera_sensor_states,
)
case _:
raise NotImplementedError("Hardware type not supported.")
Expand Down Expand Up @@ -221,29 +222,30 @@ async def _run_remote_impl(
)
)
).response
if config.modular_robot.body.core.sensors.imu_sensor is None:
imu_sensor_states = {}
else:
imu_sensor_states = {
UUIDKey(
config.modular_robot.body.core.sensors.imu_sensor
): IMUSensorStateImpl(
_capnp_to_vector3(sensor_readings.imuSpecificForce),
_capnp_to_vector3(sensor_readings.imuAngularRate),
_capnp_to_vector3(sensor_readings.imuOrientation),
)
}

imu_sensor_states = _get_imu_sensor_state(
config.modular_robot.body.core.sensors.imu_sensor,
sensor_readings,
)
camera_sensor_states = _get_camera_sensor_state(
config.modular_robot.body.core.sensors.camera_sensor,
sensor_readings,
)

sensor_state = ModularRobotSensorStateImplV2(
hinge_sensor_mapping=active_hinge_sensor_to_pin,
hinge_positions={
pin: position
for pin, position in zip(pins, sensor_readings.pins)
},
imu_sensor_states=imu_sensor_states,
camera_sensor_states=camera_sensor_states,
)

if battery_print_timer > 5.0:
print(f"Battery level is at {sensor_readings.battery*100.0}%.")
print(
f"Battery level is at {sensor_readings.battery * 100.0}%."
)
battery_print_timer = 0.0
case _:
raise NotImplementedError("Hardware type not supported.")
Expand All @@ -253,6 +255,69 @@ def _capnp_to_vector3(vector: robot_daemon_protocol_capnp.Vector3) -> Vector3:
return Vector3([vector.x, vector.y, vector.z])


def _capnp_to_camera_view(
image: robot_daemon_protocol_capnp.Image, camera_size: tuple[int, int]
) -> NDArray[np.uint8]:
"""
Convert a capnp compatible Image into an NDArray.

:param image: The capnp Image.
:param camera_size: The camera size to reconstruct the image.
:return: The NDArray imag.
"""
np_image = np.zeros(shape=(3, *camera_size), dtype=np.uint8)
np_image[0] = np.array(image.r).reshape(camera_size).astype(np.uint8)
np_image[1] = np.array(image.g).reshape(camera_size).astype(np.uint8)
np_image[2] = np.array(image.b).reshape(camera_size).astype(np.uint8)
return np_image


def _get_imu_sensor_state(
imu_sensor: IMUSensor | None,
sensor_readings: robot_daemon_protocol_capnp.SensorReadings,
) -> dict[UUIDKey[IMUSensor], IMUSensorStateImpl]:
"""
Get the IMU sensor state.

:param imu_sensor: The sensor in question.
:param sensor_readings: The sensor readings.
:return: The Sensor state.
"""
if imu_sensor is None:
return {}
else:
return {
UUIDKey(imu_sensor): IMUSensorStateImpl(
_capnp_to_vector3(sensor_readings.imuSpecificForce),
_capnp_to_vector3(sensor_readings.imuAngularRate),
_capnp_to_vector3(sensor_readings.imuOrientation),
)
}


def _get_camera_sensor_state(
camera_sensor: CameraSensor | None,
sensor_readings: robot_daemon_protocol_capnp.SensorReadings,
) -> dict[UUIDKey[CameraSensor], CameraSensorStateImpl]:
"""
Get the camera sensor state.

:param camera_sensor: The sensor in question.
:param sensor_readings: The sensor readings.
:return: The Sensor state.
"""
if camera_sensor is None:
return {}
else:
return {
UUIDKey(camera_sensor): CameraSensorStateImpl(
_capnp_to_camera_view(
sensor_readings.cameraView, camera_sensor.camera_size
)
)
}


def run_remote(
config: Config,
hostname: str,
Expand Down
Loading
Loading