| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """Action interpolation for smoother robot control. |
| |
| Provides configurable Nx control rate by interpolating between consecutive actions. |
| Useful with RTC and action-chunking policies to reduce jerkiness. |
| """ |
|
|
| from torch import Tensor |
|
|
|
|
| class ActionInterpolator: |
| """Interpolates between consecutive actions for smoother control. |
| |
| When enabled with multiplier N, produces N actions per policy action |
| by linearly interpolating between the previous and current action. |
| |
| Example with multiplier=3: |
| prev_action -> [1/3 interpolated, 2/3 interpolated, current_action] |
| |
| This effectively multiplies the control rate for smoother motion. |
| |
| Usage: |
| interpolator = ActionInterpolator(multiplier=2) # 2x control rate |
| |
| # In control loop: |
| if interpolator.needs_new_action(): |
| new_action = queue.get() |
| if new_action: |
| interpolator.add(new_action.cpu()) |
| |
| action = interpolator.get() |
| if action: |
| robot.send_action(action) |
| """ |
|
|
| def __init__(self, multiplier: int = 1): |
| """Initialize the interpolator. |
| |
| Args: |
| multiplier: Control rate multiplier (1 = no interpolation, 2 = 2x, 3 = 3x, etc.) |
| """ |
| if multiplier < 1: |
| raise ValueError(f"multiplier must be >= 1, got {multiplier}") |
| self.multiplier = multiplier |
| self._prev: Tensor | None = None |
| self._buffer: list[Tensor] = [] |
| self._idx = 0 |
|
|
| @property |
| def enabled(self) -> bool: |
| """Whether interpolation is active (multiplier > 1).""" |
| return self.multiplier > 1 |
|
|
| def reset(self): |
| """Reset interpolation state (call between episodes).""" |
| self._prev = None |
| self._buffer = [] |
| self._idx = 0 |
|
|
| def needs_new_action(self) -> bool: |
| """Check if a new action is needed from the queue.""" |
| return self._idx >= len(self._buffer) |
|
|
| def add(self, action: Tensor) -> None: |
| """Add a new action and compute interpolated sequence. |
| |
| Args: |
| action: New action tensor from policy/queue (already on CPU). |
| """ |
| if self.multiplier > 1 and self._prev is not None: |
| self._buffer = [] |
| for i in range(1, self.multiplier + 1): |
| t = i / self.multiplier |
| interp = self._prev + t * (action - self._prev) |
| self._buffer.append(interp) |
| else: |
| |
| self._buffer = [action.clone()] |
| self._prev = action.clone() |
| self._idx = 0 |
|
|
| def get(self) -> Tensor | None: |
| """Get the next interpolated action. |
| |
| Returns: |
| Next action tensor, or None if buffer is exhausted. |
| """ |
| if self._idx >= len(self._buffer): |
| return None |
| action = self._buffer[self._idx] |
| self._idx += 1 |
| return action |
|
|
| def get_control_interval(self, fps: float) -> float: |
| """Get the control interval based on interpolation multiplier. |
| |
| Args: |
| fps: Base frames per second. |
| |
| Returns: |
| Control interval in seconds (divided by multiplier). |
| """ |
| return 1.0 / (fps * self.multiplier) |
|
|