Source code for tbp.monty.simulators.tacto.sensors

# Copyright 2025 Thousand Brains Project
# Copyright 2022-2024 Numenta Inc.
#
# Copyright may exist in Contributors' modifications
# and/or contributions to the work.
#
# Use of this source code is governed by the MIT
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.

"""Tacto touch sensor implementation in habitat-sim.

See Also:
    https://github.com/facebookresearch/tacto
"""

from dataclasses import dataclass, field
from typing import Dict, List

import numpy as np
from habitat_sim.sensor import CameraSensorSpec, SensorSpec, SensorType

from tbp.monty.simulators.habitat import SensorConfig
from tbp.monty.simulators.tacto import DIGIT, TactoSensorSpec

__all__ = ["TactoSensor"]


[docs]@dataclass(frozen=True) class TactoSensor(SensorConfig): """Base class common for all tacto sensors. Each specific sensor implementation should inherit this class and pass the appropriated configuration file ('digit' or 'omnitact') Attributes: sensor_id: Sensor ID unique within the sensor module. The observations made by this sensor will be prefixed by this id. i.e. "`sensor_id`.cam0" resolution: Camera resolution (width, height). Default (32, 48) position: Sensor position relative to :class:`HabitatAgent`. Default (0, 0, 0) rotation: Sensor rotation quaternion. Default (1, 0, 0, 0) config: Tacto Sensor specification (DIGIT, OMNITACT) """ resolution: List[float] = field(default_factory=lambda: [32.0, 48.0]) config: TactoSensorSpec = DIGIT _depths: Dict = field(default_factory=dict, init=False) def __post_init__(self): # Curved gel surface gel = self.config.gel x0, y0, z0 = gel.origin n = self.resolution[0] m = self.resolution[1] r = gel.R x_max = gel.curvatureMax # Create curvature depth map y = np.linspace(y0 - gel.width / 2, y0 + gel.width / 2, n) z = np.linspace(z0 - gel.height / 2, z0 + gel.height / 2, m) yy, zz = np.meshgrid(y, z) hh = r - np.maximum(0, r**2 - (yy - y0) ** 2 - (zz - z0) ** 2) ** 0.5 depth = x0 - x_max * hh / hh.max() # Save curvature depth map for name in self.config.camera: self._depths[name] = depth
[docs] def get_specs(self) -> List[SensorSpec]: # rotation = Rotation.from_quat(self.rotation) # origin = np.array(self.position) specs = [] for name, config in self.config.camera.items(): # Create habitat depth camera based on tacto camera specs camera = CameraSensorSpec() camera.uuid = f"{self.sensor_id}.{name}" camera.sensor_type = SensorType.DEPTH camera.channels = 1 camera.near = config.znear camera.hfov = config.yfov camera.resolution = self.resolution camera.position = self.position specs.append(camera) return specs
[docs] def process_observations(self, sensor_obs): for name in sensor_obs: if name in self._depths: # Get gel curvature depth map depth0 = self._depths[name] obs = np.clip(sensor_obs[name], 0.0, depth0) sensor_obs[name] = obs return sensor_obs