Source code for blueprints.sensors

import blueprints as blue
import xml.etree.ElementTree as xml
import numpy as np
import mujoco



[docs] class BaseSensor(blue.SensorType, blue.thing.BaseThing): """ Sensors can be used to as an interface for the agent to the World. Sensors that are part of the kinematic tree of an Agent are included in the Agents observations. Most attribute descriptions are partially taken from `Mujoco <https://mujoco.readthedocs.io/en/latest/XMLreference.html#sensor>`__. """
[docs] @blue.restrict def __init__(self, noise: float|int = 0., cutoff: float|int = 0., name: str|None = None, reference: blue.ThingType|None = None, **kwargs): """ Parameters ---------- noise : float | int, optional The standard deviation of zero-mean Gaussian noise added to the sensor output. cutoff : float | int, optional When this value is positive, it limits the absolute value of the sensor output. name : str | None, optional The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme. reference : blue.ThingType | None, optional The reference of the Sensor attribute is the parent of the Sensor which is translated to the :attr:`site <SiteSensor.site>`, :attr:`joint <JointSensor.joint>` or :attr:`actuator <ActuatorSensor.actuator>` attributes for xml. **kwargs Keyword arguments are passed to ``super().__init__``. Raises ------ TypeError If the reference type is not valid an error is raised. """ if not isinstance(reference, tuple(self._REFERENCE_TYPES)) and reference is not None: raise TypeError(f'The Sensor reference must be of the types ({", ".join(map(str, self._REFERENCE_TYPES))}), got ({type(reference)}) instead.') self.noise = noise self.cutoff = cutoff super().__init__(name=name, **kwargs) self.reference = reference
@property def observation(self) -> np.ndarray: """ The live sensor data from the simulation. Returns ------- np.ndarray """ if not hasattr(self, '_index'): raise Exception('Sensor must first be build by a World before observations are available.') else: return self.root._mj_data.sensordata[self._index:self._index + self.DIMENSIONS].copy() @blue.restrict def _build(self, parent, world: blue.WorldType, indicies: dict, **kwargs): """ This method is called to build the xml. Parameters ---------- parent : xml.etree.ElementTree.Element The xml element of its parent world : WorldType The World from which the build method was called initially Returns ------- xml.etree.ElementTree.Element The builded xml element of the Sensor. """ self._index = indicies['sensors'] indicies['sensors'] += self.DIMENSIONS self._xml_root = xml.SubElement(world._xml_sensor, self.type, **self._mujoco_specs(kwargs)) return self._xml_root @blue.restrict @classmethod def _from_xml_element(cls, xml_element: xml.Element) -> blue.ThingType: """ This method reconstructs a Thing from an xml element. If any argument for an inheriting class has to be set manually this method must be overwritten. Parameters ---------- xml_element : xml.Element The xml element from which a Thing is reconstructed. Returns ------- blue.ThingType The reconstructed Sensor. """ init_args, post_args, rest_args = cls._xml_element_args(xml_element) sensor_type = xml_element.tag sensor = object.__new__(blue.REGISTER.SENSOR_THINGS[sensor_type]) sensor.__init__(**init_args) for key, val in post_args.items(): setattr(sensor, key, val) return sensor @property def reference(self): """ The reference of the Sensor attribute is the parent of the Sensor which is translated to the :attr:`site <SiteSensor.site>`, :attr:`joint <JointSensor.joint>` or :attr:`actuator <ActuatorSensor.actuator>` attributes for xml. Returns ------- blue.ThingType The sensors refferenced Thing """ return self.parent @reference.setter @blue.restrict def reference(self, reference: blue.NodeThingType|None): """ Parameters ---------- reference : blue.NodeThingType | None The reference of the Sensor attribute is the parent of the Sensor which is translated to the :attr:`site <SiteSensor.site>`, :attr:`joint <JointSensor.joint>` or :attr:`actuator <ActuatorSensor.actuator>` attributes for xml. """ if self.parent is not None: self.parent.detach(self) if reference is not None: reference.attach(self, copy=False) # MUJOCO PROPERTIES @property def type(self): """ This is a derived attribute, which is used to specify the ``type`` attribute in the mujoco ``sensor`` tag. Returns ------- str The type is the lower case name of the Sensor class. """ return self.__class__.__name__.lower() @property def noise(self) -> float: """ The standard deviation of zero-mean Gaussian noise added to the sensor output. Returns ------- float """ return self._noise @noise.setter @blue.restrict def noise(self, noise: float|int) -> None: """ Parameters ---------- noise : float | int The standard deviation of zero-mean Gaussian noise added to the sensor output. """ self._noise = float(noise) @property def cutoff(self) -> float: """ When this value is positive, it limits the absolute value of the sensor output. Returns ------- float """ return self._cutoff @cutoff.setter @blue.restrict def cutoff(self, cutoff: float|int) -> None: """ Parameters ---------- cutoff : float | int When this value is positive, it limits the absolute value of the sensor output. """ self._cutoff = float(cutoff)
# INTERMEDIATE SENSORS
[docs] class SiteSensor(blue.SiteSensorType, BaseSensor): """ Intermediate Sensor class used by Sensors that reference :class:`Sites <blueprints.sites.BaseSite>`. """ @property def site(self): """ The parent name of the Sensor which is translated to this attribute for xml construction. Returns ------- blue.ThingType """ if self.reference: return self.reference.name else: return None
[docs] class JointSensor(blue.JointSensorType, BaseSensor): """ Intermediate Sensor class used by Sensors that reference :class:`Joints <blueprints.joints.BaseJoint>`. """ @property def joint(self): """ The parent name of the Sensor which is translated to this attribute for xml construction. Returns ------- blue.ThingType """ if self.reference: return self.reference.name else: return None
[docs] class ActuatorSensor(blue.ActuatorSensorType, BaseSensor): """ Intermediate Sensor class used by Sensors that reference :class:`Actuators <blueprints.actuators.BaseActuator>`. """ @property def actuator(self): """ The parent name of the Sensor which is translated to this attribute for xml construction. Returns ------- blue.ThingType """ if self.reference: return self.reference.name else: return None
# INFO LASER
[docs] class InfoLaser(blue.InfoLaserType, SiteSensor): """ The InfoLaser is not a mujoco native object but a derived blueprints object, that will not appear in xml. Instead it uses the mujoco.mj_ray subroutine to cast a ray from the Sensors Site and returns the intersected object and the distance to it. """
[docs] @blue.restrict def __init__(self, axis: np.ndarray|list[int|float] = [0., 0., 1.], noise: float|int = 0., cutoff: float|int = 0., name: str|None = None, reference: blue.ThingType|None = None, **kwargs): """ Parameters ---------- axis : np.ndarray The local axis along which the InfoLasers ray will be casted. noise : float | int, optional The standard deviation of zero-mean Gaussian noise added to the sensor output. cutoff : float | int, optional When this value is positive, it limits the absolute value of the sensor output. name : str | None, optional The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme. reference : blue.ThingType | None, optional The reference of the Sensor attribute is the parent of the Sensor which is translated to the :attr:`site <SiteSensor.site>`, :attr:`joint <JointSensor.joint>` or :attr:`actuator <ActuatorSensor.actuator>` attributes for xml. **kwargs Keyword arguments are passed to ``super().__init__``. Raises ------ TypeError If the reference type is not valid an error is raised. """ self.axis = axis super().__init__(noise=noise, cutoff=cutoff, name=name, reference=reference, **kwargs)
@property def observation(self) -> dict: """ Computes the observation on runtime. """ if not self.root._launched: raise Exception('Sensor must first be build by a World before observations are available.') else: R = self.parent.global_rotation_matrix ID = np.array([-1], np.int32) distance = mujoco.mj_ray(m=self.root._mj_model, d=self.root._mj_data, pnt=self.parent.global_pos, vec=R @ self.axis, geomgroup=None, flg_static=1, bodyexclude=-1, geomid=ID) if distance == -1: geom = None else: geom = self.root.descendants['geoms']['descendants'][ID[0]] return {'distance': distance, 'geom': geom} @property def axis(self) -> np.ndarray: """ The local axis along which the InfoLasers ray will be casted. Returns ------- np.ndarray """ return self._axis @axis.setter @blue.restrict def axis(self, axis: np.ndarray|list[int|float]) -> None: """ Parameters ------- axis : np.ndarray The local axis along which the InfoLasers ray will be casted. """ self._axis = np.array(axis, dtype=np.float32) @blue.restrict def _build(self, parent, world, indicies, **kwargs): """ InfoLasers are not part of mujoco xml. Parameters ---------- parent : xml.etree.ElementTree.Element The parent of the Thing world : blue.WorldType The World from which the initial _meth:`blueprints.world.World.build` method was called. """ pass
# SITE SENSORS
[docs] class Touch(SiteSensor): DIMENSIONS = 1
[docs] class Accelerometer(SiteSensor): DIMENSIONS = 3
[docs] class Velocimeter(SiteSensor): DIMENSIONS = 3
[docs] class Gyro(SiteSensor): DIMENSIONS = 3
[docs] class Force(SiteSensor): DIMENSIONS = 3
[docs] class Torque(SiteSensor): DIMENSIONS = 3
[docs] class Rangefinder(SiteSensor): DIMENSIONS = 1
# JOINT SENSORS
[docs] class JointPos(JointSensor): DIMENSIONS = 1
[docs] class JointVel(JointSensor): DIMENSIONS = 1
[docs] class JointLimitPos(JointSensor): DIMENSIONS = 1
[docs] class JointLimitVel(JointSensor): DIMENSIONS = 1
[docs] class JointLimitFrc(JointSensor): DIMENSIONS = 1
[docs] class BallQuat(JointSensor): DIMENSIONS = 4
[docs] class BallAngVel(JointSensor): DIMENSIONS = 3
# ACTUATOR SENSOR
[docs] class ActuatorPos(ActuatorSensor): DIMENSIONS = 1
[docs] class ActuatorVel(ActuatorSensor): DIMENSIONS = 1
[docs] class ActuatorFrc(ActuatorSensor): DIMENSIONS = 1
SENSOR_THINGS = {'touch': Touch, 'accelerometer': Accelerometer, 'velocimeter': Velocimeter, 'gyro': Gyro, 'force': Force, 'torque': Torque, 'rangefinder': Rangefinder, 'jointpos': JointPos, 'jointvel': JointVel, 'jointlimitpos': JointLimitPos, 'jointlimitvel': JointLimitVel, 'jointlimitfrc': JointLimitFrc, 'ballquat': BallQuat, 'ballangvel': BallAngVel, 'actuatorpos': ActuatorPos, 'actuatorvel': ActuatorVel, 'actuatorfrc': ActuatorFrc}