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

# 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 configuration.

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

from dataclasses import dataclass
from typing import List, Mapping, Optional

import yaml
from importlib_resources import files

from tbp.monty.simulators.resources import tacto

__all__ = [
    "TactoSensorSpec",
    "DIGIT",
    "OMNITACT",
]


@dataclass
class Gel:
    """Sensor gel specification.

    Attributes:
        origin: Center coordinate of the gel, in meters
        width: Width of the gel, y-axis, in meters
        height: Height of the gel, z-axis, in meters
        curvature: Whether or not to model the gel as curve
        curvatureMax: Deformation of the gel due to convexity
        R: Radius of curved gel
        countW: Number of samples for horizontal direction
        mesh: Gel mesh
    """

    origin: List[float]
    width: float
    height: float
    curvature: bool
    curvatureMax: Optional[float] = 0.005  # noqa: N815 - Original YAML name
    R: Optional[float] = 0.1
    countW: Optional[int] = 100  # noqa: N815 - Original YAML name
    mesh: Optional[str] = None


@dataclass
class Camera:
    """Sensor camera specification.

    Attributes:
        position: Camera position
        orientation: Euler angles, "xyz", in degrees
        yfov: Vertical field of view in degrees
        znear: Distance to the near clipping plane, in meters
        lightIDList: Select light ID list for rendering
    """

    position: List[float]
    orientation: List[float]
    yfov: float
    znear: float
    lightIDList: List[int]  # noqa: N815 - Original YAML name


@dataclass
class Lights:
    """Sensor lights specification.

    Attributes:
        origin: center of the light plane, in meters
        spot: Whether or not to use spot light
        polar: Whether or not to use polar coordinates
        coords: Cartesian coordinates (polar=False)
        xs: x coordinate of the y-z plane (polar=True)
        rs: r in polar coordinates (polar=True)
        thetas: theta in polar coordinates (polar=True)
        colors: List of RGB colors
        intensities: List of light intensities
    """

    origin: List[float]
    polar: bool
    colors: List[float]
    intensities: List[float]
    spot: Optional[bool] = False
    shadow: Optional[bool] = False
    coords: Optional[List[float]] = None
    xs: Optional[List[float]] = None
    rs: Optional[List[float]] = None
    thetas: Optional[List[float]] = None

    def __post_init__(self):
        if self.spot:
            raise NotImplementedError("Habitat does not support spot lights")


@dataclass
class Noise:
    """Gaussian noise calibrated on the output color.

    Attributes:
        mean: Noise mean [0-255]
        std: Noise std [0-255]
    """

    mean: int
    std: int


@dataclass
class Force:
    """Sensor force configuration.

    Attributes:
        enable: Whether or not to enable force feedback
        range_force: Dynamic range of forces used to simulate the elastometer
            deformations [0-100]
        max_deformation: Max pose depth adjustment, in meters
    """

    enable: bool
    range_force: List[int]
    max_deformation: float


[docs]@dataclass class TactoSensorSpec: """Tacto sensor specifications. Attributes: name: Sensor name ('omnitact' or 'digit') camera: Camera specifications. One camera for DIGIT, five for OmniTact gel: Sensor elastomer gel configuration lights: Sensor LED light configuration noise: Gausian noise calibration force: Elastomer force feedback specification .. _Tacto: https://github.com/facebookresearch/tacto """ name: str camera: Mapping[str, Camera] gel: Gel lights: Lights noise: Optional[Noise] = None force: Optional[Force] = None def __post_init__(self): for k, v in self.camera.items(): if isinstance(v, dict): self.camera[k] = Camera(**v) if isinstance(self.gel, dict): self.gel = Gel(**self.gel) if isinstance(self.lights, dict): self.lights = Lights(**self.lights) if isinstance(self.noise, dict): self.noise = Noise(**self.noise) if isinstance(self.force, dict): self.force = Force(**self.force)
[docs] @classmethod def from_yaml(cls, yaml_file): with open(yaml_file, "r") as f: config = yaml.safe_load(f)["sensor"] return cls(**config)
# Digit sensor configuration. See https://arxiv.org/abs/2005.14679 DIGIT = TactoSensorSpec.from_yaml(str(files(tacto) / "config_digit.yml")) # Omnitact sensor configuration. See https://arxiv.org/pdf/2003.06965.pdf OMNITACT = TactoSensorSpec.from_yaml(str(files(tacto) / "config_omnitact.yml"))