[Bug Report] Observation ModifierCfg func is not resolved from ResolvableString before signature validation
Descri
When using a class-based observation modifier through ModifierCfg, environment creation fails if the config has gone through Hydra / from_dict() and the modifier func is represented as a callable string / ResolvableString.
The failure happens in ObservationManager._prepare_terms() when validating modifier parameters. For regular observation term functions, Isaac Lab resolves string callables via string_to_callable, but modifier functions do not appear to receive the same resolution before inspect.signature(mod_cfg.func) is called.
As a result, inspect.signature() sees ResolvableString.__call__(*args, **kwargs) instead of the modifier class or modifier instance signature. This makes the manager believe the modifier expects a mandatory parameter named kwargs.
Observed error:
ValueError: Modifier 'ModifierCfg(func='{repo}.mdp.observation:DelayedObsModifier', params={'min_delay': 1, 'max_delay': 3})' of observation term 'joint_pos' expects mandatory parameters: ['kwargs'] and optional parameters: [], but received: ['min_delay', 'max_delay'].
Steps to reproduce
Use a class-based observation modifier in an observation term:
from collections.abc import Sequence
import torch
from isaaclab.utils.buffers import DelayBuffer
from isaaclab.utils.modifiers import ModifierBase, ModifierCfg
class DelayedObsModifier(ModifierBase):
def __init__(self, cfg: ModifierCfg, data_dim: tuple[int, ...], device: str) -> None:
super().__init__(cfg, data_dim, device)
self.max_delay = cfg.params.get("max_delay", 0)
self.min_delay = cfg.params.get("min_delay", 0)
self.delay_buffer = DelayBuffer(
history_length=self.max_delay,
batch_size=data_dim[0],
device=device,
)
def reset(self, env_ids: Sequence[int] | None = None):
self.delay_buffer.reset(env_ids)
def __call__(self, data: torch.Tensor) -> torch.Tensor:
return self.delay_buffer.compute(data)
Configure an observation term with the modifier:
joint_pos = ObsTerm(
func=mdp.joint_pos,
modifiers=[
ModifierCfg(
func=DelayedObsModifier,
params={"min_delay": 1, "max_delay": 3},
)
],
)
Run the task through the Hydra training flow, so that the environment config is serialized and restored through env_cfg.from_dict(hydra_cfg["env"]).
During environment creation, the modifier config becomes similar to:
ModifierCfg(
func='my_package.my_module:DelayedObsModifier',
params={'min_delay': 1, 'max_delay': 3},
)
Then ObservationManager fails during _prepare_terms() with:
ValueError: Modifier 'ModifierCfg(func='my_package.my_module:DelayedObsModifier', params={'min_delay': 1, 'max_delay': 3})' of observation term 'joint_pos' expects mandatory parameters: ['kwargs'] and optional parameters: [], but received: ['min_delay', 'max_delay'].
A minimal root-cause check:
import inspect
from isaaclab.utils.modifiers import ModifierCfg
from isaaclab.utils.string import ResolvableString
cfg = ModifierCfg(
func=ResolvableString("my_package.my_module:DelayedObsModifier"),
params={"min_delay": 1, "max_delay": 3},
)
print(inspect.signature(cfg.func))
# Actual: (*args, **kwargs)
Expected behavior: mod_cfg.func should be resolved to the actual modifier class before class checks and signature validation. The class-based modifier should then be instantiated, and its call signature should be (data: torch.Tensor) -> torch.Tensor.
System Info
- tag:
v3.0.0-beta
- Isaac Sim Version: 6.0
- OS: Ubuntu 24.04
- GPU: RTX 5060 mobile
- CUDA: 12.8
- GPU Driver: 595.71.05
Additional context
The issue seems to be caused by missing callable-string resolution for observation modifiers.
In isaaclab/managers/manager_base.py, normal manager term functions are resolved when term_cfg.func is a string. However, in isaaclab/managers/observation_manager.py, modifier functions are checked with inspect.isclass(mod_cfg.func) and later inspect.signature(mod_cfg.func) without first resolving string / ResolvableString callables.
A potential fix would be to resolve mod_cfg.func before the class check in ObservationManager._prepare_terms():
from isaaclab.utils import string_to_callable
...
if isinstance(mod_cfg.func, str):
mod_cfg.func = string_to_callable(str(mod_cfg.func))
if inspect.isclass(mod_cfg.func):
...
This would make class-based modifiers behave consistently whether the config is used directly or after Hydra / from_dict() processing.
Checklist
Acceptance Criteria
[Bug Report] Observation ModifierCfg func is not resolved from ResolvableString before signature validation
Descri
When using a class-based observation modifier through
ModifierCfg, environment creation fails if the config has gone through Hydra /from_dict()and the modifierfuncis represented as a callable string /ResolvableString.The failure happens in
ObservationManager._prepare_terms()when validating modifier parameters. For regular observation term functions, Isaac Lab resolves string callables viastring_to_callable, but modifier functions do not appear to receive the same resolution beforeinspect.signature(mod_cfg.func)is called.As a result,
inspect.signature()seesResolvableString.__call__(*args, **kwargs)instead of the modifier class or modifier instance signature. This makes the manager believe the modifier expects a mandatory parameter namedkwargs.Observed error:
Steps to reproduce
Use a class-based observation modifier in an observation term:
Configure an observation term with the modifier:
Run the task through the Hydra training flow, so that the environment config is serialized and restored through
env_cfg.from_dict(hydra_cfg["env"]).During environment creation, the modifier config becomes similar to:
Then
ObservationManagerfails during_prepare_terms()with:A minimal root-cause check:
Expected behavior:
mod_cfg.funcshould be resolved to the actual modifier class before class checks and signature validation. The class-based modifier should then be instantiated, and its call signature should be(data: torch.Tensor) -> torch.Tensor.System Info
v3.0.0-betaAdditional context
The issue seems to be caused by missing callable-string resolution for observation modifiers.
In
isaaclab/managers/manager_base.py, normal manager term functions are resolved whenterm_cfg.funcis a string. However, inisaaclab/managers/observation_manager.py, modifier functions are checked withinspect.isclass(mod_cfg.func)and laterinspect.signature(mod_cfg.func)without first resolving string /ResolvableStringcallables.A potential fix would be to resolve
mod_cfg.funcbefore the class check inObservationManager._prepare_terms():This would make class-based modifiers behave consistently whether the config is used directly or after Hydra /
from_dict()processing.Checklist
Acceptance Criteria
ModifierCfg.funcfor observation modifiers is resolved from callable strings /ResolvableStringbefore class checks and signature validation.from_dict().