Ashwinvk/leapp observation term inputs#6081
Conversation
df5e2eb to
4fc0d6e
Compare
Greptile SummaryThis PR introduces
Confidence Score: 3/5Two concrete defects need to be fixed before merging: one causes a NameError crash in the RSL-RL recurrent-state export path, and one silently omits observation post-processing from the exported LEAPP graph. The
Important Files Changed
Sequence DiagramsequenceDiagram
participant OA as ExportPatcher
participant OM as ObservationManager
participant WF as wrapped()
participant AN as annotate
Note over OA: Export time (_patch_observation_manager)
OA->>OA: resolve_leapp_observation_input_semantics(term_cfg.func)
OA->>OA: _wrap_observation_input(original_func, real_env, term_cfg)
Note right of OA: term_cfg captured by reference in wrapped()
OA->>OA: "term_cfg.modifiers = None"
OA->>OA: "term_cfg.clip = None"
OA->>OA: "term_cfg.scale = None"
Note over OM,AN: Validation pass (obs_manager.compute called)
OM->>WF: "term_cfg.func(proxy_env, **params)"
WF->>WF: "original_func(real_env, **kwargs)"
WF->>WF: _apply_observation_post_processing(result, term_cfg)
Note right of WF: term_cfg.modifiers/clip/scale are None - no-op
WF->>AN: annotate.input_tensors(task_name, sem)
AN-->>OM: annotated tensor
Note over OA: Deployment time (_compute_observation_input)
OA->>OM: observation_manager._group_obs_term_cfgs[group][idx]
OA->>OA: "term_cfg.func(self, **params)"
OA->>OA: apply modifiers / clip / scale
OA-->>OA: return obs tensor
|
| if observation_input_semantics is not None: | ||
| term_cfg.func = self._wrap_observation_input( | ||
| original_func, | ||
| real_env, | ||
| group_name, | ||
| term_name, | ||
| observation_input_semantics, | ||
| term_cfg, | ||
| ) | ||
| term_cfg.modifiers = None | ||
| term_cfg.clip = None | ||
| term_cfg.scale = None |
There was a problem hiding this comment.
Post-processing silently dropped during export
_wrap_observation_input receives term_cfg by reference and the wrapped closure captures it. Immediately after the call, lines 308–310 null out term_cfg.modifiers, term_cfg.clip, and term_cfg.scale on the same object. When wrapped later calls _apply_observation_post_processing(result, term_cfg), all three fields are already None, so modifiers, clipping, and scaling are never applied. This contradicts the documented guarantee that "deterministic post-processing such as modifiers, clipping, and scaling" is included in the LEAPP input boundary. The unit test in test_leapp_observation_input_wrapper_annotates_processed_term does not catch this because it calls _wrap_observation_input directly without the post-mutation performed here.
| elif conn_type == "observation": | ||
| group_name, term_name = parts[1], parts[2] |
There was a problem hiding this comment.
IndexError on malformed observation: connection strings
parts[1] and parts[2] are accessed without a length guard. A YAML connection value of "observation:policy" (only two colon-separated segments) would raise IndexError at this line. The state: and write: branches above share the same pattern, but adding an explicit len(parts) != 3 check here with a descriptive ValueError would make misconfigured YAMLs much easier to diagnose.
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot — PR #6081
Summary: This PR adds LEAPP observation-term input boundaries, allowing selected policy observations to be exported as runtime inputs using observation:{group}:{term} metadata. The design is clean and well-integrated with the existing LEAPP framework.
CI Status: pre-commit and changelog fragments checks are failing. These should be addressed before merge.
Findings
🟡 Warning — No guard for malformed observation: connection strings (leapp_deployment_env.py)
The parsing group_name, term_name = parts[1], parts[2] will raise an unguarded IndexError if the YAML contains a malformed connection like "observation:foo" (missing term component). Consider adding a parts-length check with a descriptive error message.
🟡 Warning — _compute_observation_input accesses private _group_obs_term_names/_group_obs_term_cfgs (leapp_deployment_env.py)
The deployment env reaches into observation_manager._group_obs_term_names and ._group_obs_term_cfgs (private attributes). If the ObservationManager internal layout changes, this will break silently. The validation uses the public .active_terms API but the actual computation bypasses it. Consider whether a targeted public method on ObservationManager (e.g. compute_term(group, name)) would be more maintainable.
🔵 Suggestion — _compute_observation_input duplicates post-processing logic (leapp_deployment_env.py)
The modifier/clip/scale post-processing in _compute_observation_input is identical to ExportPatcher._apply_observation_post_processing. Consider extracting a shared utility to avoid divergence if post-processing logic evolves (e.g., new transform types are added).
🔵 Suggestion — Document that leapp_observation_input disables noise at export (export_annotator.py)
When observation_input_semantics is detected, term_cfg.modifiers, term_cfg.clip, and term_cfg.scale are set to None on the term config—but the wrapper applies them internally before registering the LEAPP input. This is correct but subtle. A brief inline comment noting that post-processing moves inside the wrapper (rather than being dropped) would help future readers.
🔵 Suggestion — Test coverage for LeappDeploymentEnv observation input path
The unit tests thoroughly cover the export/annotation side (test_leapp_observation_input.py), but there's no test for _compute_observation_input in LeappDeploymentEnv. A test verifying that the deployment env correctly computes observation inputs from a mock YAML with observation: connections would catch regressions in the deployment path.
Verdict: Well-designed feature that fills a real gap for deployment workflows. The architecture cleanly separates export-time annotation from deployment-time computation. Main concerns are the fragile private-attribute access in the deployment env and the lack of input validation on malformed connection strings. Overall quality is high—good docs, good tests on the export side, and proper error messages for misconfiguration.
| "CommandManager is available (cfg.commands is None)." | ||
| ) | ||
| self._input_mapping[key] = CommandInputSpec(command_term_name=command_name) | ||
| elif conn_type == "observation": |
There was a problem hiding this comment.
🟡 Warning: If a LEAPP YAML contains a malformed connection like "observation:foo" (missing the term component), parts[2] raises an unguarded IndexError. Consider adding validation:
if len(parts) < 3:
raise ValueError(
f"Malformed observation connection '{connection}' for input '{key}': "
"expected format 'observation:<group>:<term>'."
)| def _compute_observation_input(self, spec: ObservationInputSpec) -> torch.Tensor: | ||
| """Compute one named observation term without injecting observation noise.""" | ||
| observation_manager = self.observation_manager | ||
| assert observation_manager is not None |
There was a problem hiding this comment.
🟡 Warning: This accesses private attributes _group_obs_term_names and _group_obs_term_cfgs. The validation above uses the public .active_terms API, but the actual computation bypasses it. If ObservationManager internals change, this will break without a clear error. Consider whether a targeted public method (e.g., compute_term(group, name, noise=False)) would be more maintainable long-term.
Description
Adds LEAPP observation-term input boundaries so selected policy observations can be exported as runtime inputs using
observation:{group}:{term}metadata. This supports workflows like gear assembly where deployment provides final shaft pose observations directly, instead of exposing simulator-only bookkeeping.Also updates LEAPP deployment env support, marks gear shaft pose observations, adds docs, and adds focused unit tests.
Fixes # N/A
Type of change
Screenshots
N/A
Checklist
./isaaclab.sh --formatCONTRIBUTORS.mdor my name already exists there