| from collections.abc import Sequence |
| import logging |
|
|
| import torch |
|
|
| from openpi_value.shared import image_tools |
| import openpi_value.transforms as _transforms |
| import kornia.augmentation as K |
|
|
| logger = logging.getLogger("openpi") |
|
|
| |
|
|
|
|
| IMAGE_RESOLUTION = (224, 224) |
|
|
|
|
| def preprocess_observation_pytorch( |
| observation, |
| *, |
| train: bool = False, |
| |
| image_keys: Sequence[str] = None, |
| image_resolution: tuple[int, int] = IMAGE_RESOLUTION, |
| return_full_obs: bool = False, |
| apply_shape_visual_aug: bool = False, |
| apply_blur_visual_aug: bool = False, |
| p_mask_base: float = 0.0, |
| state_noise_snr: float | None = None, |
| ): |
| """Torch.compile-compatible version of preprocess_observation_pytorch with simplified type annotations. |
| |
| This function avoids complex type annotations that can cause torch.compile issues. |
| """ |
|
|
| assert image_keys is None, "Deprecated: cannot use image_key anymore" |
| |
| |
|
|
| batch_shape = observation.state.shape[:-1] |
|
|
| image_keys = list(observation.images.keys()) |
|
|
| part_order = {'base': 0, 'left_wrist': 1, 'right_wrist': 2} |
| def simple_sort_key(k): |
| part, timestep_str, _ = k.rsplit('_', 2) |
| timestep = int(timestep_str) |
| return (timestep, part_order[part]) |
| image_keys = sorted(image_keys, key=simple_sort_key) |
|
|
| out_images = {} |
|
|
| for key in image_keys: |
| image = observation.images[key] |
|
|
| |
| is_channels_first = image.shape[1] == 3 |
|
|
| if is_channels_first: |
| |
| image = image.permute(0, 2, 3, 1) |
|
|
| if image.shape[1:3] != image_resolution: |
| logger.info(f"Resizing image {key} from {image.shape[1:3]} to {image_resolution}") |
| image = image_tools.resize_with_pad_torch(image, *image_resolution) |
|
|
| if train: |
| |
| image = image / 2.0 + 0.5 |
| |
| |
| if "wrist" not in key and apply_shape_visual_aug: |
| |
| height, width = image.shape[1:3] |
|
|
| |
| crop_height = int(height * 0.95) |
| crop_width = int(width * 0.95) |
|
|
| |
| max_h = height - crop_height |
| max_w = width - crop_width |
| if max_h > 0 and max_w > 0: |
| |
| start_h = torch.randint(0, max_h + 1, (1,), device=image.device) |
| start_w = torch.randint(0, max_w + 1, (1,), device=image.device) |
| image = image[:, start_h : start_h + crop_height, start_w : start_w + crop_width, :] |
|
|
| |
| image = torch.nn.functional.interpolate( |
| image.permute(0, 3, 1, 2), |
| size=(height, width), |
| mode="bilinear", |
| align_corners=False, |
| ).permute(0, 2, 3, 1) |
|
|
| |
| |
| angle = torch.rand(1, device=image.device) * 10 - 5 |
| if torch.abs(angle) > 0.1: |
| |
| angle_rad = angle * torch.pi / 180.0 |
|
|
| |
| cos_a = torch.cos(angle_rad) |
| sin_a = torch.sin(angle_rad) |
|
|
| |
| grid_x = torch.linspace(-1, 1, width, device=image.device) |
| grid_y = torch.linspace(-1, 1, height, device=image.device) |
|
|
| |
| grid_y, grid_x = torch.meshgrid(grid_y, grid_x, indexing="ij") |
|
|
| |
| grid_x = grid_x.unsqueeze(0).expand(image.shape[0], -1, -1) |
| grid_y = grid_y.unsqueeze(0).expand(image.shape[0], -1, -1) |
|
|
| |
| grid_x_rot = grid_x * cos_a - grid_y * sin_a |
| grid_y_rot = grid_x * sin_a + grid_y * cos_a |
|
|
| |
| grid = torch.stack([grid_x_rot, grid_y_rot], dim=-1) |
|
|
| image = torch.nn.functional.grid_sample( |
| image.permute(0, 3, 1, 2), |
| grid, |
| mode="bilinear", |
| padding_mode="zeros", |
| align_corners=False, |
| ).permute(0, 2, 3, 1) |
|
|
| |
| if apply_blur_visual_aug: |
| |
| image_nchw = image.permute(0, 3, 1, 2).contiguous() |
|
|
| aug = K.AugmentationSequential( |
| K.RandomMedianBlur(kernel_size=(3, 5), p=0.1), |
| K.RandomMotionBlur(kernel_size=(3, 5), angle=35., direction=0.5, p=0.1), |
| keepdim=True, |
| ) |
| |
| |
| image_nchw = aug(image_nchw) |
| |
| |
| image = image_nchw.permute(0, 2, 3, 1).contiguous() |
| |
| |
| |
| image = torch.clamp(image, 0, 1) |
|
|
| |
| image = image * 2.0 - 1.0 |
|
|
|
|
| |
| if is_channels_first: |
| image = image.permute(0, 3, 1, 2) |
|
|
| out_images[key] = image |
|
|
| out_masks = {} |
| for key in out_images: |
| if key not in observation.image_masks: |
| |
| out_masks[key] = torch.ones(batch_shape, dtype=torch.bool, device=observation.state.device) |
| else: |
| out_masks[key] = observation.image_masks[key] |
| |
| if 'base' in key and train and p_mask_base > 0.0: |
| |
| random_tensor = torch.rand(batch_shape, device=out_masks[key].device) |
| base_mask = random_tensor > p_mask_base |
| out_masks[key] = out_masks[key] & base_mask |
| |
| |
| |
| |
| |
| state_std = [ |
| 0.2079681158065796, |
| 0.7834290266036987, |
| 0.5441722273826599, |
| 0.14168238639831543, |
| 0.1750941127538681, |
| 0.15182428061962128, |
| 0.024107031524181366, |
| 0.19041913747787476, |
| 0.6899408102035522, |
| 0.4627247452735901, |
| 0.10430814325809479, |
| 0.1795605719089508, |
| 0.11770003288984299, |
| 0.03210258111357689, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0, |
| 0.0 |
| ], |
| |
| states = observation.state |
|
|
| |
| if state_noise_snr is not None: |
| |
| |
| |
| state_std = torch.tensor(state_std).to(states).reshape(1, -1) |
| |
| epsilon = 1e-6 |
| noise_scale = state_std / torch.sqrt(torch.tensor(10) ** (state_noise_snr / 10) + epsilon) |
| noise_scale = noise_scale.expand(states.shape) |
| |
| |
| |
| |
| |
| states += torch.randn_like(states) * noise_scale |
| |
|
|
| |
| |
| |
| class SimpleProcessedObservation: |
| def __init__(self, **kwargs): |
| for key, value in kwargs.items(): |
| setattr(self, key, value) |
|
|
| if return_full_obs: |
| return SimpleProcessedObservation( |
| images=out_images, |
| image_masks=out_masks, |
| |
| state=states, |
| tokenized_prompt=observation.tokenized_prompt, |
| tokenized_prompt_mask=observation.tokenized_prompt_mask, |
|
|
| token_ar_mask=observation.token_ar_mask, |
| token_loss_mask=observation.token_loss_mask, |
|
|
| action_advantage=observation.action_advantage, |
| action_advantage_original=observation.action_advantage_original, |
| |
| frame_index=observation.frame_index, |
| frame_index_progress=observation.frame_index_progress, |
| is_failure_data=observation.is_failure_data, |
| is_infer_data=observation.is_infer_data, |
| episode_length=observation.episode_length, |
|
|
| image_original=observation.image_original, |
| episode_index=observation.episode_index, |
| |
| inferred_action=observation.inferred_action, |
| noise=observation.noise, |
| |
| ) |
| else: |
| |
| return SimpleProcessedObservation( |
| images=out_images, |
| image_masks=out_masks, |
| state=states, |
| tokenized_prompt=observation.tokenized_prompt, |
| tokenized_prompt_mask=observation.tokenized_prompt_mask, |
| ) |
|
|