Add Relay-Kitchen FDK student + DDPM/EDM teachers (fw0 step80k + comparison archive) (part 4)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/qgpo_classifier.py +77 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/rew_classifiers.py +29 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/base_dataset.py +41 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/block_push_dataset.py +76 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_antmaze_dataset.py +563 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_kitchen_dataset.py +444 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_maze2d_dataset.py +290 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_mujoco_dataset.py +453 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/dataset_utils.py +409 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/imagecodecs.py +1385 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/kitchen_dataset.py +247 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/pusht_dataset.py +233 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/replay_buffer.py +592 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_dataset.py +506 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_rl_dataset.py +450 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/rotation_conversions.py +597 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/__init__.py +5 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/basic.py +106 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/consistency_model.py +428 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddim.py +190 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddpm.py +375 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/diffusionsde.py +987 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver.py +889 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver_discrete.py +598 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edm.py +426 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edmddim.py +84 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/koopman_net.py +427 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/newedm.py +438 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/rectifiedflow.py +632 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/veode.py +73 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpode.py +78 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpsde.py +1099 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/async_vector_env.py +671 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/__init__.py +7 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing.py +1092 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_discontinuous.py +338 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_multimodal.py +802 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/discontinuous_push_oracle.py +70 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/multimodal_push_oracle.py +187 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/oriented_push_oracle.py +258 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/pushing_info.py +35 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/reach_oracle.py +61 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/pose3d.py +70 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/utils_pybullet.py +451 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/xarm_sim_robot.py +230 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/__init__.py +30 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/base.py +156 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_lowdim_wrapper.py +56 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_util.py +51 -0
- checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/relay_policy_learning/adept_envs/adept_envs/__init__.py +19 -0
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/qgpo_classifier.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
from .base import BaseClassifier
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class QGPOClassifier(BaseClassifier):
|
| 10 |
+
"""
|
| 11 |
+
QGPOClassifier defines logp(y | x, t) using negative MSE.
|
| 12 |
+
Assuming nn_classifier is a NN used to predict y through x and t, i.e, pred_y = nn_classifier(x, t),
|
| 13 |
+
logp is defined as - temperature * MSE(nn_classifier(x, t), y).
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def loss(self, x: torch.Tensor, t: torch.Tensor, y: Dict[str, torch.Tensor]):
|
| 17 |
+
""" In-support Contrastive Energy Prediction Loss in https://arxiv.org/pdf/2304.12824
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
x: torch.Tensor,
|
| 21 |
+
Noisy support actions. Shape: (batch_size, K, act_dim)
|
| 22 |
+
t: torch.Tensor,
|
| 23 |
+
Diffusion time steps. Shape: (batch_size, )
|
| 24 |
+
y: Dict[str, torch.Tensor],
|
| 25 |
+
{
|
| 26 |
+
"soft_label": torch.Tensor,
|
| 27 |
+
Softmax of Q values. Shape: (batch_size, K, 1)
|
| 28 |
+
"obs": torch.Tensor,
|
| 29 |
+
Observations. Shape: (batch_size, obs_dim)}
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
loss: torch.Tensor
|
| 33 |
+
"""
|
| 34 |
+
b, k = x.shape[:2]
|
| 35 |
+
|
| 36 |
+
soft_label, obs = y["soft_label"], y["obs"]
|
| 37 |
+
|
| 38 |
+
f = self.model(x, t.unsqueeze(1).repeat(1, k), obs.unsqueeze(1).repeat(1, k, 1))
|
| 39 |
+
|
| 40 |
+
loss = - (soft_label * F.log_softmax(f, 1)).sum(1).mean()
|
| 41 |
+
|
| 42 |
+
with torch.no_grad():
|
| 43 |
+
f_max = f.max(1)[0].mean().item()
|
| 44 |
+
f_mean = f.mean().item()
|
| 45 |
+
f_min = f.min(1)[0].mean().item()
|
| 46 |
+
|
| 47 |
+
return loss, {"f_max": f_max, "f_mean": f_mean, "f_min": f_min}
|
| 48 |
+
|
| 49 |
+
def update(self, x: torch.Tensor, noise: torch.Tensor, y: Dict[str, torch.Tensor], update_ema: bool = True):
|
| 50 |
+
loss, log = self.loss(x, noise, y)
|
| 51 |
+
self.optim.zero_grad()
|
| 52 |
+
loss.backward()
|
| 53 |
+
if isinstance(self.grad_clip_norm, float):
|
| 54 |
+
grad_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm).item()
|
| 55 |
+
else:
|
| 56 |
+
grad_norm = None
|
| 57 |
+
self.optim.step()
|
| 58 |
+
if update_ema:
|
| 59 |
+
self.ema_update()
|
| 60 |
+
log.update({"loss": loss.item(), "grad_norm": grad_norm})
|
| 61 |
+
return log
|
| 62 |
+
|
| 63 |
+
def logp(self, x: torch.Tensor, t: torch.Tensor, c: torch.Tensor):
|
| 64 |
+
"""
|
| 65 |
+
Args:
|
| 66 |
+
x: torch.Tensor,
|
| 67 |
+
Noisy actions. Shape (batch_size, act_dim)
|
| 68 |
+
t: torch.Tensor,
|
| 69 |
+
Timestep. Shape (batch_size, )
|
| 70 |
+
c: torch.Tensor,
|
| 71 |
+
Observations. Shape (batch_size, obs_dim)
|
| 72 |
+
|
| 73 |
+
Returns:
|
| 74 |
+
f: torch.Tensor,
|
| 75 |
+
Energy prediction. Shape (batch_size, 1)
|
| 76 |
+
"""
|
| 77 |
+
return self.model_ema(x, t, c)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/rew_classifiers.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
|
| 3 |
+
from cleandiffuser.nn_classifier import BaseNNClassifier
|
| 4 |
+
from .base import BaseClassifier
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class CumRewClassifier(BaseClassifier):
|
| 8 |
+
def __init__(
|
| 9 |
+
self,
|
| 10 |
+
nn_classifier: BaseNNClassifier,
|
| 11 |
+
device: str = "cpu",
|
| 12 |
+
optim_params: Optional[dict] = None,
|
| 13 |
+
):
|
| 14 |
+
super().__init__(nn_classifier, 0.995, None, optim_params, device)
|
| 15 |
+
|
| 16 |
+
def loss(self, x, noise, R):
|
| 17 |
+
pred_R = self.model(x, noise, None)
|
| 18 |
+
return ((pred_R - R) ** 2).mean()
|
| 19 |
+
|
| 20 |
+
def update(self, x, noise, R):
|
| 21 |
+
self.optim.zero_grad()
|
| 22 |
+
loss = self.loss(x, noise, R)
|
| 23 |
+
loss.backward()
|
| 24 |
+
self.optim.step()
|
| 25 |
+
self.ema_update()
|
| 26 |
+
return {"loss": loss.item()}
|
| 27 |
+
|
| 28 |
+
def logp(self, x, noise, c=None):
|
| 29 |
+
return self.model_ema(x, noise)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/base_dataset.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import torch.nn
|
| 4 |
+
from torch.utils.data import Dataset
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Adapted from the datasets on: https://github.com/real-stanford/diffusion_policy
|
| 8 |
+
|
| 9 |
+
# Observation Horizon: To|n_obs_steps
|
| 10 |
+
# Action Horizon: Ta|n_action_steps
|
| 11 |
+
# Prediction Horizon: T|horizon
|
| 12 |
+
# To = 3
|
| 13 |
+
# Ta = 4
|
| 14 |
+
# T = 6
|
| 15 |
+
# |o|o|o|
|
| 16 |
+
# | | |a|a|a|a|
|
| 17 |
+
# To = 2
|
| 18 |
+
# Ta = 5
|
| 19 |
+
# T = 6
|
| 20 |
+
# |o|o|
|
| 21 |
+
# | |a|a|a|a|a|
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class BaseDataset(Dataset):
|
| 25 |
+
def get_normalizer(self, **kwargs):
|
| 26 |
+
raise NotImplementedError()
|
| 27 |
+
|
| 28 |
+
def __len__(self) -> int:
|
| 29 |
+
return 0
|
| 30 |
+
|
| 31 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 32 |
+
"""
|
| 33 |
+
output:
|
| 34 |
+
obs:
|
| 35 |
+
key1: T, Do1 # default key: state
|
| 36 |
+
key2: T, Do2
|
| 37 |
+
action: T, Da
|
| 38 |
+
reward: T, 1
|
| 39 |
+
info:
|
| 40 |
+
"""
|
| 41 |
+
raise NotImplementedError()
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/block_push_dataset.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import copy
|
| 5 |
+
import pathlib
|
| 6 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 7 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 8 |
+
from cleandiffuser.dataset.dataset_utils import SequenceSampler, MinMaxNormalizer, EmptyNormalizer, dict_apply
|
| 9 |
+
|
| 10 |
+
# dev/block_pushing/multimodal_push_seed.zarr
|
| 11 |
+
# ├── data
|
| 12 |
+
# │ ├── action (114962, 2) float32
|
| 13 |
+
# │ └── obs (114962, 16) float32
|
| 14 |
+
# └── meta
|
| 15 |
+
# └── episode_ends (1000,) int64
|
| 16 |
+
|
| 17 |
+
class BlockPushDataset(BaseDataset):
|
| 18 |
+
def __init__(self,
|
| 19 |
+
zarr_path,
|
| 20 |
+
horizon=1,
|
| 21 |
+
pad_before=0,
|
| 22 |
+
pad_after=0,
|
| 23 |
+
obs_keys=['obs', 'action']
|
| 24 |
+
):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.replay_buffer = ReplayBuffer.copy_from_path(
|
| 27 |
+
zarr_path, keys=obs_keys)
|
| 28 |
+
|
| 29 |
+
self.sampler = SequenceSampler(
|
| 30 |
+
replay_buffer=self.replay_buffer,
|
| 31 |
+
sequence_length=horizon,
|
| 32 |
+
pad_before=pad_before,
|
| 33 |
+
pad_after=pad_after)
|
| 34 |
+
|
| 35 |
+
self.horizon = horizon
|
| 36 |
+
self.pad_before = pad_before
|
| 37 |
+
self.pad_after = pad_after
|
| 38 |
+
|
| 39 |
+
self.normalizer = self.get_normalizer()
|
| 40 |
+
|
| 41 |
+
def get_normalizer(self):
|
| 42 |
+
state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
|
| 43 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
|
| 44 |
+
|
| 45 |
+
return {
|
| 46 |
+
"obs": {
|
| 47 |
+
"state": state_normalizer
|
| 48 |
+
},
|
| 49 |
+
"action": action_normalizer
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
def __str__(self) -> str:
|
| 53 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 54 |
+
|
| 55 |
+
def __len__(self) -> int:
|
| 56 |
+
return len(self.sampler)
|
| 57 |
+
|
| 58 |
+
def _sample_to_data(self, sample):
|
| 59 |
+
state = sample['obs'].astype(np.float32) # (T, 5)
|
| 60 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 61 |
+
|
| 62 |
+
action = sample['action'].astype(np.float32) # (T, 2)
|
| 63 |
+
action = self.normalizer['action'].normalize(action)
|
| 64 |
+
data = {
|
| 65 |
+
'obs': {
|
| 66 |
+
'state': state, # T, D_o
|
| 67 |
+
},
|
| 68 |
+
'action': action, # T, D_a
|
| 69 |
+
}
|
| 70 |
+
return data
|
| 71 |
+
|
| 72 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 73 |
+
sample = self.sampler.sample_sequence(idx)
|
| 74 |
+
data = self._sample_to_data(sample)
|
| 75 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 76 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_antmaze_dataset.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 7 |
+
from cleandiffuser.utils import GaussianNormalizer, dict_apply
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class D4RLAntmazeDataset(BaseDataset):
|
| 11 |
+
""" **D4RL-Antmaze Sequential Dataset**
|
| 12 |
+
|
| 13 |
+
torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
|
| 14 |
+
Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
|
| 15 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 16 |
+
Each batch contains
|
| 17 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 18 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 19 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 20 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
dataset: Dict[str, np.ndarray],
|
| 24 |
+
D4RL-Antmaze dataset. Obtained by calling `env.get_dataset()`.
|
| 25 |
+
horizon: int,
|
| 26 |
+
Length of each sequence. Default is 1.
|
| 27 |
+
max_path_length: int,
|
| 28 |
+
Maximum length of the episodes. Default is 1001.
|
| 29 |
+
noreaching_penalty: float,
|
| 30 |
+
Penalty for not reaching the goal. Default is -100.
|
| 31 |
+
discount: float,
|
| 32 |
+
Discount factor. Default is 0.99.
|
| 33 |
+
|
| 34 |
+
Examples:
|
| 35 |
+
>>> env = gym.make("antmaze-medium-play-v2")
|
| 36 |
+
>>> dataset = D4RLAntmazeDataset(env.get_dataset(), horizon=32)
|
| 37 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 38 |
+
>>> batch = next(iter(dataloader))
|
| 39 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 29)
|
| 40 |
+
>>> act = batch["act"] # (32, 32, 8)
|
| 41 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 42 |
+
>>> val = batch["val"] # (32, 1)
|
| 43 |
+
|
| 44 |
+
>>> normalizer = dataset.get_normalizer()
|
| 45 |
+
>>> obs = env.reset()[None, :]
|
| 46 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 47 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 48 |
+
"""
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
dataset: Dict[str, np.ndarray],
|
| 52 |
+
horizon: int = 1,
|
| 53 |
+
max_path_length: int = 1001,
|
| 54 |
+
noreaching_penalty: float = -100.,
|
| 55 |
+
discount: float = 0.99,
|
| 56 |
+
):
|
| 57 |
+
super().__init__()
|
| 58 |
+
|
| 59 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 60 |
+
dataset["observations"].astype(np.float32),
|
| 61 |
+
dataset["actions"].astype(np.float32),
|
| 62 |
+
dataset["rewards"].astype(np.float32),
|
| 63 |
+
dataset["timeouts"],
|
| 64 |
+
dataset["terminals"])
|
| 65 |
+
rewards -= 1 # -1 for each step and 0 for reaching the goal
|
| 66 |
+
dones = np.logical_or(timeouts, terminals)
|
| 67 |
+
self.normalizers = {
|
| 68 |
+
"state": GaussianNormalizer(observations)}
|
| 69 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 70 |
+
|
| 71 |
+
self.horizon = horizon
|
| 72 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 73 |
+
|
| 74 |
+
self.indices = []
|
| 75 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 76 |
+
self.tml_and_not_timeout = []
|
| 77 |
+
|
| 78 |
+
self.path_lengths, ptr = [], 0
|
| 79 |
+
path_idx = 0
|
| 80 |
+
for i in range(timeouts.shape[0]):
|
| 81 |
+
|
| 82 |
+
if i != 0 and ((dones[i - 1] and not dones[i]) or timeouts[i - 1]):
|
| 83 |
+
|
| 84 |
+
path_length = i - ptr
|
| 85 |
+
self.path_lengths.append(path_length)
|
| 86 |
+
|
| 87 |
+
if terminals[i] and not timeouts[i]:
|
| 88 |
+
self.tml_and_not_timeout.append([path_idx, i - ptr])
|
| 89 |
+
|
| 90 |
+
# 1. agent walks out of the goal
|
| 91 |
+
if path_length < max_path_length:
|
| 92 |
+
|
| 93 |
+
_seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
|
| 94 |
+
_seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
|
| 95 |
+
_seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
|
| 96 |
+
|
| 97 |
+
_seq_obs[:i - ptr] = normed_observations[ptr:i]
|
| 98 |
+
_seq_act[:i - ptr] = actions[ptr:i]
|
| 99 |
+
_seq_rew[:i - ptr] = rewards[ptr:i][:, None]
|
| 100 |
+
|
| 101 |
+
# repeat padding
|
| 102 |
+
_seq_obs[i - ptr:] = normed_observations[i] # repeat last state
|
| 103 |
+
_seq_act[i - ptr:] = 0 # repeat zero action
|
| 104 |
+
_seq_rew[i - ptr:] = 0 # repeat zero reward
|
| 105 |
+
|
| 106 |
+
self.seq_obs.append(_seq_obs)
|
| 107 |
+
self.seq_act.append(_seq_act)
|
| 108 |
+
self.seq_rew.append(_seq_rew)
|
| 109 |
+
|
| 110 |
+
# 2. agent never reaches the goal during the episode
|
| 111 |
+
elif path_length == max_path_length:
|
| 112 |
+
|
| 113 |
+
self.seq_obs.append(normed_observations[ptr:i])
|
| 114 |
+
self.seq_act.append(actions[ptr:i])
|
| 115 |
+
self.seq_rew.append(rewards[ptr:i][:, None])
|
| 116 |
+
|
| 117 |
+
# panelty for not reaching the goal
|
| 118 |
+
self.seq_rew[-1][-1] = noreaching_penalty
|
| 119 |
+
|
| 120 |
+
else:
|
| 121 |
+
raise ValueError(f"path_length: {path_length} > max_path_length: {max_path_length}")
|
| 122 |
+
|
| 123 |
+
max_start = min(self.path_lengths[-1] - 1, max_path_length - horizon)
|
| 124 |
+
self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
|
| 125 |
+
|
| 126 |
+
ptr = i
|
| 127 |
+
path_idx += 1
|
| 128 |
+
|
| 129 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 130 |
+
self.seq_act = np.array(self.seq_act)
|
| 131 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 132 |
+
|
| 133 |
+
self.seq_val = np.copy(self.seq_rew)
|
| 134 |
+
for i in range(max_path_length - 1):
|
| 135 |
+
self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
|
| 136 |
+
self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
|
| 137 |
+
|
| 138 |
+
def get_normalizer(self):
|
| 139 |
+
return self.normalizers["state"]
|
| 140 |
+
|
| 141 |
+
def __len__(self):
|
| 142 |
+
return len(self.indices)
|
| 143 |
+
|
| 144 |
+
def __getitem__(self, idx: int):
|
| 145 |
+
path_idx, start, end = self.indices[idx]
|
| 146 |
+
|
| 147 |
+
data = {
|
| 148 |
+
'obs': {
|
| 149 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 150 |
+
'act': self.seq_act[path_idx, start:end],
|
| 151 |
+
'rew': self.seq_rew[path_idx, start:end],
|
| 152 |
+
'val': self.seq_val[path_idx, start],
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 156 |
+
|
| 157 |
+
return torch_data
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class D4RLAntmazeTDDataset(BaseDataset):
|
| 161 |
+
""" **D4RL-Antmaze Transition Dataset**
|
| 162 |
+
|
| 163 |
+
torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
|
| 164 |
+
Chunk the dataset into transitions.
|
| 165 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 166 |
+
Each batch contains
|
| 167 |
+
- batch["obs"]["state"], observation of shape (batch_size, o_dim)
|
| 168 |
+
- batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
|
| 169 |
+
- batch["act"], action of shape (batch_size, a_dim)
|
| 170 |
+
- batch["rew"], reward of shape (batch_size, 1)
|
| 171 |
+
- batch["tml"], terminal of shape (batch_size, 1)
|
| 172 |
+
|
| 173 |
+
Args:
|
| 174 |
+
dataset: Dict[str, np.ndarray],
|
| 175 |
+
D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
|
| 176 |
+
reward_tune: str,
|
| 177 |
+
Reward tuning method. Can be "iql", "cql", "antmaze", or "none". Default is "iql".
|
| 178 |
+
|
| 179 |
+
Examples:
|
| 180 |
+
>>> env = gym.make("antmaze-medium-play-v2")
|
| 181 |
+
>>> dataset = D4RLAntmazeTDDataset(d4rl.qlearning_dataset(env))
|
| 182 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 183 |
+
>>> batch = next(iter(dataloader))
|
| 184 |
+
>>> obs = batch["obs"]["state"] # (32, 29)
|
| 185 |
+
>>> act = batch["act"] # (32, 8)
|
| 186 |
+
>>> rew = batch["rew"] # (32, 1)
|
| 187 |
+
>>> tml = batch["tml"] # (32, 1)
|
| 188 |
+
>>> next_obs = batch["next_obs"]["state"] # (32, 29)
|
| 189 |
+
|
| 190 |
+
>>> normalizer = dataset.get_normalizer()
|
| 191 |
+
>>> obs = env.reset()[None, :]
|
| 192 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 193 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 194 |
+
"""
|
| 195 |
+
def __init__(self, dataset: Dict[str, np.ndarray], reward_tune: str = "iql"):
|
| 196 |
+
super().__init__()
|
| 197 |
+
|
| 198 |
+
observations, actions, next_observations, rewards, terminals = (
|
| 199 |
+
dataset["observations"].astype(np.float32),
|
| 200 |
+
dataset["actions"].astype(np.float32),
|
| 201 |
+
dataset["next_observations"].astype(np.float32),
|
| 202 |
+
dataset["rewards"].astype(np.float32),
|
| 203 |
+
dataset["terminals"].astype(np.float32))
|
| 204 |
+
|
| 205 |
+
if reward_tune == "iql":
|
| 206 |
+
rewards = rewards - 1.
|
| 207 |
+
elif reward_tune == "cql":
|
| 208 |
+
rewards = (rewards - 0.5) * 4.
|
| 209 |
+
elif reward_tune == "antmaze":
|
| 210 |
+
rewards = (rewards - 0.25) * 2.
|
| 211 |
+
elif reward_tune == "none":
|
| 212 |
+
rewards = rewards
|
| 213 |
+
else:
|
| 214 |
+
raise ValueError(f"reward_tune: {reward_tune} is not supported.")
|
| 215 |
+
|
| 216 |
+
self.normalizers = {
|
| 217 |
+
"state": GaussianNormalizer(observations)}
|
| 218 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 219 |
+
normed_next_observations = self.normalizers["state"].normalize(next_observations)
|
| 220 |
+
|
| 221 |
+
self.obs = torch.tensor(normed_observations)
|
| 222 |
+
self.act = torch.tensor(actions)
|
| 223 |
+
self.rew = torch.tensor(rewards)[:, None]
|
| 224 |
+
self.tml = torch.tensor(terminals)[:, None]
|
| 225 |
+
self.next_obs = torch.tensor(normed_next_observations)
|
| 226 |
+
|
| 227 |
+
self.size = self.obs.shape[0]
|
| 228 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 229 |
+
|
| 230 |
+
def get_normalizer(self):
|
| 231 |
+
return self.normalizers["state"]
|
| 232 |
+
|
| 233 |
+
def __len__(self):
|
| 234 |
+
return self.size
|
| 235 |
+
|
| 236 |
+
def __getitem__(self, idx: int):
|
| 237 |
+
|
| 238 |
+
data = {
|
| 239 |
+
'obs': {
|
| 240 |
+
'state': self.obs[idx], },
|
| 241 |
+
'next_obs': {
|
| 242 |
+
'state': self.next_obs[idx], },
|
| 243 |
+
'act': self.act[idx],
|
| 244 |
+
'rew': self.rew[idx],
|
| 245 |
+
'tml': self.tml[idx], }
|
| 246 |
+
|
| 247 |
+
return data
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
class MultiHorizonD4RLAntmazeDataset(BaseDataset):
|
| 251 |
+
def __init__(
|
| 252 |
+
self,
|
| 253 |
+
dataset,
|
| 254 |
+
horizons=(10, 20),
|
| 255 |
+
max_path_length=1001,
|
| 256 |
+
noreaching_penalty=-100,
|
| 257 |
+
discount=0.99,
|
| 258 |
+
):
|
| 259 |
+
super().__init__()
|
| 260 |
+
|
| 261 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 262 |
+
dataset["observations"].astype(np.float32),
|
| 263 |
+
dataset["actions"].astype(np.float32),
|
| 264 |
+
dataset["rewards"].astype(np.float32),
|
| 265 |
+
dataset["timeouts"],
|
| 266 |
+
dataset["terminals"])
|
| 267 |
+
rewards -= 1
|
| 268 |
+
dones = np.logical_or(timeouts, terminals)
|
| 269 |
+
self.normalizers = {
|
| 270 |
+
"state": GaussianNormalizer(observations)}
|
| 271 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 272 |
+
|
| 273 |
+
self.horizons = horizons
|
| 274 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 275 |
+
self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
|
| 276 |
+
|
| 277 |
+
self.indices = [[] for _ in range(len(horizons))]
|
| 278 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 279 |
+
|
| 280 |
+
self.path_lengths, ptr = [], 0
|
| 281 |
+
path_idx = 0
|
| 282 |
+
for i in range(timeouts.shape[0]):
|
| 283 |
+
|
| 284 |
+
if i != 0 and ((dones[i - 1] and not dones[i]) or timeouts[i - 1]):
|
| 285 |
+
|
| 286 |
+
path_length = i - ptr
|
| 287 |
+
self.path_lengths.append(path_length)
|
| 288 |
+
|
| 289 |
+
# 1. agent walks out of the goal
|
| 290 |
+
if path_length < max_path_length:
|
| 291 |
+
|
| 292 |
+
_seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
|
| 293 |
+
_seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
|
| 294 |
+
_seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
|
| 295 |
+
|
| 296 |
+
_seq_obs[:i - ptr] = normed_observations[ptr:i]
|
| 297 |
+
_seq_act[:i - ptr] = actions[ptr:i]
|
| 298 |
+
_seq_rew[:i - ptr] = rewards[ptr:i][:, None]
|
| 299 |
+
|
| 300 |
+
# repeat padding
|
| 301 |
+
_seq_obs[i - ptr:] = normed_observations[i] # repeat last state
|
| 302 |
+
_seq_act[i - ptr:] = 0 # repeat zero action
|
| 303 |
+
_seq_rew[i - ptr:] = 0 # repeat zero reward
|
| 304 |
+
|
| 305 |
+
self.seq_obs.append(_seq_obs)
|
| 306 |
+
self.seq_act.append(_seq_act)
|
| 307 |
+
self.seq_rew.append(_seq_rew)
|
| 308 |
+
|
| 309 |
+
# 2. agent never reaches the goal during the episode
|
| 310 |
+
elif path_length == max_path_length:
|
| 311 |
+
|
| 312 |
+
self.seq_obs.append(normed_observations[ptr:i])
|
| 313 |
+
self.seq_act.append(actions[ptr:i])
|
| 314 |
+
self.seq_rew.append(rewards[ptr:i][:, None])
|
| 315 |
+
|
| 316 |
+
# panelty for not reaching the goal
|
| 317 |
+
self.seq_rew[-1][-1] = noreaching_penalty
|
| 318 |
+
|
| 319 |
+
else:
|
| 320 |
+
raise ValueError(f"path_length: {path_length} > max_path_length: {max_path_length}")
|
| 321 |
+
|
| 322 |
+
max_starts = [min(self.path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
|
| 323 |
+
for k in range(len(horizons)):
|
| 324 |
+
self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
|
| 325 |
+
|
| 326 |
+
ptr = i
|
| 327 |
+
path_idx += 1
|
| 328 |
+
|
| 329 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 330 |
+
self.seq_act = np.array(self.seq_act)
|
| 331 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 332 |
+
|
| 333 |
+
self.len_each_horizon = [len(indices) for indices in self.indices]
|
| 334 |
+
|
| 335 |
+
def get_normalizer(self):
|
| 336 |
+
return self.normalizers["state"]
|
| 337 |
+
|
| 338 |
+
def __len__(self):
|
| 339 |
+
return max(self.len_each_horizon)
|
| 340 |
+
|
| 341 |
+
def __getitem__(self, idx: int):
|
| 342 |
+
|
| 343 |
+
indices = [
|
| 344 |
+
int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
|
| 345 |
+
|
| 346 |
+
torch_datas = []
|
| 347 |
+
|
| 348 |
+
for i, horizon in enumerate(self.horizons):
|
| 349 |
+
|
| 350 |
+
path_idx, start, end = self.indices[i][indices[i]]
|
| 351 |
+
|
| 352 |
+
rewards = self.seq_rew[path_idx, start:]
|
| 353 |
+
values = (rewards * self.discount[:rewards.shape[0], None]).sum(0)
|
| 354 |
+
|
| 355 |
+
data = {
|
| 356 |
+
'obs': {
|
| 357 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 358 |
+
'act': self.seq_act[path_idx, start:end],
|
| 359 |
+
'rew': self.seq_rew[path_idx, start:end],
|
| 360 |
+
'val': values}
|
| 361 |
+
|
| 362 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 363 |
+
|
| 364 |
+
torch_datas.append({
|
| 365 |
+
"horizon": horizon,
|
| 366 |
+
"data": torch_data,
|
| 367 |
+
})
|
| 368 |
+
|
| 369 |
+
return torch_datas
|
| 370 |
+
|
| 371 |
+
class DV_D4RLAntmazeSeqDataset(BaseDataset):
|
| 372 |
+
""" **D4RL-Antmaze Sequential Dataset**
|
| 373 |
+
|
| 374 |
+
torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
|
| 375 |
+
Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
|
| 376 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 377 |
+
Each batch contains
|
| 378 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 379 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 380 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 381 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 382 |
+
|
| 383 |
+
Args:
|
| 384 |
+
dataset: Dict[str, np.ndarray],
|
| 385 |
+
D4RL-Antmaze dataset. Obtained by calling `env.get_dataset()`.
|
| 386 |
+
horizon: int,
|
| 387 |
+
Length of each sequence. Default is 1.
|
| 388 |
+
max_path_length: int,
|
| 389 |
+
Maximum length of the episodes. Default is 1001.
|
| 390 |
+
noreaching_penalty: float,
|
| 391 |
+
Penalty for not reaching the goal. Default is -100.
|
| 392 |
+
discount: float,
|
| 393 |
+
Discount factor. Default is 0.99.
|
| 394 |
+
|
| 395 |
+
Examples:
|
| 396 |
+
>>> env = gym.make("antmaze-medium-play-v2")
|
| 397 |
+
>>> dataset = D4RLAntmazeDataset(env.get_dataset(), horizon=32)
|
| 398 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 399 |
+
>>> batch = next(iter(dataloader))
|
| 400 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 29)
|
| 401 |
+
>>> act = batch["act"] # (32, 32, 8)
|
| 402 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 403 |
+
>>> val = batch["val"] # (32, 1)
|
| 404 |
+
|
| 405 |
+
>>> normalizer = dataset.get_normalizer()
|
| 406 |
+
>>> obs = env.reset()[None, :]
|
| 407 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 408 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 409 |
+
"""
|
| 410 |
+
def __init__(
|
| 411 |
+
self,
|
| 412 |
+
dataset: Dict[str, np.ndarray],
|
| 413 |
+
horizon: int = 1,
|
| 414 |
+
max_path_length: int = 1001,
|
| 415 |
+
discount: float = 0.99,
|
| 416 |
+
continous_reward_at_done: bool = False,
|
| 417 |
+
reward_tune: str = "iql",
|
| 418 |
+
center_mapping: bool = True,
|
| 419 |
+
learn_policy: bool = False,
|
| 420 |
+
stride: int = 1,
|
| 421 |
+
only_learn_reached_policy: bool = False,
|
| 422 |
+
):
|
| 423 |
+
super().__init__()
|
| 424 |
+
|
| 425 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 426 |
+
dataset["observations"].astype(np.float32),
|
| 427 |
+
dataset["actions"].astype(np.float32),
|
| 428 |
+
dataset["rewards"].astype(np.float32),
|
| 429 |
+
dataset["timeouts"].astype(np.float32),
|
| 430 |
+
dataset["terminals"].astype(np.float32))
|
| 431 |
+
|
| 432 |
+
self.learn_policy = learn_policy
|
| 433 |
+
self.stride = stride
|
| 434 |
+
|
| 435 |
+
self.normalizers = {
|
| 436 |
+
"state": GaussianNormalizer(observations)}
|
| 437 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 438 |
+
|
| 439 |
+
self.horizon = horizon
|
| 440 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 441 |
+
|
| 442 |
+
self.indices = []
|
| 443 |
+
self.seq_obs, self.seq_act, self.seq_rew, self.seq_tml = [], [], [], []
|
| 444 |
+
|
| 445 |
+
ptr = 0 # ptr: t=0 pos
|
| 446 |
+
path_idx = 0
|
| 447 |
+
|
| 448 |
+
timeouts_indices = np.where(timeouts == 1)[0]
|
| 449 |
+
|
| 450 |
+
for index in timeouts_indices:
|
| 451 |
+
terminal_index = -1
|
| 452 |
+
assert index - ptr + 1 == max_path_length
|
| 453 |
+
for i in range(ptr, index + 1):
|
| 454 |
+
if terminals[i]:
|
| 455 |
+
terminal_index = i
|
| 456 |
+
break
|
| 457 |
+
|
| 458 |
+
if terminal_index == -1 and self.learn_policy and not only_learn_reached_policy:
|
| 459 |
+
path_length = index - ptr + 1
|
| 460 |
+
assert path_length == max_path_length
|
| 461 |
+
|
| 462 |
+
_seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
|
| 463 |
+
_seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
|
| 464 |
+
_seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 465 |
+
_seq_tml = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 466 |
+
|
| 467 |
+
_seq_obs[:path_length] = normed_observations[ptr:index+1]
|
| 468 |
+
_seq_act[:path_length] = actions[ptr:index+1]
|
| 469 |
+
_seq_rew[:path_length] = rewards[ptr:index+1][:, None]
|
| 470 |
+
_seq_tml[:path_length] = terminals[ptr:index+1][:, None]
|
| 471 |
+
|
| 472 |
+
self.seq_obs.append(_seq_obs)
|
| 473 |
+
self.seq_act.append(_seq_act)
|
| 474 |
+
self.seq_rew.append(_seq_rew)
|
| 475 |
+
self.seq_tml.append(_seq_tml)
|
| 476 |
+
|
| 477 |
+
max_start = max_path_length - (horizon - 1) * stride - 1
|
| 478 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 479 |
+
path_idx += 1
|
| 480 |
+
|
| 481 |
+
elif terminal_index != -1:
|
| 482 |
+
path_length = terminal_index - ptr + 1
|
| 483 |
+
assert path_length <= max_path_length
|
| 484 |
+
assert rewards[terminal_index] == 1
|
| 485 |
+
|
| 486 |
+
_seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
|
| 487 |
+
_seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
|
| 488 |
+
_seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 489 |
+
_seq_tml = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 490 |
+
|
| 491 |
+
_seq_obs[:path_length] = normed_observations[ptr:terminal_index+1]
|
| 492 |
+
_seq_act[:path_length] = actions[ptr:terminal_index+1]
|
| 493 |
+
_seq_rew[:path_length] = rewards[ptr:terminal_index+1][:, None]
|
| 494 |
+
_seq_tml[:path_length] = terminals[ptr:terminal_index+1][:, None]
|
| 495 |
+
|
| 496 |
+
_seq_obs[path_length:] = normed_observations[terminal_index] # repeat state
|
| 497 |
+
_seq_act[path_length:] = 0 # zero action
|
| 498 |
+
_seq_rew[path_length:] = 1 if continous_reward_at_done else 0
|
| 499 |
+
_seq_tml[path_length:] = 1
|
| 500 |
+
|
| 501 |
+
self.seq_obs.append(_seq_obs)
|
| 502 |
+
self.seq_act.append(_seq_act)
|
| 503 |
+
self.seq_rew.append(_seq_rew)
|
| 504 |
+
self.seq_tml.append(_seq_tml)
|
| 505 |
+
|
| 506 |
+
max_start = path_length - 1
|
| 507 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 508 |
+
path_idx += 1
|
| 509 |
+
|
| 510 |
+
ptr = index + 1
|
| 511 |
+
|
| 512 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 513 |
+
self.seq_act = np.array(self.seq_act)
|
| 514 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 515 |
+
self.seq_tml = np.array(self.seq_tml)
|
| 516 |
+
|
| 517 |
+
if reward_tune == "iql":
|
| 518 |
+
self.seq_rew += -1
|
| 519 |
+
elif reward_tune == "none":
|
| 520 |
+
self.seq_rew = self.seq_rew
|
| 521 |
+
else:
|
| 522 |
+
raise ValueError(f"reward_tune: {reward_tune} is not supported.")
|
| 523 |
+
|
| 524 |
+
self.seq_val = np.copy(self.seq_rew)
|
| 525 |
+
for i in reversed(range(max_path_length - 1)):
|
| 526 |
+
self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
|
| 527 |
+
|
| 528 |
+
print(f"max discounted return: {self.seq_val.max()}")
|
| 529 |
+
print(f"min discounted return: {self.seq_val.min()}")
|
| 530 |
+
|
| 531 |
+
# val \in [-1, 1]
|
| 532 |
+
self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
|
| 533 |
+
if center_mapping:
|
| 534 |
+
self.seq_val = self.seq_val * 2 - 1
|
| 535 |
+
print(f"max normed discounted return: {self.seq_val.max()}")
|
| 536 |
+
print(f"min normed discounted return: {self.seq_val.min()}")
|
| 537 |
+
|
| 538 |
+
def get_normalizer(self):
|
| 539 |
+
return self.normalizers["state"]
|
| 540 |
+
|
| 541 |
+
def __len__(self):
|
| 542 |
+
return len(self.indices)
|
| 543 |
+
|
| 544 |
+
def __getitem__(self, idx: int):
|
| 545 |
+
path_idx, start, end = self.indices[idx]
|
| 546 |
+
|
| 547 |
+
if self.learn_policy:
|
| 548 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride].copy()
|
| 549 |
+
horizon_state[:, :2] -= horizon_state[0, :2]
|
| 550 |
+
else:
|
| 551 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride]
|
| 552 |
+
|
| 553 |
+
data = {
|
| 554 |
+
'obs': {'state': horizon_state},
|
| 555 |
+
'act': self.seq_act[path_idx, start:end:self.stride],
|
| 556 |
+
'rew': self.seq_rew[path_idx, start:end:self.stride],
|
| 557 |
+
'val': self.seq_val[path_idx, start],
|
| 558 |
+
'tml': self.seq_tml[path_idx, start],
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 562 |
+
|
| 563 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_kitchen_dataset.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 7 |
+
from cleandiffuser.utils import GaussianNormalizer, dict_apply
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class D4RLKitchenDataset(BaseDataset):
|
| 11 |
+
""" **D4RL-Kitchen Sequential Dataset**
|
| 12 |
+
|
| 13 |
+
torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
|
| 14 |
+
Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-repeat padding.
|
| 15 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 16 |
+
Each batch contains
|
| 17 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 18 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 19 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 20 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
dataset: Dict[str, np.ndarray],
|
| 24 |
+
D4RL-Kitchen dataset. Obtained by calling `env.get_dataset()`.
|
| 25 |
+
horizon: int,
|
| 26 |
+
Length of each sequence. Default is 1.
|
| 27 |
+
max_path_length: int,
|
| 28 |
+
Maximum length of the episodes. Default is 280.
|
| 29 |
+
discount: float,
|
| 30 |
+
Discount factor. Default is 0.99.
|
| 31 |
+
|
| 32 |
+
Examples:
|
| 33 |
+
>>> env = gym.make("kitchen-mixed-v0")
|
| 34 |
+
>>> dataset = D4RLKitchenDataset(env.get_dataset(), horizon=32)
|
| 35 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 36 |
+
>>> batch = next(iter(dataloader))
|
| 37 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 60)
|
| 38 |
+
>>> act = batch["act"] # (32, 32, 9)
|
| 39 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 40 |
+
>>> val = batch["val"] # (32, 1)
|
| 41 |
+
|
| 42 |
+
>>> normalizer = dataset.get_normalizer()
|
| 43 |
+
>>> obs = env.reset()[None, :]
|
| 44 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 45 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 46 |
+
"""
|
| 47 |
+
def __init__(
|
| 48 |
+
self,
|
| 49 |
+
dataset: Dict[str, np.ndarray],
|
| 50 |
+
horizon: int = 1,
|
| 51 |
+
max_path_length: int = 280,
|
| 52 |
+
discount: float = 0.99,
|
| 53 |
+
):
|
| 54 |
+
super().__init__()
|
| 55 |
+
|
| 56 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 57 |
+
dataset["observations"].astype(np.float32),
|
| 58 |
+
dataset["actions"].astype(np.float32),
|
| 59 |
+
dataset["rewards"].astype(np.float32),
|
| 60 |
+
dataset["timeouts"],
|
| 61 |
+
dataset["terminals"])
|
| 62 |
+
self.normalizers = {
|
| 63 |
+
"state": GaussianNormalizer(observations)}
|
| 64 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 65 |
+
|
| 66 |
+
self.horizon = horizon
|
| 67 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 68 |
+
|
| 69 |
+
self.indices = []
|
| 70 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 71 |
+
self.tml_and_not_timeout = []
|
| 72 |
+
|
| 73 |
+
self.path_lengths, ptr = [], 0
|
| 74 |
+
path_idx = 0
|
| 75 |
+
for i in range(timeouts.shape[0]):
|
| 76 |
+
if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
|
| 77 |
+
self.path_lengths.append(i - ptr + 1)
|
| 78 |
+
|
| 79 |
+
if terminals[i] and not timeouts[i]:
|
| 80 |
+
self.tml_and_not_timeout.append([path_idx, i - ptr])
|
| 81 |
+
|
| 82 |
+
_seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
|
| 83 |
+
_seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
|
| 84 |
+
_seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
|
| 85 |
+
|
| 86 |
+
_seq_obs[:i - ptr + 1] = normed_observations[ptr:i + 1]
|
| 87 |
+
_seq_act[:i - ptr + 1] = actions[ptr:i + 1]
|
| 88 |
+
_seq_rew[:i - ptr + 1] = rewards[ptr:i + 1][:, None]
|
| 89 |
+
|
| 90 |
+
# repeat padding
|
| 91 |
+
_seq_obs[i - ptr + 1:] = normed_observations[i] # repeat last state
|
| 92 |
+
_seq_act[i - ptr + 1:] = 0 # repeat zero action
|
| 93 |
+
_seq_rew[i - ptr + 1:] = rewards[i] # repeat last reward
|
| 94 |
+
|
| 95 |
+
self.seq_obs.append(_seq_obs)
|
| 96 |
+
self.seq_act.append(_seq_act)
|
| 97 |
+
self.seq_rew.append(_seq_rew)
|
| 98 |
+
|
| 99 |
+
max_start = min(self.path_lengths[-1] - 1, max_path_length - horizon)
|
| 100 |
+
self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
|
| 101 |
+
|
| 102 |
+
ptr = i + 1
|
| 103 |
+
path_idx += 1
|
| 104 |
+
|
| 105 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 106 |
+
self.seq_act = np.array(self.seq_act)
|
| 107 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 108 |
+
|
| 109 |
+
self.seq_val = np.copy(self.seq_rew)
|
| 110 |
+
for i in range(max_path_length - 1):
|
| 111 |
+
self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
|
| 112 |
+
self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
|
| 113 |
+
|
| 114 |
+
def get_normalizer(self):
|
| 115 |
+
return self.normalizers["state"]
|
| 116 |
+
|
| 117 |
+
def __len__(self):
|
| 118 |
+
return len(self.indices)
|
| 119 |
+
|
| 120 |
+
def __getitem__(self, idx: int):
|
| 121 |
+
path_idx, start, end = self.indices[idx]
|
| 122 |
+
|
| 123 |
+
data = {
|
| 124 |
+
'obs': {
|
| 125 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 126 |
+
'act': self.seq_act[path_idx, start:end],
|
| 127 |
+
'rew': self.seq_rew[path_idx, start:end],
|
| 128 |
+
'val': self.seq_val[path_idx, start],
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 132 |
+
|
| 133 |
+
return torch_data
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class D4RLKitchenTDDataset(BaseDataset):
|
| 137 |
+
""" **D4RL-Kitchen Transition Dataset**
|
| 138 |
+
|
| 139 |
+
torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
|
| 140 |
+
Chunk the dataset into transitions.
|
| 141 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 142 |
+
Each batch contains
|
| 143 |
+
- batch["obs"]["state"], observation of shape (batch_size, o_dim)
|
| 144 |
+
- batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
|
| 145 |
+
- batch["act"], action of shape (batch_size, a_dim)
|
| 146 |
+
- batch["rew"], reward of shape (batch_size, 1)
|
| 147 |
+
- batch["tml"], terminal of shape (batch_size, 1)
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
dataset: Dict[str, np.ndarray],
|
| 151 |
+
D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
|
| 152 |
+
|
| 153 |
+
Examples:
|
| 154 |
+
>>> env = gym.make("kitchen-mixed-v0")
|
| 155 |
+
>>> dataset = D4RLKitchenTDDataset(d4rl.qlearning_dataset(env))
|
| 156 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 157 |
+
>>> batch = next(iter(dataloader))
|
| 158 |
+
>>> obs = batch["obs"]["state"] # (32, 60)
|
| 159 |
+
>>> act = batch["act"] # (32, 9)
|
| 160 |
+
>>> rew = batch["rew"] # (32, 1)
|
| 161 |
+
>>> tml = batch["tml"] # (32, 1)
|
| 162 |
+
>>> next_obs = batch["next_obs"]["state"] # (32, 60)
|
| 163 |
+
|
| 164 |
+
>>> normalizer = dataset.get_normalizer()
|
| 165 |
+
>>> obs = env.reset()[None, :]
|
| 166 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 167 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 168 |
+
"""
|
| 169 |
+
def __init__(self, dataset: Dict[str, np.ndarray]):
|
| 170 |
+
super().__init__()
|
| 171 |
+
|
| 172 |
+
observations, actions, next_observations, rewards, terminals = (
|
| 173 |
+
dataset["observations"].astype(np.float32),
|
| 174 |
+
dataset["actions"].astype(np.float32),
|
| 175 |
+
dataset["next_observations"].astype(np.float32),
|
| 176 |
+
dataset["rewards"].astype(np.float32),
|
| 177 |
+
dataset["terminals"].astype(np.float32))
|
| 178 |
+
|
| 179 |
+
self.normalizers = {
|
| 180 |
+
"state": GaussianNormalizer(observations)}
|
| 181 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 182 |
+
normed_next_observations = self.normalizers["state"].normalize(next_observations)
|
| 183 |
+
|
| 184 |
+
self.obs = torch.tensor(normed_observations)
|
| 185 |
+
self.act = torch.tensor(actions)
|
| 186 |
+
self.rew = torch.tensor(rewards)[:, None]
|
| 187 |
+
self.tml = torch.tensor(terminals)[:, None]
|
| 188 |
+
self.next_obs = torch.tensor(normed_next_observations)
|
| 189 |
+
|
| 190 |
+
self.size = self.obs.shape[0]
|
| 191 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 192 |
+
|
| 193 |
+
def get_normalizer(self):
|
| 194 |
+
return self.normalizers["state"]
|
| 195 |
+
|
| 196 |
+
def __len__(self):
|
| 197 |
+
return self.size
|
| 198 |
+
|
| 199 |
+
def __getitem__(self, idx: int):
|
| 200 |
+
data = {
|
| 201 |
+
'obs': {
|
| 202 |
+
'state': self.obs[idx], },
|
| 203 |
+
'next_obs': {
|
| 204 |
+
'state': self.next_obs[idx], },
|
| 205 |
+
'act': self.act[idx],
|
| 206 |
+
'rew': self.rew[idx],
|
| 207 |
+
'tml': self.tml[idx], }
|
| 208 |
+
|
| 209 |
+
return data
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class MultiHorizonD4RLKitchenDataset(BaseDataset):
|
| 213 |
+
def __init__(
|
| 214 |
+
self,
|
| 215 |
+
dataset,
|
| 216 |
+
horizons=(10, 20),
|
| 217 |
+
max_path_length=280,
|
| 218 |
+
discount=0.99,
|
| 219 |
+
):
|
| 220 |
+
super().__init__()
|
| 221 |
+
|
| 222 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 223 |
+
dataset["observations"].astype(np.float32),
|
| 224 |
+
dataset["actions"].astype(np.float32),
|
| 225 |
+
dataset["rewards"].astype(np.float32),
|
| 226 |
+
dataset["timeouts"],
|
| 227 |
+
dataset["terminals"])
|
| 228 |
+
self.normalizers = {
|
| 229 |
+
"state": GaussianNormalizer(observations)}
|
| 230 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 231 |
+
|
| 232 |
+
self.horizons = horizons
|
| 233 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 234 |
+
self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
|
| 235 |
+
|
| 236 |
+
self.indices = [[] for _ in range(len(horizons))]
|
| 237 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 238 |
+
|
| 239 |
+
self.path_lengths, ptr = [], 0
|
| 240 |
+
path_idx = 0
|
| 241 |
+
for i in range(timeouts.shape[0]):
|
| 242 |
+
if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
|
| 243 |
+
self.path_lengths.append(i - ptr + 1)
|
| 244 |
+
|
| 245 |
+
_seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
|
| 246 |
+
_seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
|
| 247 |
+
_seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
|
| 248 |
+
|
| 249 |
+
_seq_obs[:i - ptr + 1] = normed_observations[ptr:i + 1]
|
| 250 |
+
_seq_act[:i - ptr + 1] = actions[ptr:i + 1]
|
| 251 |
+
_seq_rew[:i - ptr + 1] = rewards[ptr:i + 1][:, None]
|
| 252 |
+
|
| 253 |
+
# repeat padding
|
| 254 |
+
_seq_obs[i - ptr + 1:] = normed_observations[i] # repeat last state
|
| 255 |
+
_seq_act[i - ptr + 1:] = 0 # repeat zero action
|
| 256 |
+
_seq_rew[i - ptr + 1:] = rewards[i] # repeat last reward
|
| 257 |
+
|
| 258 |
+
self.seq_obs.append(_seq_obs)
|
| 259 |
+
self.seq_act.append(_seq_act)
|
| 260 |
+
self.seq_rew.append(_seq_rew)
|
| 261 |
+
|
| 262 |
+
max_starts = [min(self.path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
|
| 263 |
+
for k in range(len(horizons)):
|
| 264 |
+
self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
|
| 265 |
+
|
| 266 |
+
ptr = i + 1
|
| 267 |
+
path_idx += 1
|
| 268 |
+
|
| 269 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 270 |
+
self.seq_act = np.array(self.seq_act)
|
| 271 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 272 |
+
|
| 273 |
+
self.len_each_horizon = [len(indices) for indices in self.indices]
|
| 274 |
+
|
| 275 |
+
def get_normalizer(self):
|
| 276 |
+
return self.normalizers["state"]
|
| 277 |
+
|
| 278 |
+
def __len__(self):
|
| 279 |
+
return max(self.len_each_horizon)
|
| 280 |
+
|
| 281 |
+
def __getitem__(self, idx: int):
|
| 282 |
+
|
| 283 |
+
indices = [
|
| 284 |
+
int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
|
| 285 |
+
|
| 286 |
+
torch_datas = []
|
| 287 |
+
|
| 288 |
+
for i, horizon in enumerate(self.horizons):
|
| 289 |
+
path_idx, start, end = self.indices[i][indices[i]]
|
| 290 |
+
|
| 291 |
+
rewards = self.seq_rew[path_idx, start:]
|
| 292 |
+
values = (rewards * self.discount[:rewards.shape[0], None]).sum(0)
|
| 293 |
+
|
| 294 |
+
data = {
|
| 295 |
+
'obs': {
|
| 296 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 297 |
+
'act': self.seq_act[path_idx, start:end],
|
| 298 |
+
'rew': self.seq_rew[path_idx, start:end],
|
| 299 |
+
'val': values}
|
| 300 |
+
|
| 301 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 302 |
+
|
| 303 |
+
torch_datas.append({
|
| 304 |
+
"horizon": horizon,
|
| 305 |
+
"data": torch_data,
|
| 306 |
+
})
|
| 307 |
+
|
| 308 |
+
return torch_datas
|
| 309 |
+
|
| 310 |
+
class DV_D4RLKitchenSeqDataset(BaseDataset):
|
| 311 |
+
""" **D4RL-Kitchen Sequential Dataset**
|
| 312 |
+
|
| 313 |
+
torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
|
| 314 |
+
Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-repeat padding.
|
| 315 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 316 |
+
Each batch contains
|
| 317 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 318 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 319 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 320 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 321 |
+
|
| 322 |
+
Args:
|
| 323 |
+
dataset: Dict[str, np.ndarray],
|
| 324 |
+
D4RL-Kitchen dataset. Obtained by calling `env.get_dataset()`.
|
| 325 |
+
horizon: int,
|
| 326 |
+
Length of each sequence. Default is 1.
|
| 327 |
+
max_path_length: int,
|
| 328 |
+
Maximum length of the episodes. Default is 280.
|
| 329 |
+
discount: float,
|
| 330 |
+
Discount factor. Default is 0.99.
|
| 331 |
+
|
| 332 |
+
Examples:
|
| 333 |
+
>>> env = gym.make("kitchen-mixed-v0")
|
| 334 |
+
>>> dataset = D4RLKitchenDataset(env.get_dataset(), horizon=32)
|
| 335 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 336 |
+
>>> batch = next(iter(dataloader))
|
| 337 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 60)
|
| 338 |
+
>>> act = batch["act"] # (32, 32, 9)
|
| 339 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 340 |
+
>>> val = batch["val"] # (32, 1)
|
| 341 |
+
|
| 342 |
+
>>> normalizer = dataset.get_normalizer()
|
| 343 |
+
>>> obs = env.reset()[None, :]
|
| 344 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 345 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 346 |
+
"""
|
| 347 |
+
def __init__(
|
| 348 |
+
self,
|
| 349 |
+
dataset: Dict[str, np.ndarray],
|
| 350 |
+
horizon: int = 1,
|
| 351 |
+
max_path_length: int = 280,
|
| 352 |
+
discount: float = 0.99,
|
| 353 |
+
center_mapping: bool = True,
|
| 354 |
+
stride: int = 1,
|
| 355 |
+
):
|
| 356 |
+
super().__init__()
|
| 357 |
+
|
| 358 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 359 |
+
dataset["observations"].astype(np.float32),
|
| 360 |
+
dataset["actions"].astype(np.float32),
|
| 361 |
+
dataset["rewards"].astype(np.float32),
|
| 362 |
+
dataset["timeouts"].astype(np.float32),
|
| 363 |
+
dataset["terminals"].astype(np.float32))
|
| 364 |
+
self.stride = stride
|
| 365 |
+
|
| 366 |
+
self.normalizers = {
|
| 367 |
+
"state": GaussianNormalizer(observations)}
|
| 368 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 369 |
+
|
| 370 |
+
self.horizon = horizon
|
| 371 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 372 |
+
|
| 373 |
+
self.indices = []
|
| 374 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 375 |
+
|
| 376 |
+
ptr = 0
|
| 377 |
+
path_idx = 0
|
| 378 |
+
for i in range(timeouts.shape[0]):
|
| 379 |
+
if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
|
| 380 |
+
path_length = i - ptr + 1
|
| 381 |
+
assert path_length <= max_path_length
|
| 382 |
+
|
| 383 |
+
_seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
|
| 384 |
+
_seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
|
| 385 |
+
_seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 386 |
+
|
| 387 |
+
_seq_obs[:path_length] = normed_observations[ptr:i + 1]
|
| 388 |
+
_seq_act[:path_length] = actions[ptr:i + 1]
|
| 389 |
+
_seq_rew[:path_length] = rewards[ptr:i + 1][:, None]
|
| 390 |
+
|
| 391 |
+
# repeat padding
|
| 392 |
+
_seq_obs[path_length:] = normed_observations[i] # repeat last state
|
| 393 |
+
_seq_act[path_length:] = 0
|
| 394 |
+
_seq_rew[path_length:] = rewards[i]
|
| 395 |
+
|
| 396 |
+
self.seq_obs.append(_seq_obs)
|
| 397 |
+
self.seq_act.append(_seq_act)
|
| 398 |
+
self.seq_rew.append(_seq_rew)
|
| 399 |
+
|
| 400 |
+
max_start = path_length - 1
|
| 401 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 402 |
+
|
| 403 |
+
ptr = i + 1
|
| 404 |
+
path_idx += 1
|
| 405 |
+
|
| 406 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 407 |
+
self.seq_act = np.array(self.seq_act)
|
| 408 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 409 |
+
|
| 410 |
+
self.seq_val = np.copy(self.seq_rew)
|
| 411 |
+
for i in reversed(range(max_path_length - 1)):
|
| 412 |
+
self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
|
| 413 |
+
|
| 414 |
+
print(f"max discounted return: {self.seq_val.max()}")
|
| 415 |
+
print(f"min discounted return: {self.seq_val.min()}")
|
| 416 |
+
|
| 417 |
+
# val \in [-1, 1]
|
| 418 |
+
self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
|
| 419 |
+
if center_mapping:
|
| 420 |
+
self.seq_val = self.seq_val * 2 - 1
|
| 421 |
+
print(f"max normed discounted return: {self.seq_val.max()}")
|
| 422 |
+
print(f"min normed discounted return: {self.seq_val.min()}")
|
| 423 |
+
|
| 424 |
+
def get_normalizer(self):
|
| 425 |
+
return self.normalizers["state"]
|
| 426 |
+
|
| 427 |
+
def __len__(self):
|
| 428 |
+
return len(self.indices)
|
| 429 |
+
|
| 430 |
+
def __getitem__(self, idx: int):
|
| 431 |
+
path_idx, start, end = self.indices[idx]
|
| 432 |
+
|
| 433 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride]
|
| 434 |
+
|
| 435 |
+
data = {
|
| 436 |
+
'obs': {'state': horizon_state},
|
| 437 |
+
'act': self.seq_act[path_idx, start:end:self.stride],
|
| 438 |
+
'rew': self.seq_rew[path_idx, start:end:self.stride],
|
| 439 |
+
'val': self.seq_val[path_idx, start],
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 443 |
+
|
| 444 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_maze2d_dataset.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 7 |
+
from cleandiffuser.utils import GaussianNormalizer, dict_apply
|
| 8 |
+
|
| 9 |
+
class DV_D4RLMaze2DSeqDataset(BaseDataset):
|
| 10 |
+
""" **D4RL-Maze2D Sequential Dataset**
|
| 11 |
+
|
| 12 |
+
torch.utils.data.Dataset wrapper for D4RL-Maze2D dataset.
|
| 13 |
+
Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
|
| 14 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 15 |
+
Each batch contains
|
| 16 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 17 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 18 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 19 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
dataset: Dict[str, np.ndarray],
|
| 23 |
+
D4RL-Maze2D dataset. Obtained by calling `env.get_dataset()`.
|
| 24 |
+
horizon: int,
|
| 25 |
+
Length of each sequence. Default is 1.
|
| 26 |
+
max_path_length: int,
|
| 27 |
+
Maximum length of the episodes. Default is 1001.
|
| 28 |
+
noreaching_penalty: float,
|
| 29 |
+
Penalty for not reaching the goal. Default is -100.
|
| 30 |
+
discount: float,
|
| 31 |
+
Discount factor. Default is 0.99.
|
| 32 |
+
|
| 33 |
+
Examples:
|
| 34 |
+
>>> env = gym.make("Maze2D-medium-play-v2")
|
| 35 |
+
>>> dataset = D4RLMaze2DDataset(env.get_dataset(), horizon=32)
|
| 36 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 37 |
+
>>> batch = next(iter(dataloader))
|
| 38 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 29)
|
| 39 |
+
>>> act = batch["act"] # (32, 32, 8)
|
| 40 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 41 |
+
>>> val = batch["val"] # (32, 1)
|
| 42 |
+
|
| 43 |
+
>>> normalizer = dataset.get_normalizer()
|
| 44 |
+
>>> obs = env.reset()[None, :]
|
| 45 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 46 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 47 |
+
"""
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
dataset: Dict[str, np.ndarray],
|
| 51 |
+
horizon: int = 1,
|
| 52 |
+
max_path_length: int = 800,
|
| 53 |
+
discount: float = 0.99,
|
| 54 |
+
continous_reward_at_done: bool = False,
|
| 55 |
+
reward_tune: str = "iql",
|
| 56 |
+
center_mapping: bool = True,
|
| 57 |
+
learn_policy: bool = False,
|
| 58 |
+
stride: int = 1,
|
| 59 |
+
):
|
| 60 |
+
super().__init__()
|
| 61 |
+
|
| 62 |
+
self.max_path_length = max_path_length
|
| 63 |
+
|
| 64 |
+
observations, actions, rewards, timeouts = (
|
| 65 |
+
dataset["observations"].astype(np.float32),
|
| 66 |
+
dataset["actions"].astype(np.float32),
|
| 67 |
+
dataset["rewards"].astype(np.float32),
|
| 68 |
+
dataset["timeouts"].astype(np.float32))
|
| 69 |
+
self.learn_policy = learn_policy
|
| 70 |
+
self.stride = stride
|
| 71 |
+
|
| 72 |
+
self.normalizers = {
|
| 73 |
+
"state": GaussianNormalizer(observations)}
|
| 74 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 75 |
+
|
| 76 |
+
self.horizon = horizon
|
| 77 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 78 |
+
|
| 79 |
+
self.indices = []
|
| 80 |
+
self.seq_obs, self.seq_act, self.seq_rew = [], [], []
|
| 81 |
+
|
| 82 |
+
path_idx = 0
|
| 83 |
+
|
| 84 |
+
self.paths = []
|
| 85 |
+
next_end = [-1] * (timeouts.shape[0] + 1)
|
| 86 |
+
next_start = [-1] * (timeouts.shape[0] + 1)
|
| 87 |
+
for index in reversed(range(timeouts.shape[0])):
|
| 88 |
+
if rewards[index] == 1.0:
|
| 89 |
+
next_end[index] = index
|
| 90 |
+
next_start[index] = next_start[index+1]
|
| 91 |
+
else:
|
| 92 |
+
next_end[index] = next_end[index+1]
|
| 93 |
+
next_start[index] = index
|
| 94 |
+
|
| 95 |
+
path_start = next_start[0]
|
| 96 |
+
path_end = next_end[path_start]
|
| 97 |
+
|
| 98 |
+
if self.learn_policy:
|
| 99 |
+
for path_start in range(0, timeouts.shape[0], max_path_length):
|
| 100 |
+
path_end = min(path_start + max_path_length - 1, timeouts.shape[0] - 1)
|
| 101 |
+
path_length = path_end - path_start + 1
|
| 102 |
+
|
| 103 |
+
_seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
|
| 104 |
+
_seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
|
| 105 |
+
_seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 106 |
+
|
| 107 |
+
_seq_obs[:path_length] = normed_observations[path_start:path_end+1]
|
| 108 |
+
_seq_act[:path_length] = actions[path_start:path_end+1]
|
| 109 |
+
_seq_rew[:path_length] = rewards[path_start:path_end+1][:, None]
|
| 110 |
+
|
| 111 |
+
_seq_obs[path_length:] = normed_observations[path_end] # repeat state
|
| 112 |
+
_seq_act[path_length:] = 0 # zero action
|
| 113 |
+
_seq_rew[path_length:] = 1 if continous_reward_at_done else 0
|
| 114 |
+
|
| 115 |
+
self.seq_obs.append(_seq_obs)
|
| 116 |
+
self.seq_act.append(_seq_act)
|
| 117 |
+
self.seq_rew.append(_seq_rew)
|
| 118 |
+
|
| 119 |
+
max_start = path_length - 1
|
| 120 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 121 |
+
self.paths.append((path_start, path_end))
|
| 122 |
+
path_idx += 1
|
| 123 |
+
|
| 124 |
+
else:
|
| 125 |
+
while path_end != -1:
|
| 126 |
+
path_start = max(path_start, path_end - max_path_length + 1)
|
| 127 |
+
path_length = path_end - path_start + 1
|
| 128 |
+
assert path_length >= 2
|
| 129 |
+
|
| 130 |
+
_seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
|
| 131 |
+
_seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
|
| 132 |
+
_seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
|
| 133 |
+
|
| 134 |
+
_seq_obs[:path_length] = normed_observations[path_start:path_end+1]
|
| 135 |
+
_seq_act[:path_length] = actions[path_start:path_end+1]
|
| 136 |
+
_seq_rew[:path_length] = rewards[path_start:path_end+1][:, None]
|
| 137 |
+
|
| 138 |
+
_seq_obs[path_length:] = normed_observations[path_end] # repeat state
|
| 139 |
+
_seq_act[path_length:] = 0 # zero action
|
| 140 |
+
_seq_rew[path_length:] = 1 if continous_reward_at_done else 0
|
| 141 |
+
|
| 142 |
+
self.seq_obs.append(_seq_obs)
|
| 143 |
+
self.seq_act.append(_seq_act)
|
| 144 |
+
self.seq_rew.append(_seq_rew)
|
| 145 |
+
|
| 146 |
+
max_start = path_length - 1
|
| 147 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 148 |
+
self.paths.append((path_start, path_end))
|
| 149 |
+
path_idx += 1
|
| 150 |
+
|
| 151 |
+
path_start = next_start[path_end]
|
| 152 |
+
path_end = next_end[path_start]
|
| 153 |
+
|
| 154 |
+
self.seq_obs = np.array(self.seq_obs)
|
| 155 |
+
self.seq_act = np.array(self.seq_act)
|
| 156 |
+
self.seq_rew = np.array(self.seq_rew)
|
| 157 |
+
|
| 158 |
+
if reward_tune == "iql":
|
| 159 |
+
self.seq_rew += -1
|
| 160 |
+
elif reward_tune == "none":
|
| 161 |
+
self.seq_rew = self.seq_rew
|
| 162 |
+
else:
|
| 163 |
+
raise ValueError(f"reward_tune: {reward_tune} is not supported.")
|
| 164 |
+
|
| 165 |
+
self.seq_val = np.copy(self.seq_rew)
|
| 166 |
+
print(self.seq_obs.shape)
|
| 167 |
+
for i in reversed(range(max_path_length - 1)):
|
| 168 |
+
self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
|
| 169 |
+
|
| 170 |
+
print(f"max discounted return: {self.seq_val.max()}")
|
| 171 |
+
print(f"min discounted return: {self.seq_val.min()}")
|
| 172 |
+
|
| 173 |
+
# val \in [-1, 1]
|
| 174 |
+
self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
|
| 175 |
+
if center_mapping:
|
| 176 |
+
self.seq_val = self.seq_val * 2 - 1
|
| 177 |
+
print(f"max normed discounted return: {self.seq_val.max()}")
|
| 178 |
+
print(f"min normed discounted return: {self.seq_val.min()}")
|
| 179 |
+
|
| 180 |
+
def get_normalizer(self):
|
| 181 |
+
return self.normalizers["state"]
|
| 182 |
+
|
| 183 |
+
def __len__(self):
|
| 184 |
+
return len(self.indices)
|
| 185 |
+
|
| 186 |
+
def __getitem__(self, idx: int):
|
| 187 |
+
path_idx, start, end = self.indices[idx]
|
| 188 |
+
|
| 189 |
+
if self.learn_policy:
|
| 190 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride].copy()
|
| 191 |
+
horizon_state[:, :2] -= horizon_state[0, :2]
|
| 192 |
+
else:
|
| 193 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride]
|
| 194 |
+
|
| 195 |
+
data = {
|
| 196 |
+
'obs': {'state': horizon_state},
|
| 197 |
+
'act': self.seq_act[path_idx, start:end:self.stride],
|
| 198 |
+
'rew': self.seq_rew[path_idx, start:end:self.stride],
|
| 199 |
+
'val': self.seq_val[path_idx, start],
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 203 |
+
|
| 204 |
+
return torch_data
|
| 205 |
+
|
| 206 |
+
class D4RLMaze2DTDDataset(BaseDataset):
|
| 207 |
+
""" **D4RL-Kitchen Transition Dataset**
|
| 208 |
+
|
| 209 |
+
torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
|
| 210 |
+
Chunk the dataset into transitions.
|
| 211 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 212 |
+
Each batch contains
|
| 213 |
+
- batch["obs"]["state"], observation of shape (batch_size, o_dim)
|
| 214 |
+
- batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
|
| 215 |
+
- batch["act"], action of shape (batch_size, a_dim)
|
| 216 |
+
- batch["rew"], reward of shape (batch_size, 1)
|
| 217 |
+
- batch["tml"], terminal of shape (batch_size, 1)
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
dataset: Dict[str, np.ndarray],
|
| 221 |
+
D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
|
| 222 |
+
|
| 223 |
+
Examples:
|
| 224 |
+
>>> env = gym.make("kitchen-mixed-v0")
|
| 225 |
+
>>> dataset = D4RLKitchenTDDataset(d4rl.qlearning_dataset(env))
|
| 226 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 227 |
+
>>> batch = next(iter(dataloader))
|
| 228 |
+
>>> obs = batch["obs"]["state"] # (32, 60)
|
| 229 |
+
>>> act = batch["act"] # (32, 9)
|
| 230 |
+
>>> rew = batch["rew"] # (32, 1)
|
| 231 |
+
>>> tml = batch["tml"] # (32, 1)
|
| 232 |
+
>>> next_obs = batch["next_obs"]["state"] # (32, 60)
|
| 233 |
+
|
| 234 |
+
>>> normalizer = dataset.get_normalizer()
|
| 235 |
+
>>> obs = env.reset()[None, :]
|
| 236 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 237 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 238 |
+
"""
|
| 239 |
+
def __init__(self, dataset: Dict[str, np.ndarray], reward_tune: str = "iql"):
|
| 240 |
+
super().__init__()
|
| 241 |
+
|
| 242 |
+
observations, actions, next_observations, rewards, terminals = (
|
| 243 |
+
dataset["observations"].astype(np.float32),
|
| 244 |
+
dataset["actions"].astype(np.float32),
|
| 245 |
+
dataset["next_observations"].astype(np.float32),
|
| 246 |
+
dataset["rewards"].astype(np.float32),
|
| 247 |
+
dataset["terminals"].astype(np.float32))
|
| 248 |
+
|
| 249 |
+
if reward_tune == "iql":
|
| 250 |
+
rewards = rewards - 1.
|
| 251 |
+
elif reward_tune == "cql":
|
| 252 |
+
rewards = (rewards - 0.5) * 4.
|
| 253 |
+
elif reward_tune == "antmaze":
|
| 254 |
+
rewards = (rewards - 0.25) * 2.
|
| 255 |
+
elif reward_tune == "none":
|
| 256 |
+
rewards = rewards
|
| 257 |
+
else:
|
| 258 |
+
raise ValueError(f"reward_tune: {reward_tune} is not supported.")
|
| 259 |
+
|
| 260 |
+
self.normalizers = {
|
| 261 |
+
"state": GaussianNormalizer(observations)}
|
| 262 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 263 |
+
normed_next_observations = self.normalizers["state"].normalize(next_observations)
|
| 264 |
+
|
| 265 |
+
self.obs = torch.tensor(normed_observations)
|
| 266 |
+
self.act = torch.tensor(actions)
|
| 267 |
+
self.rew = torch.tensor(rewards)[:, None]
|
| 268 |
+
self.tml = torch.tensor(terminals)[:, None]
|
| 269 |
+
self.next_obs = torch.tensor(normed_next_observations)
|
| 270 |
+
|
| 271 |
+
self.size = self.obs.shape[0]
|
| 272 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 273 |
+
|
| 274 |
+
def get_normalizer(self):
|
| 275 |
+
return self.normalizers["state"]
|
| 276 |
+
|
| 277 |
+
def __len__(self):
|
| 278 |
+
return self.size
|
| 279 |
+
|
| 280 |
+
def __getitem__(self, idx: int):
|
| 281 |
+
data = {
|
| 282 |
+
'obs': {
|
| 283 |
+
'state': self.obs[idx], },
|
| 284 |
+
'next_obs': {
|
| 285 |
+
'state': self.next_obs[idx], },
|
| 286 |
+
'act': self.act[idx],
|
| 287 |
+
'rew': self.rew[idx],
|
| 288 |
+
'tml': self.tml[idx], }
|
| 289 |
+
|
| 290 |
+
return data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_mujoco_dataset.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 7 |
+
from cleandiffuser.utils import GaussianNormalizer, dict_apply
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def return_reward_range(dataset, max_episode_steps):
|
| 11 |
+
""" Return the range of episodic returns in the D4RL-MuJoCo dataset. """
|
| 12 |
+
returns, lengths = [], []
|
| 13 |
+
ep_ret, ep_len = 0.0, 0
|
| 14 |
+
for r, d in zip(dataset["rewards"], dataset["terminals"]):
|
| 15 |
+
ep_ret += float(r)
|
| 16 |
+
ep_len += 1
|
| 17 |
+
if d or ep_len == max_episode_steps:
|
| 18 |
+
returns.append(ep_ret)
|
| 19 |
+
lengths.append(ep_len)
|
| 20 |
+
ep_ret, ep_len = 0.0, 0
|
| 21 |
+
lengths.append(ep_len) # but still keep track of number of steps
|
| 22 |
+
assert sum(lengths) == len(dataset["rewards"])
|
| 23 |
+
return min(returns), max(returns)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def modify_reward(dataset, max_episode_steps=1000):
|
| 27 |
+
""" Modify the episodic return scale of the D4RL-MuJoCo dataset to be within [0, max_episode_steps]. """
|
| 28 |
+
min_ret, max_ret = return_reward_range(dataset, max_episode_steps)
|
| 29 |
+
dataset["rewards"] /= max_ret - min_ret
|
| 30 |
+
dataset["rewards"] *= max_episode_steps
|
| 31 |
+
return dataset
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class D4RLMuJoCoDataset(BaseDataset):
|
| 35 |
+
""" **D4RL-MuJoCo Sequential Dataset**
|
| 36 |
+
|
| 37 |
+
torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
|
| 38 |
+
Chunk the dataset into sequences of length `horizon` without padding.
|
| 39 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 40 |
+
Each batch contains
|
| 41 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 42 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 43 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 44 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
dataset: Dict[str, np.ndarray],
|
| 48 |
+
D4RL-MuJoCo dataset. Obtained by calling `env.get_dataset()`.
|
| 49 |
+
terminal_penalty: float,
|
| 50 |
+
Penalty reward for early-terminal states. Default is -100.
|
| 51 |
+
horizon: int,
|
| 52 |
+
Length of each sequence. Default is 1.
|
| 53 |
+
max_path_length: int,
|
| 54 |
+
Maximum length of the episodes. Default is 1000.
|
| 55 |
+
discount: float,
|
| 56 |
+
Discount factor. Default is 0.99.
|
| 57 |
+
|
| 58 |
+
Examples:
|
| 59 |
+
>>> env = gym.make("halfcheetah-medium-expert-v2")
|
| 60 |
+
>>> dataset = D4RLMuJoCoDataset(env.get_dataset(), horizon=32)
|
| 61 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 62 |
+
>>> batch = next(iter(dataloader))
|
| 63 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 17)
|
| 64 |
+
>>> act = batch["act"] # (32, 32, 6)
|
| 65 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 66 |
+
>>> val = batch["val"] # (32, 1)
|
| 67 |
+
|
| 68 |
+
>>> normalizer = dataset.get_normalizer()
|
| 69 |
+
>>> obs = env.reset()[None, :]
|
| 70 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 71 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 72 |
+
"""
|
| 73 |
+
def __init__(
|
| 74 |
+
self,
|
| 75 |
+
dataset: Dict[str, np.ndarray],
|
| 76 |
+
terminal_penalty: float = -100.,
|
| 77 |
+
horizon: int = 1,
|
| 78 |
+
max_path_length: int = 1000,
|
| 79 |
+
discount: float = 0.99,
|
| 80 |
+
):
|
| 81 |
+
super().__init__()
|
| 82 |
+
|
| 83 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 84 |
+
dataset["observations"].astype(np.float32),
|
| 85 |
+
dataset["actions"].astype(np.float32),
|
| 86 |
+
dataset["rewards"].astype(np.float32),
|
| 87 |
+
dataset["timeouts"],
|
| 88 |
+
dataset["terminals"])
|
| 89 |
+
self.normalizers = {
|
| 90 |
+
"state": GaussianNormalizer(observations)}
|
| 91 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 92 |
+
|
| 93 |
+
self.horizon = horizon
|
| 94 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 95 |
+
|
| 96 |
+
n_paths = np.sum(np.logical_or(terminals, timeouts))
|
| 97 |
+
self.seq_obs = np.zeros((n_paths, max_path_length, self.o_dim), dtype=np.float32)
|
| 98 |
+
self.seq_act = np.zeros((n_paths, max_path_length, self.a_dim), dtype=np.float32)
|
| 99 |
+
self.seq_rew = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
|
| 100 |
+
self.seq_val = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
|
| 101 |
+
self.tml_and_not_timeout = []
|
| 102 |
+
self.indices = []
|
| 103 |
+
|
| 104 |
+
path_lengths, ptr = [], 0
|
| 105 |
+
path_idx = 0
|
| 106 |
+
for i in range(timeouts.shape[0]):
|
| 107 |
+
if timeouts[i] or terminals[i]:
|
| 108 |
+
path_lengths.append(i - ptr + 1)
|
| 109 |
+
|
| 110 |
+
if terminals[i] and not timeouts[i]:
|
| 111 |
+
rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
|
| 112 |
+
self.tml_and_not_timeout.append([path_idx, i - ptr])
|
| 113 |
+
|
| 114 |
+
self.seq_obs[path_idx, :i - ptr + 1] = normed_observations[ptr:i + 1]
|
| 115 |
+
self.seq_act[path_idx, :i - ptr + 1] = actions[ptr:i + 1]
|
| 116 |
+
self.seq_rew[path_idx, :i - ptr + 1] = rewards[ptr:i + 1][:, None]
|
| 117 |
+
|
| 118 |
+
max_start = min(path_lengths[-1] - 1, max_path_length - horizon)
|
| 119 |
+
self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
|
| 120 |
+
|
| 121 |
+
ptr = i + 1
|
| 122 |
+
path_idx += 1
|
| 123 |
+
|
| 124 |
+
self.seq_val[:, -1] = self.seq_rew[:, -1]
|
| 125 |
+
for i in range(max_path_length - 1):
|
| 126 |
+
self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
|
| 127 |
+
self.path_lengths = np.array(path_lengths)
|
| 128 |
+
self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
|
| 129 |
+
|
| 130 |
+
def get_normalizer(self):
|
| 131 |
+
return self.normalizers["state"]
|
| 132 |
+
|
| 133 |
+
def __len__(self):
|
| 134 |
+
return len(self.indices)
|
| 135 |
+
|
| 136 |
+
def __getitem__(self, idx: int):
|
| 137 |
+
path_idx, start, end = self.indices[idx]
|
| 138 |
+
|
| 139 |
+
data = {
|
| 140 |
+
'obs': {
|
| 141 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 142 |
+
'act': self.seq_act[path_idx, start:end],
|
| 143 |
+
'rew': self.seq_rew[path_idx, start:end],
|
| 144 |
+
'val': self.seq_val[path_idx, start],
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 148 |
+
|
| 149 |
+
return torch_data
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class D4RLMuJoCoTDDataset(BaseDataset):
|
| 153 |
+
""" **D4RL-MuJoCo Transition Dataset**
|
| 154 |
+
|
| 155 |
+
torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
|
| 156 |
+
Chunk the dataset into transitions.
|
| 157 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 158 |
+
Each batch contains
|
| 159 |
+
- batch["obs"]["state"], observation of shape (batch_size, o_dim)
|
| 160 |
+
- batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
|
| 161 |
+
- batch["act"], action of shape (batch_size, a_dim)
|
| 162 |
+
- batch["rew"], reward of shape (batch_size, 1)
|
| 163 |
+
- batch["tml"], terminal of shape (batch_size, 1)
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
dataset: Dict[str, np.ndarray],
|
| 167 |
+
D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
|
| 168 |
+
normalize_reward: bool,
|
| 169 |
+
Normalize the reward. Default is False.
|
| 170 |
+
|
| 171 |
+
Examples:
|
| 172 |
+
>>> env = gym.make("halfcheetah-medium-expert-v2")
|
| 173 |
+
>>> dataset = D4RLMuJoCoTDDataset(d4rl.qlearning_dataset(env))
|
| 174 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 175 |
+
>>> batch = next(iter(dataloader))
|
| 176 |
+
>>> obs = batch["obs"]["state"] # (32, 17)
|
| 177 |
+
>>> act = batch["act"] # (32, 6)
|
| 178 |
+
>>> rew = batch["rew"] # (32, 1)
|
| 179 |
+
>>> tml = batch["tml"] # (32, 1)
|
| 180 |
+
>>> next_obs = batch["next_obs"]["state"] # (32, 17)
|
| 181 |
+
|
| 182 |
+
>>> normalizer = dataset.get_normalizer()
|
| 183 |
+
>>> obs = env.reset()[None, :]
|
| 184 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 185 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 186 |
+
"""
|
| 187 |
+
def __init__(self, dataset: Dict[str, np.ndarray], normalize_reward: bool = False):
|
| 188 |
+
super().__init__()
|
| 189 |
+
if normalize_reward:
|
| 190 |
+
dataset = modify_reward(dataset, 1000)
|
| 191 |
+
|
| 192 |
+
observations, actions, next_observations, rewards, terminals = (
|
| 193 |
+
dataset["observations"].astype(np.float32),
|
| 194 |
+
dataset["actions"].astype(np.float32),
|
| 195 |
+
dataset["next_observations"].astype(np.float32),
|
| 196 |
+
dataset["rewards"].astype(np.float32),
|
| 197 |
+
dataset["terminals"].astype(np.float32))
|
| 198 |
+
|
| 199 |
+
self.normalizers = {
|
| 200 |
+
"state": GaussianNormalizer(observations)}
|
| 201 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 202 |
+
normed_next_observations = self.normalizers["state"].normalize(next_observations)
|
| 203 |
+
|
| 204 |
+
self.obs = torch.tensor(normed_observations, dtype=torch.float32)
|
| 205 |
+
self.act = torch.tensor(actions, dtype=torch.float32)
|
| 206 |
+
self.rew = torch.tensor(rewards, dtype=torch.float32)[:, None]
|
| 207 |
+
self.tml = torch.tensor(terminals, dtype=torch.float32)[:, None]
|
| 208 |
+
self.next_obs = torch.tensor(normed_next_observations, dtype=torch.float32)
|
| 209 |
+
|
| 210 |
+
self.size = self.obs.shape[0]
|
| 211 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 212 |
+
|
| 213 |
+
def get_normalizer(self):
|
| 214 |
+
return self.normalizers["state"]
|
| 215 |
+
|
| 216 |
+
def __len__(self):
|
| 217 |
+
return self.size
|
| 218 |
+
|
| 219 |
+
def __getitem__(self, idx: int):
|
| 220 |
+
data = {
|
| 221 |
+
'obs': {
|
| 222 |
+
'state': self.obs[idx], },
|
| 223 |
+
'next_obs': {
|
| 224 |
+
'state': self.next_obs[idx], },
|
| 225 |
+
'act': self.act[idx],
|
| 226 |
+
'rew': self.rew[idx],
|
| 227 |
+
'tml': self.tml[idx], }
|
| 228 |
+
|
| 229 |
+
return data
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
class MultiHorizonD4RLMuJoCoDataset(BaseDataset):
|
| 233 |
+
def __init__(
|
| 234 |
+
self,
|
| 235 |
+
dataset,
|
| 236 |
+
terminal_penalty=-100,
|
| 237 |
+
horizons=(10, 20),
|
| 238 |
+
max_path_length=1000,
|
| 239 |
+
discount=0.99,
|
| 240 |
+
):
|
| 241 |
+
super().__init__()
|
| 242 |
+
|
| 243 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 244 |
+
dataset["observations"].astype(np.float32),
|
| 245 |
+
dataset["actions"].astype(np.float32),
|
| 246 |
+
dataset["rewards"].astype(np.float32),
|
| 247 |
+
dataset["timeouts"],
|
| 248 |
+
dataset["terminals"])
|
| 249 |
+
self.normalizers = {
|
| 250 |
+
"state": GaussianNormalizer(observations)}
|
| 251 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 252 |
+
|
| 253 |
+
self.horizons = horizons
|
| 254 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 255 |
+
self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
|
| 256 |
+
|
| 257 |
+
n_paths = np.sum(np.logical_or(terminals, timeouts))
|
| 258 |
+
self.seq_obs = np.zeros((n_paths, max_path_length, self.o_dim), dtype=np.float32)
|
| 259 |
+
self.seq_act = np.zeros((n_paths, max_path_length, self.a_dim), dtype=np.float32)
|
| 260 |
+
self.seq_rew = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
|
| 261 |
+
self.seq_val = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
|
| 262 |
+
self.indices = [[] for _ in range(len(horizons))]
|
| 263 |
+
|
| 264 |
+
path_lengths, ptr = [], 0
|
| 265 |
+
path_idx = 0
|
| 266 |
+
for i in range(timeouts.shape[0]):
|
| 267 |
+
if timeouts[i] or terminals[i]:
|
| 268 |
+
path_lengths.append(i - ptr + 1)
|
| 269 |
+
|
| 270 |
+
if terminals[i] and not timeouts[i]:
|
| 271 |
+
rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
|
| 272 |
+
|
| 273 |
+
self.seq_obs[path_idx, :i - ptr + 1] = normed_observations[ptr:i + 1]
|
| 274 |
+
self.seq_act[path_idx, :i - ptr + 1] = actions[ptr:i + 1]
|
| 275 |
+
self.seq_rew[path_idx, :i - ptr + 1] = rewards[ptr:i + 1][:, None]
|
| 276 |
+
|
| 277 |
+
max_starts = [min(path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
|
| 278 |
+
for k in range(len(horizons)):
|
| 279 |
+
self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
|
| 280 |
+
|
| 281 |
+
ptr = i + 1
|
| 282 |
+
path_idx += 1
|
| 283 |
+
|
| 284 |
+
self.seq_val[:, -1] = self.seq_rew[:, -1]
|
| 285 |
+
for i in range(max_path_length - 1):
|
| 286 |
+
self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
|
| 287 |
+
self.path_lengths = np.array(path_lengths)
|
| 288 |
+
self.len_each_horizon = [len(indices) for indices in self.indices]
|
| 289 |
+
|
| 290 |
+
def get_normalizer(self):
|
| 291 |
+
return self.normalizers["state"]
|
| 292 |
+
|
| 293 |
+
def __len__(self):
|
| 294 |
+
return max(self.len_each_horizon)
|
| 295 |
+
|
| 296 |
+
def __getitem__(self, idx: int):
|
| 297 |
+
|
| 298 |
+
indices = [
|
| 299 |
+
int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
|
| 300 |
+
|
| 301 |
+
torch_datas = []
|
| 302 |
+
|
| 303 |
+
for i, horizon in enumerate(self.horizons):
|
| 304 |
+
|
| 305 |
+
path_idx, start, end = self.indices[i][indices[i]]
|
| 306 |
+
|
| 307 |
+
data = {
|
| 308 |
+
'obs': {
|
| 309 |
+
'state': self.seq_obs[path_idx, start:end]},
|
| 310 |
+
'act': self.seq_act[path_idx, start:end],
|
| 311 |
+
'val': self.seq_val[path_idx, start]}
|
| 312 |
+
|
| 313 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 314 |
+
|
| 315 |
+
torch_datas.append({
|
| 316 |
+
"horizon": horizon,
|
| 317 |
+
"data": torch_data,
|
| 318 |
+
})
|
| 319 |
+
|
| 320 |
+
return torch_datas
|
| 321 |
+
|
| 322 |
+
class DV_D4RLMuJoCoSeqDataset(BaseDataset):
|
| 323 |
+
""" **D4RL-MuJoCo Sequential Dataset**
|
| 324 |
+
|
| 325 |
+
torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
|
| 326 |
+
Chunk the dataset into sequences of length `horizon` without padding.
|
| 327 |
+
Use GaussianNormalizer to normalize the observations as default.
|
| 328 |
+
Each batch contains
|
| 329 |
+
- batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
|
| 330 |
+
- batch["act"], actions of shape (batch_size, horizon, a_dim)
|
| 331 |
+
- batch["rew"], rewards of shape (batch_size, horizon, 1)
|
| 332 |
+
- batch["val"], Monte Carlo return of shape (batch_size, 1)
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
dataset: Dict[str, np.ndarray],
|
| 336 |
+
D4RL-MuJoCo dataset. Obtained by calling `env.get_dataset()`.
|
| 337 |
+
terminal_penalty: float,
|
| 338 |
+
Penalty reward for early-terminal states. Default is -100.
|
| 339 |
+
horizon: int,
|
| 340 |
+
Length of each sequence. Default is 1.
|
| 341 |
+
max_path_length: int,
|
| 342 |
+
Maximum length of the episodes. Default is 1000.
|
| 343 |
+
discount: float,
|
| 344 |
+
Discount factor. Default is 0.99.
|
| 345 |
+
|
| 346 |
+
Examples:
|
| 347 |
+
>>> env = gym.make("halfcheetah-medium-expert-v2")
|
| 348 |
+
>>> dataset = D4RLMuJoCoDataset(env.get_dataset(), horizon=32)
|
| 349 |
+
>>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
|
| 350 |
+
>>> batch = next(iter(dataloader))
|
| 351 |
+
>>> obs = batch["obs"]["state"] # (32, 32, 17)
|
| 352 |
+
>>> act = batch["act"] # (32, 32, 6)
|
| 353 |
+
>>> rew = batch["rew"] # (32, 32, 1)
|
| 354 |
+
>>> val = batch["val"] # (32, 1)
|
| 355 |
+
|
| 356 |
+
>>> normalizer = dataset.get_normalizer()
|
| 357 |
+
>>> obs = env.reset()[None, :]
|
| 358 |
+
>>> normed_obs = normalizer.normalize(obs)
|
| 359 |
+
>>> unnormed_obs = normalizer.unnormalize(normed_obs)
|
| 360 |
+
"""
|
| 361 |
+
def __init__(
|
| 362 |
+
self,
|
| 363 |
+
dataset: Dict[str, np.ndarray],
|
| 364 |
+
terminal_penalty: float = -100,
|
| 365 |
+
horizon: int = 1,
|
| 366 |
+
max_path_length: int = 1000,
|
| 367 |
+
discount: float = 0.99,
|
| 368 |
+
center_mapping: bool = True,
|
| 369 |
+
stride: int = 1,
|
| 370 |
+
full_traj_bonus: float = 100,
|
| 371 |
+
):
|
| 372 |
+
super().__init__()
|
| 373 |
+
|
| 374 |
+
observations, actions, rewards, timeouts, terminals = (
|
| 375 |
+
dataset["observations"].astype(np.float32),
|
| 376 |
+
dataset["actions"].astype(np.float32),
|
| 377 |
+
dataset["rewards"].astype(np.float32),
|
| 378 |
+
dataset["timeouts"].astype(np.float32),
|
| 379 |
+
dataset["terminals"].astype(np.float32))
|
| 380 |
+
self.stride = stride
|
| 381 |
+
|
| 382 |
+
self.normalizers = {
|
| 383 |
+
"state": GaussianNormalizer(observations)}
|
| 384 |
+
normed_observations = self.normalizers["state"].normalize(observations)
|
| 385 |
+
|
| 386 |
+
self.horizon = horizon
|
| 387 |
+
self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
|
| 388 |
+
|
| 389 |
+
n_paths = np.sum(np.logical_or(terminals, timeouts))
|
| 390 |
+
self.seq_obs = np.zeros((n_paths+1, max_path_length, self.o_dim), dtype=np.float32)
|
| 391 |
+
self.seq_act = np.zeros((n_paths+1, max_path_length, self.a_dim), dtype=np.float32)
|
| 392 |
+
self.seq_rew = np.zeros((n_paths+1, max_path_length, 1), dtype=np.float32)
|
| 393 |
+
self.seq_val = np.zeros((n_paths+1, max_path_length, 1), dtype=np.float32)
|
| 394 |
+
self.indices = []
|
| 395 |
+
|
| 396 |
+
ptr = 0
|
| 397 |
+
path_idx = 0
|
| 398 |
+
for i in range(timeouts.shape[0]):
|
| 399 |
+
if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
|
| 400 |
+
path_length = i - ptr + 1
|
| 401 |
+
assert path_length <= max_path_length, f"current path length {path_length}"
|
| 402 |
+
|
| 403 |
+
if terminals[i]:
|
| 404 |
+
rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
|
| 405 |
+
|
| 406 |
+
if path_length == max_path_length:
|
| 407 |
+
rewards[i] = rewards[i] + full_traj_bonus if full_traj_bonus is not None else rewards[i]
|
| 408 |
+
|
| 409 |
+
self.seq_obs[path_idx, :path_length] = normed_observations[ptr:i + 1]
|
| 410 |
+
self.seq_act[path_idx, :path_length] = actions[ptr:i + 1]
|
| 411 |
+
self.seq_rew[path_idx, :path_length] = rewards[ptr:i + 1][:, None]
|
| 412 |
+
|
| 413 |
+
max_start = path_length - (horizon - 1) * stride - 1
|
| 414 |
+
self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
|
| 415 |
+
|
| 416 |
+
ptr = i + 1
|
| 417 |
+
path_idx += 1
|
| 418 |
+
|
| 419 |
+
self.seq_val[:, -1] = self.seq_rew[:, -1]
|
| 420 |
+
for i in reversed(range(max_path_length-1)):
|
| 421 |
+
self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
|
| 422 |
+
|
| 423 |
+
print(f"max discounted return: {self.seq_val.max()}")
|
| 424 |
+
print(f"min discounted return: {self.seq_val.min()}")
|
| 425 |
+
|
| 426 |
+
# val \in [-1, 1]
|
| 427 |
+
self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
|
| 428 |
+
if center_mapping:
|
| 429 |
+
self.seq_val = self.seq_val * 2 - 1
|
| 430 |
+
print(f"max normed discounted return: {self.seq_val.max()}")
|
| 431 |
+
print(f"min normed discounted return: {self.seq_val.min()}")
|
| 432 |
+
|
| 433 |
+
def get_normalizer(self):
|
| 434 |
+
return self.normalizers["state"]
|
| 435 |
+
|
| 436 |
+
def __len__(self):
|
| 437 |
+
return len(self.indices)
|
| 438 |
+
|
| 439 |
+
def __getitem__(self, idx: int):
|
| 440 |
+
path_idx, start, end = self.indices[idx]
|
| 441 |
+
|
| 442 |
+
horizon_state = self.seq_obs[path_idx, start:end:self.stride]
|
| 443 |
+
|
| 444 |
+
data = {
|
| 445 |
+
'obs': {'state': horizon_state},
|
| 446 |
+
'act': self.seq_act[path_idx, start:end:self.stride],
|
| 447 |
+
'rew': self.seq_rew[path_idx, start:end:self.stride],
|
| 448 |
+
'val': self.seq_val[path_idx, start],
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 452 |
+
|
| 453 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/dataset_utils.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, Callable, List, Union
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import scipy.interpolate as interpolate
|
| 5 |
+
import numba
|
| 6 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 7 |
+
import cleandiffuser.dataset.rotation_conversions as rc
|
| 8 |
+
import functools
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# -----------------------------------------------------------------------------#
|
| 12 |
+
# ------------------------------ SequenceSampler ------------------------------#
|
| 13 |
+
# -----------------------------------------------------------------------------#
|
| 14 |
+
|
| 15 |
+
# Original implemetation: https://github.com/real-stanford/diffusion_policy
|
| 16 |
+
# Observation Horizon: To|n_obs_steps
|
| 17 |
+
# Action Horizon: Ta|n_action_steps
|
| 18 |
+
# Prediction Horizon: T|horizon
|
| 19 |
+
# To = 3
|
| 20 |
+
# Ta = 4
|
| 21 |
+
# T = 6
|
| 22 |
+
# |o|o|o|
|
| 23 |
+
# | | |a|a|a|a|
|
| 24 |
+
# pad_before = 2
|
| 25 |
+
# pad_after = 3
|
| 26 |
+
|
| 27 |
+
@numba.jit(nopython=True)
|
| 28 |
+
def create_indices(
|
| 29 |
+
episode_ends: np.ndarray,
|
| 30 |
+
sequence_length: int,
|
| 31 |
+
pad_before: int = 0, pad_after: int = 0,
|
| 32 |
+
debug: bool = True) -> np.ndarray:
|
| 33 |
+
pad_before = min(max(pad_before, 0), sequence_length - 1)
|
| 34 |
+
pad_after = min(max(pad_after, 0), sequence_length - 1)
|
| 35 |
+
|
| 36 |
+
indices = list()
|
| 37 |
+
for i in range(len(episode_ends)):
|
| 38 |
+
start_idx = 0 # episode start index
|
| 39 |
+
if i > 0:
|
| 40 |
+
start_idx = episode_ends[i - 1]
|
| 41 |
+
end_idx = episode_ends[i] # episode end index
|
| 42 |
+
episode_length = end_idx - start_idx # episode length
|
| 43 |
+
|
| 44 |
+
min_start = -pad_before
|
| 45 |
+
max_start = episode_length - sequence_length + pad_after
|
| 46 |
+
|
| 47 |
+
# range stops one idx before end
|
| 48 |
+
for idx in range(min_start, max_start + 1):
|
| 49 |
+
buffer_start_idx = max(idx, 0) + start_idx
|
| 50 |
+
buffer_end_idx = min(idx + sequence_length, episode_length) + start_idx
|
| 51 |
+
start_offset = buffer_start_idx - (idx + start_idx)
|
| 52 |
+
end_offset = (idx + sequence_length + start_idx) - buffer_end_idx
|
| 53 |
+
sample_start_idx = 0 + start_offset
|
| 54 |
+
sample_end_idx = sequence_length - end_offset
|
| 55 |
+
if debug:
|
| 56 |
+
assert (start_offset >= 0)
|
| 57 |
+
assert (end_offset >= 0)
|
| 58 |
+
assert (sample_end_idx - sample_start_idx) == (buffer_end_idx - buffer_start_idx)
|
| 59 |
+
indices.append([
|
| 60 |
+
buffer_start_idx, buffer_end_idx,
|
| 61 |
+
sample_start_idx, sample_end_idx])
|
| 62 |
+
indices = np.array(indices)
|
| 63 |
+
return indices
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class SequenceSampler:
|
| 67 |
+
def __init__(
|
| 68 |
+
self,
|
| 69 |
+
replay_buffer: ReplayBuffer,
|
| 70 |
+
sequence_length: int,
|
| 71 |
+
pad_before: int = 0,
|
| 72 |
+
pad_after: int = 0,
|
| 73 |
+
keys=None,
|
| 74 |
+
key_first_k=dict(),
|
| 75 |
+
zero_padding: bool = False,
|
| 76 |
+
):
|
| 77 |
+
"""
|
| 78 |
+
key_first_k: dict str: int
|
| 79 |
+
Only take first k data from these keys (to improve perf)
|
| 80 |
+
"""
|
| 81 |
+
super().__init__()
|
| 82 |
+
assert (sequence_length >= 1)
|
| 83 |
+
|
| 84 |
+
# all keys
|
| 85 |
+
if keys is None:
|
| 86 |
+
keys = list(replay_buffer.keys())
|
| 87 |
+
|
| 88 |
+
episode_ends = replay_buffer.episode_ends[:]
|
| 89 |
+
|
| 90 |
+
# create indices
|
| 91 |
+
# indices (buffer_start_idx, buffer_end_idx, sample_start_idx, sample_end_idx)
|
| 92 |
+
# buffer_start_idx and buffer_end_idx define the actual start and end positions of the sample sequence within the original dataset.
|
| 93 |
+
# sample_start_idx and sample_end_idx define the relative start and end positions within the sample sequence,
|
| 94 |
+
# which is particularly useful when dealing with padding as it can affect the actual length of the sequence.
|
| 95 |
+
indices = create_indices(
|
| 96 |
+
episode_ends=episode_ends,
|
| 97 |
+
sequence_length=sequence_length,
|
| 98 |
+
pad_before=pad_before,
|
| 99 |
+
pad_after=pad_after,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
self.indices = indices
|
| 103 |
+
self.keys = list(keys) # prevent OmegaConf list performance problem
|
| 104 |
+
self.sequence_length = sequence_length
|
| 105 |
+
self.replay_buffer = replay_buffer
|
| 106 |
+
self.zero_padding = zero_padding
|
| 107 |
+
self.key_first_k = key_first_k
|
| 108 |
+
|
| 109 |
+
def __len__(self):
|
| 110 |
+
return len(self.indices)
|
| 111 |
+
|
| 112 |
+
def sample_sequence(self, idx):
|
| 113 |
+
buffer_start_idx, buffer_end_idx, sample_start_idx, sample_end_idx = self.indices[idx]
|
| 114 |
+
result = dict()
|
| 115 |
+
for key in self.keys:
|
| 116 |
+
input_arr = self.replay_buffer[key]
|
| 117 |
+
# performance optimization, avoid small allocation if possible
|
| 118 |
+
if key not in self.key_first_k:
|
| 119 |
+
sample = input_arr[buffer_start_idx:buffer_end_idx]
|
| 120 |
+
else:
|
| 121 |
+
# performance optimization, only load used obs steps
|
| 122 |
+
n_data = buffer_end_idx - buffer_start_idx
|
| 123 |
+
k_data = min(self.key_first_k[key], n_data)
|
| 124 |
+
# fill value with Nan to catch bugs
|
| 125 |
+
# the non-loaded region should never be used
|
| 126 |
+
sample = np.full((n_data,) + input_arr.shape[1:],
|
| 127 |
+
fill_value=np.nan, dtype=input_arr.dtype)
|
| 128 |
+
sample[:k_data] = input_arr[buffer_start_idx:buffer_start_idx + k_data]
|
| 129 |
+
data = sample
|
| 130 |
+
if (sample_start_idx > 0) or (sample_end_idx < self.sequence_length):
|
| 131 |
+
data = np.zeros(
|
| 132 |
+
shape=(self.sequence_length,) + input_arr.shape[1:],
|
| 133 |
+
dtype=input_arr.dtype)
|
| 134 |
+
if not self.zero_padding:
|
| 135 |
+
if sample_start_idx > 0:
|
| 136 |
+
data[:sample_start_idx] = sample[0]
|
| 137 |
+
if sample_end_idx < self.sequence_length:
|
| 138 |
+
data[sample_end_idx:] = sample[-1]
|
| 139 |
+
data[sample_start_idx:sample_end_idx] = sample
|
| 140 |
+
result[key] = data
|
| 141 |
+
return result
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# -----------------------------------------------------------------------------#
|
| 145 |
+
# ---------------------------- Rotation Transformer ---------------------------#
|
| 146 |
+
# -----------------------------------------------------------------------------#
|
| 147 |
+
|
| 148 |
+
class RotationTransformer:
|
| 149 |
+
valid_reps = [
|
| 150 |
+
'axis_angle',
|
| 151 |
+
'euler_angles',
|
| 152 |
+
'quaternion',
|
| 153 |
+
'rotation_6d',
|
| 154 |
+
'matrix'
|
| 155 |
+
]
|
| 156 |
+
|
| 157 |
+
def __init__(self,
|
| 158 |
+
from_rep='axis_angle',
|
| 159 |
+
to_rep='rotation_6d',
|
| 160 |
+
from_convention=None,
|
| 161 |
+
to_convention=None):
|
| 162 |
+
"""
|
| 163 |
+
Valid representations
|
| 164 |
+
|
| 165 |
+
Always use matrix as intermediate representation.
|
| 166 |
+
"""
|
| 167 |
+
assert from_rep != to_rep
|
| 168 |
+
assert from_rep in self.valid_reps
|
| 169 |
+
assert to_rep in self.valid_reps
|
| 170 |
+
if from_rep == 'euler_angles':
|
| 171 |
+
assert from_convention is not None
|
| 172 |
+
if to_rep == 'euler_angles':
|
| 173 |
+
assert to_convention is not None
|
| 174 |
+
|
| 175 |
+
forward_funcs = list()
|
| 176 |
+
inverse_funcs = list()
|
| 177 |
+
|
| 178 |
+
if from_rep != 'matrix':
|
| 179 |
+
funcs = [
|
| 180 |
+
getattr(rc, f'{from_rep}_to_matrix'),
|
| 181 |
+
getattr(rc, f'matrix_to_{from_rep}')
|
| 182 |
+
]
|
| 183 |
+
if from_convention is not None:
|
| 184 |
+
funcs = [functools.partial(func, convention=from_convention)
|
| 185 |
+
for func in funcs]
|
| 186 |
+
forward_funcs.append(funcs[0])
|
| 187 |
+
inverse_funcs.append(funcs[1])
|
| 188 |
+
|
| 189 |
+
if to_rep != 'matrix':
|
| 190 |
+
funcs = [
|
| 191 |
+
getattr(rc, f'matrix_to_{to_rep}'),
|
| 192 |
+
getattr(rc, f'{to_rep}_to_matrix')
|
| 193 |
+
]
|
| 194 |
+
if to_convention is not None:
|
| 195 |
+
funcs = [functools.partial(func, convention=to_convention)
|
| 196 |
+
for func in funcs]
|
| 197 |
+
forward_funcs.append(funcs[0])
|
| 198 |
+
inverse_funcs.append(funcs[1])
|
| 199 |
+
|
| 200 |
+
inverse_funcs = inverse_funcs[::-1]
|
| 201 |
+
|
| 202 |
+
self.forward_funcs = forward_funcs
|
| 203 |
+
self.inverse_funcs = inverse_funcs
|
| 204 |
+
|
| 205 |
+
@staticmethod
|
| 206 |
+
def _apply_funcs(x: Union[np.ndarray, torch.Tensor], funcs: list) -> Union[np.ndarray, torch.Tensor]:
|
| 207 |
+
x_ = x
|
| 208 |
+
if isinstance(x, np.ndarray):
|
| 209 |
+
x_ = torch.tensor(x)
|
| 210 |
+
x_: torch.Tensor
|
| 211 |
+
for func in funcs:
|
| 212 |
+
x_ = func(x_)
|
| 213 |
+
y = x_
|
| 214 |
+
if isinstance(x, np.ndarray):
|
| 215 |
+
y = x_.numpy()
|
| 216 |
+
return y
|
| 217 |
+
|
| 218 |
+
def forward(self, x: Union[np.ndarray, torch.Tensor]
|
| 219 |
+
) -> Union[np.ndarray, torch.Tensor]:
|
| 220 |
+
return self._apply_funcs(x, self.forward_funcs)
|
| 221 |
+
|
| 222 |
+
def inverse(self, x: Union[np.ndarray, torch.Tensor]
|
| 223 |
+
) -> Union[np.ndarray, torch.Tensor]:
|
| 224 |
+
return self._apply_funcs(x, self.inverse_funcs)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
# -----------------------------------------------------------------------------#
|
| 228 |
+
# --------------------------- multi-field normalizer --------------------------#
|
| 229 |
+
# -----------------------------------------------------------------------------#
|
| 230 |
+
|
| 231 |
+
def empirical_cdf(sample):
|
| 232 |
+
""" https://stackoverflow.com/a/33346366 """
|
| 233 |
+
|
| 234 |
+
# find the unique values and their corresponding counts
|
| 235 |
+
quantiles, counts = np.unique(sample, return_counts=True)
|
| 236 |
+
|
| 237 |
+
# take the cumulative sum of the counts and divide by the sample size to
|
| 238 |
+
# get the cumulative probabilities between 0 and 1
|
| 239 |
+
cumprob = np.cumsum(counts).astype(np.double) / sample.size
|
| 240 |
+
|
| 241 |
+
return quantiles, cumprob
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class CDFNormalizer1d:
|
| 245 |
+
"""
|
| 246 |
+
CDF normalizer for a single dimension
|
| 247 |
+
"""
|
| 248 |
+
|
| 249 |
+
def __init__(self, X):
|
| 250 |
+
assert X.ndim == 1
|
| 251 |
+
self.X = X.astype(np.float32)
|
| 252 |
+
quantiles, cumprob = empirical_cdf(self.X)
|
| 253 |
+
self.fn = interpolate.interp1d(quantiles, cumprob)
|
| 254 |
+
self.inv = interpolate.interp1d(cumprob, quantiles)
|
| 255 |
+
self.xmin, self.xmax = quantiles.min(), quantiles.max()
|
| 256 |
+
self.ymin, self.ymax = cumprob.min(), cumprob.max()
|
| 257 |
+
|
| 258 |
+
def normalize(self, x):
|
| 259 |
+
x = np.clip(x, self.xmin, self.xmax)
|
| 260 |
+
y = self.fn(x)
|
| 261 |
+
y = 2 * y - 1
|
| 262 |
+
return y
|
| 263 |
+
|
| 264 |
+
def unnormalize(self, x, eps=1e-4):
|
| 265 |
+
x = (x + 1) / 2.
|
| 266 |
+
if (x < self.ymin - eps).any() or (x > self.ymax + eps).any():
|
| 267 |
+
print(
|
| 268 |
+
f'''[ dataset/normalization ] Warning: out of range in unnormalize: '''
|
| 269 |
+
f'''[{x.min()}, {x.max()}] | '''
|
| 270 |
+
f'''x : [{self.xmin}, {self.xmax}] | '''
|
| 271 |
+
f'''y: [{self.ymin}, {self.ymax}]''')
|
| 272 |
+
x = np.clip(x, self.ymin, self.ymax)
|
| 273 |
+
y = self.inv(x)
|
| 274 |
+
return y
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
class CDFNormalizer:
|
| 278 |
+
"""
|
| 279 |
+
makes training data uniform (over each dimension) by transforming it with marginal CDFs
|
| 280 |
+
"""
|
| 281 |
+
|
| 282 |
+
def __init__(self, X):
|
| 283 |
+
self.X = X.astype(np.float32)
|
| 284 |
+
self.mins, self.maxs = X.min(0), X.max(0)
|
| 285 |
+
self.dim = X.shape[-1]
|
| 286 |
+
self.cdfs = [
|
| 287 |
+
CDFNormalizer1d(self.X[:, i])
|
| 288 |
+
for i in range(self.dim)]
|
| 289 |
+
|
| 290 |
+
def wrap(self, fn_name, x):
|
| 291 |
+
shape = x.shape
|
| 292 |
+
x = x.reshape(-1, self.dim)
|
| 293 |
+
out = np.zeros_like(x)
|
| 294 |
+
for i, cdf in enumerate(self.cdfs):
|
| 295 |
+
fn = getattr(cdf, fn_name)
|
| 296 |
+
out[:, i] = fn(x[:, i])
|
| 297 |
+
return out.reshape(shape)
|
| 298 |
+
|
| 299 |
+
def normalize(self, x):
|
| 300 |
+
return self.wrap('normalize', x)
|
| 301 |
+
|
| 302 |
+
def unnormalize(self, x):
|
| 303 |
+
return self.wrap('unnormalize', x)
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
class GaussianNormalizer:
|
| 307 |
+
"""
|
| 308 |
+
normalizes data to have zero mean and unit variance
|
| 309 |
+
"""
|
| 310 |
+
|
| 311 |
+
def __init__(self, X):
|
| 312 |
+
self.X = X.astype(np.float32)
|
| 313 |
+
self.means, self.stds = X.mean(0), X.std(0)
|
| 314 |
+
self.stds[self.stds == 0] = 1.
|
| 315 |
+
|
| 316 |
+
def normalize(self, x):
|
| 317 |
+
return (x - self.means[None,]) / self.stds[None,]
|
| 318 |
+
|
| 319 |
+
def unnormalize(self, x):
|
| 320 |
+
return x * self.stds[None,] + self.means[None,]
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
class ImageNormalizer:
|
| 324 |
+
"""
|
| 325 |
+
normalizes image data from range [0, 1] to [-1, 1].
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
def __init__(self):
|
| 329 |
+
pass
|
| 330 |
+
|
| 331 |
+
def normalize(self, x):
|
| 332 |
+
return x * 2.0 - 1.0
|
| 333 |
+
|
| 334 |
+
def unnormalize(self, x):
|
| 335 |
+
return (x + 1.0) / 2.0
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class MinMaxNormalizer:
|
| 339 |
+
"""
|
| 340 |
+
normalizes data through maximum and minimum expansion.
|
| 341 |
+
"""
|
| 342 |
+
|
| 343 |
+
def __init__(self, X):
|
| 344 |
+
X = X.reshape(-1, X.shape[-1]).astype(np.float32)
|
| 345 |
+
self.min, self.max = np.min(X, axis=0), np.max(X, axis=0)
|
| 346 |
+
self.range = self.max - self.min
|
| 347 |
+
if np.any(self.range == 0):
|
| 348 |
+
self.range = self.max - self.min
|
| 349 |
+
print("Warning: Some features have the same min and max value. These will be set to 0.")
|
| 350 |
+
self.range[self.range == 0] = 1
|
| 351 |
+
|
| 352 |
+
def normalize(self, x):
|
| 353 |
+
x = x.astype(np.float32)
|
| 354 |
+
# nomalize to [0,1]
|
| 355 |
+
nx = (x - self.min) / self.range
|
| 356 |
+
# normalize to [-1, 1]
|
| 357 |
+
nx = nx * 2 - 1
|
| 358 |
+
return nx
|
| 359 |
+
|
| 360 |
+
def unnormalize(self, x):
|
| 361 |
+
x = x.astype(np.float32)
|
| 362 |
+
nx = (x + 1) / 2
|
| 363 |
+
x = nx * self.range + self.min
|
| 364 |
+
return x
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
class EmptyNormalizer:
|
| 368 |
+
"""
|
| 369 |
+
do nothing and change nothing
|
| 370 |
+
"""
|
| 371 |
+
|
| 372 |
+
def __init__(self):
|
| 373 |
+
pass
|
| 374 |
+
|
| 375 |
+
def normalize(self, x):
|
| 376 |
+
return x
|
| 377 |
+
|
| 378 |
+
def unnormalize(self, x):
|
| 379 |
+
return x
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
# -----------------------------------------------------------------------------#
|
| 383 |
+
# ------------------------------- useful tool ---------------------------------#
|
| 384 |
+
# -----------------------------------------------------------------------------#
|
| 385 |
+
|
| 386 |
+
def dict_apply(
|
| 387 |
+
x: Dict[str, torch.Tensor],
|
| 388 |
+
func: Callable[[torch.Tensor], torch.Tensor]
|
| 389 |
+
) -> Dict[str, torch.Tensor]:
|
| 390 |
+
result = dict()
|
| 391 |
+
for key, value in x.items():
|
| 392 |
+
if isinstance(value, dict):
|
| 393 |
+
result[key] = dict_apply(value, func)
|
| 394 |
+
elif value is None:
|
| 395 |
+
result[key] = None
|
| 396 |
+
else:
|
| 397 |
+
result[key] = func(value)
|
| 398 |
+
return result
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def loop_dataloader(dl):
|
| 402 |
+
while True:
|
| 403 |
+
for b in dl:
|
| 404 |
+
yield b
|
| 405 |
+
|
| 406 |
+
def loop_two_dataloaders(dl1, dl2):
|
| 407 |
+
while True:
|
| 408 |
+
for b1, b2 in zip(dl1, dl2):
|
| 409 |
+
yield b1, b2
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/imagecodecs.py
ADDED
|
@@ -0,0 +1,1385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# imagecodecs/numcodecs.py
|
| 2 |
+
|
| 3 |
+
# Copyright (c) 2021-2022, Christoph Gohlke
|
| 4 |
+
# All rights reserved.
|
| 5 |
+
#
|
| 6 |
+
# Redistribution and use in source and binary forms, with or without
|
| 7 |
+
# modification, are permitted provided that the following conditions are met:
|
| 8 |
+
#
|
| 9 |
+
# 1. Redistributions of source code must retain the above copyright notice,
|
| 10 |
+
# this list of conditions and the following disclaimer.
|
| 11 |
+
#
|
| 12 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
# and/or other materials provided with the distribution.
|
| 15 |
+
#
|
| 16 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 17 |
+
# contributors may be used to endorse or promote products derived from
|
| 18 |
+
# this software without specific prior written permission.
|
| 19 |
+
#
|
| 20 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 23 |
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
| 24 |
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 25 |
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 26 |
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 27 |
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 28 |
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 29 |
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 30 |
+
# POSSIBILITY OF SUCH DAMAGE.
|
| 31 |
+
|
| 32 |
+
"""Additional numcodecs implemented using imagecodecs."""
|
| 33 |
+
|
| 34 |
+
__version__ = '2022.9.26'
|
| 35 |
+
|
| 36 |
+
__all__ = ('register_codecs',)
|
| 37 |
+
|
| 38 |
+
import numpy
|
| 39 |
+
from numcodecs.abc import Codec
|
| 40 |
+
from numcodecs.registry import register_codec, get_codec
|
| 41 |
+
|
| 42 |
+
import imagecodecs
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def protective_squeeze(x: numpy.ndarray):
|
| 46 |
+
"""
|
| 47 |
+
Squeeze dim only if it's not the last dim.
|
| 48 |
+
Image dim expected to be *, H, W, C
|
| 49 |
+
"""
|
| 50 |
+
img_shape = x.shape[-3:]
|
| 51 |
+
if len(x.shape) > 3:
|
| 52 |
+
n_imgs = numpy.prod(x.shape[:-3])
|
| 53 |
+
if n_imgs > 1:
|
| 54 |
+
img_shape = (-1,) + img_shape
|
| 55 |
+
return x.reshape(img_shape)
|
| 56 |
+
|
| 57 |
+
def get_default_image_compressor(**kwargs):
|
| 58 |
+
if imagecodecs.JPEGXL:
|
| 59 |
+
# has JPEGXL
|
| 60 |
+
this_kwargs = {
|
| 61 |
+
'effort': 3,
|
| 62 |
+
'distance': 0.3,
|
| 63 |
+
# bug in libjxl, invalid codestream for non-lossless
|
| 64 |
+
# when decoding speed > 1
|
| 65 |
+
'decodingspeed': 1
|
| 66 |
+
}
|
| 67 |
+
this_kwargs.update(kwargs)
|
| 68 |
+
return JpegXl(**this_kwargs)
|
| 69 |
+
else:
|
| 70 |
+
this_kwargs = {
|
| 71 |
+
'level': 50
|
| 72 |
+
}
|
| 73 |
+
this_kwargs.update(kwargs)
|
| 74 |
+
return Jpeg2k(**this_kwargs)
|
| 75 |
+
|
| 76 |
+
class Aec(Codec):
|
| 77 |
+
"""AEC codec for numcodecs."""
|
| 78 |
+
|
| 79 |
+
codec_id = 'imagecodecs_aec'
|
| 80 |
+
|
| 81 |
+
def __init__(
|
| 82 |
+
self, bitspersample=None, flags=None, blocksize=None, rsi=None
|
| 83 |
+
):
|
| 84 |
+
self.bitspersample = bitspersample
|
| 85 |
+
self.flags = flags
|
| 86 |
+
self.blocksize = blocksize
|
| 87 |
+
self.rsi = rsi
|
| 88 |
+
|
| 89 |
+
def encode(self, buf):
|
| 90 |
+
return imagecodecs.aec_encode(
|
| 91 |
+
buf,
|
| 92 |
+
bitspersample=self.bitspersample,
|
| 93 |
+
flags=self.flags,
|
| 94 |
+
blocksize=self.blocksize,
|
| 95 |
+
rsi=self.rsi,
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
def decode(self, buf, out=None):
|
| 99 |
+
return imagecodecs.aec_decode(
|
| 100 |
+
buf,
|
| 101 |
+
bitspersample=self.bitspersample,
|
| 102 |
+
flags=self.flags,
|
| 103 |
+
blocksize=self.blocksize,
|
| 104 |
+
rsi=self.rsi,
|
| 105 |
+
out=_flat(out),
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class Apng(Codec):
|
| 110 |
+
"""APNG codec for numcodecs."""
|
| 111 |
+
|
| 112 |
+
codec_id = 'imagecodecs_apng'
|
| 113 |
+
|
| 114 |
+
def __init__(self, level=None, photometric=None, delay=None):
|
| 115 |
+
self.level = level
|
| 116 |
+
self.photometric = photometric
|
| 117 |
+
self.delay = delay
|
| 118 |
+
|
| 119 |
+
def encode(self, buf):
|
| 120 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 121 |
+
return imagecodecs.apng_encode(
|
| 122 |
+
buf,
|
| 123 |
+
level=self.level,
|
| 124 |
+
photometric=self.photometric,
|
| 125 |
+
delay=self.delay,
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
def decode(self, buf, out=None):
|
| 129 |
+
return imagecodecs.apng_decode(buf, out=out)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class Avif(Codec):
|
| 133 |
+
"""AVIF codec for numcodecs."""
|
| 134 |
+
|
| 135 |
+
codec_id = 'imagecodecs_avif'
|
| 136 |
+
|
| 137 |
+
def __init__(
|
| 138 |
+
self,
|
| 139 |
+
level=None,
|
| 140 |
+
speed=None,
|
| 141 |
+
tilelog2=None,
|
| 142 |
+
bitspersample=None,
|
| 143 |
+
pixelformat=None,
|
| 144 |
+
numthreads=None,
|
| 145 |
+
index=None,
|
| 146 |
+
):
|
| 147 |
+
self.level = level
|
| 148 |
+
self.speed = speed
|
| 149 |
+
self.tilelog2 = tilelog2
|
| 150 |
+
self.bitspersample = bitspersample
|
| 151 |
+
self.pixelformat = pixelformat
|
| 152 |
+
self.numthreads = numthreads
|
| 153 |
+
self.index = index
|
| 154 |
+
|
| 155 |
+
def encode(self, buf):
|
| 156 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 157 |
+
return imagecodecs.avif_encode(
|
| 158 |
+
buf,
|
| 159 |
+
level=self.level,
|
| 160 |
+
speed=self.speed,
|
| 161 |
+
tilelog2=self.tilelog2,
|
| 162 |
+
bitspersample=self.bitspersample,
|
| 163 |
+
pixelformat=self.pixelformat,
|
| 164 |
+
numthreads=self.numthreads,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
def decode(self, buf, out=None):
|
| 168 |
+
return imagecodecs.avif_decode(
|
| 169 |
+
buf, index=self.index, numthreads=self.numthreads, out=out
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class Bitorder(Codec):
|
| 174 |
+
"""Bitorder codec for numcodecs."""
|
| 175 |
+
|
| 176 |
+
codec_id = 'imagecodecs_bitorder'
|
| 177 |
+
|
| 178 |
+
def encode(self, buf):
|
| 179 |
+
return imagecodecs.bitorder_encode(buf)
|
| 180 |
+
|
| 181 |
+
def decode(self, buf, out=None):
|
| 182 |
+
return imagecodecs.bitorder_decode(buf, out=_flat(out))
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
class Bitshuffle(Codec):
|
| 186 |
+
"""Bitshuffle codec for numcodecs."""
|
| 187 |
+
|
| 188 |
+
codec_id = 'imagecodecs_bitshuffle'
|
| 189 |
+
|
| 190 |
+
def __init__(self, itemsize=1, blocksize=0):
|
| 191 |
+
self.itemsize = itemsize
|
| 192 |
+
self.blocksize = blocksize
|
| 193 |
+
|
| 194 |
+
def encode(self, buf):
|
| 195 |
+
return imagecodecs.bitshuffle_encode(
|
| 196 |
+
buf, itemsize=self.itemsize, blocksize=self.blocksize
|
| 197 |
+
).tobytes()
|
| 198 |
+
|
| 199 |
+
def decode(self, buf, out=None):
|
| 200 |
+
return imagecodecs.bitshuffle_decode(
|
| 201 |
+
buf,
|
| 202 |
+
itemsize=self.itemsize,
|
| 203 |
+
blocksize=self.blocksize,
|
| 204 |
+
out=_flat(out),
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class Blosc(Codec):
|
| 209 |
+
"""Blosc codec for numcodecs."""
|
| 210 |
+
|
| 211 |
+
codec_id = 'imagecodecs_blosc'
|
| 212 |
+
|
| 213 |
+
def __init__(
|
| 214 |
+
self,
|
| 215 |
+
level=None,
|
| 216 |
+
compressor=None,
|
| 217 |
+
typesize=None,
|
| 218 |
+
blocksize=None,
|
| 219 |
+
shuffle=None,
|
| 220 |
+
numthreads=None,
|
| 221 |
+
):
|
| 222 |
+
self.level = level
|
| 223 |
+
self.compressor = compressor
|
| 224 |
+
self.typesize = typesize
|
| 225 |
+
self.blocksize = blocksize
|
| 226 |
+
self.shuffle = shuffle
|
| 227 |
+
self.numthreads = numthreads
|
| 228 |
+
|
| 229 |
+
def encode(self, buf):
|
| 230 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 231 |
+
return imagecodecs.blosc_encode(
|
| 232 |
+
buf,
|
| 233 |
+
level=self.level,
|
| 234 |
+
compressor=self.compressor,
|
| 235 |
+
typesize=self.typesize,
|
| 236 |
+
blocksize=self.blocksize,
|
| 237 |
+
shuffle=self.shuffle,
|
| 238 |
+
numthreads=self.numthreads,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
def decode(self, buf, out=None):
|
| 242 |
+
return imagecodecs.blosc_decode(
|
| 243 |
+
buf, numthreads=self.numthreads, out=_flat(out)
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
class Blosc2(Codec):
|
| 248 |
+
"""Blosc2 codec for numcodecs."""
|
| 249 |
+
|
| 250 |
+
codec_id = 'imagecodecs_blosc2'
|
| 251 |
+
|
| 252 |
+
def __init__(
|
| 253 |
+
self,
|
| 254 |
+
level=None,
|
| 255 |
+
compressor=None,
|
| 256 |
+
typesize=None,
|
| 257 |
+
blocksize=None,
|
| 258 |
+
shuffle=None,
|
| 259 |
+
numthreads=None,
|
| 260 |
+
):
|
| 261 |
+
self.level = level
|
| 262 |
+
self.compressor = compressor
|
| 263 |
+
self.typesize = typesize
|
| 264 |
+
self.blocksize = blocksize
|
| 265 |
+
self.shuffle = shuffle
|
| 266 |
+
self.numthreads = numthreads
|
| 267 |
+
|
| 268 |
+
def encode(self, buf):
|
| 269 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 270 |
+
return imagecodecs.blosc2_encode(
|
| 271 |
+
buf,
|
| 272 |
+
level=self.level,
|
| 273 |
+
compressor=self.compressor,
|
| 274 |
+
typesize=self.typesize,
|
| 275 |
+
blocksize=self.blocksize,
|
| 276 |
+
shuffle=self.shuffle,
|
| 277 |
+
numthreads=self.numthreads,
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
def decode(self, buf, out=None):
|
| 281 |
+
return imagecodecs.blosc2_decode(
|
| 282 |
+
buf, numthreads=self.numthreads, out=_flat(out)
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class Brotli(Codec):
|
| 287 |
+
"""Brotli codec for numcodecs."""
|
| 288 |
+
|
| 289 |
+
codec_id = 'imagecodecs_brotli'
|
| 290 |
+
|
| 291 |
+
def __init__(self, level=None, mode=None, lgwin=None):
|
| 292 |
+
self.level = level
|
| 293 |
+
self.mode = mode
|
| 294 |
+
self.lgwin = lgwin
|
| 295 |
+
|
| 296 |
+
def encode(self, buf):
|
| 297 |
+
return imagecodecs.brotli_encode(
|
| 298 |
+
buf, level=self.level, mode=self.mode, lgwin=self.lgwin
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
def decode(self, buf, out=None):
|
| 302 |
+
return imagecodecs.brotli_decode(buf, out=_flat(out))
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class ByteShuffle(Codec):
|
| 306 |
+
"""ByteShuffle codec for numcodecs."""
|
| 307 |
+
|
| 308 |
+
codec_id = 'imagecodecs_byteshuffle'
|
| 309 |
+
|
| 310 |
+
def __init__(
|
| 311 |
+
self, shape, dtype, axis=-1, dist=1, delta=False, reorder=False
|
| 312 |
+
):
|
| 313 |
+
self.shape = tuple(shape)
|
| 314 |
+
self.dtype = numpy.dtype(dtype).str
|
| 315 |
+
self.axis = axis
|
| 316 |
+
self.dist = dist
|
| 317 |
+
self.delta = bool(delta)
|
| 318 |
+
self.reorder = bool(reorder)
|
| 319 |
+
|
| 320 |
+
def encode(self, buf):
|
| 321 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 322 |
+
assert buf.shape == self.shape
|
| 323 |
+
assert buf.dtype == self.dtype
|
| 324 |
+
return imagecodecs.byteshuffle_encode(
|
| 325 |
+
buf,
|
| 326 |
+
axis=self.axis,
|
| 327 |
+
dist=self.dist,
|
| 328 |
+
delta=self.delta,
|
| 329 |
+
reorder=self.reorder,
|
| 330 |
+
).tobytes()
|
| 331 |
+
|
| 332 |
+
def decode(self, buf, out=None):
|
| 333 |
+
if not isinstance(buf, numpy.ndarray):
|
| 334 |
+
buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
|
| 335 |
+
return imagecodecs.byteshuffle_decode(
|
| 336 |
+
buf,
|
| 337 |
+
axis=self.axis,
|
| 338 |
+
dist=self.dist,
|
| 339 |
+
delta=self.delta,
|
| 340 |
+
reorder=self.reorder,
|
| 341 |
+
out=out,
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
class Bz2(Codec):
|
| 346 |
+
"""Bz2 codec for numcodecs."""
|
| 347 |
+
|
| 348 |
+
codec_id = 'imagecodecs_bz2'
|
| 349 |
+
|
| 350 |
+
def __init__(self, level=None):
|
| 351 |
+
self.level = level
|
| 352 |
+
|
| 353 |
+
def encode(self, buf):
|
| 354 |
+
return imagecodecs.bz2_encode(buf, level=self.level)
|
| 355 |
+
|
| 356 |
+
def decode(self, buf, out=None):
|
| 357 |
+
return imagecodecs.bz2_decode(buf, out=_flat(out))
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
class Cms(Codec):
|
| 361 |
+
"""CMS codec for numcodecs."""
|
| 362 |
+
|
| 363 |
+
codec_id = 'imagecodecs_cms'
|
| 364 |
+
|
| 365 |
+
def __init__(self, *args, **kwargs):
|
| 366 |
+
pass
|
| 367 |
+
|
| 368 |
+
def encode(self, buf, out=None):
|
| 369 |
+
# return imagecodecs.cms_transform(buf)
|
| 370 |
+
raise NotImplementedError
|
| 371 |
+
|
| 372 |
+
def decode(self, buf, out=None):
|
| 373 |
+
# return imagecodecs.cms_transform(buf)
|
| 374 |
+
raise NotImplementedError
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
class Deflate(Codec):
|
| 378 |
+
"""Deflate codec for numcodecs."""
|
| 379 |
+
|
| 380 |
+
codec_id = 'imagecodecs_deflate'
|
| 381 |
+
|
| 382 |
+
def __init__(self, level=None, raw=False):
|
| 383 |
+
self.level = level
|
| 384 |
+
self.raw = bool(raw)
|
| 385 |
+
|
| 386 |
+
def encode(self, buf):
|
| 387 |
+
return imagecodecs.deflate_encode(buf, level=self.level, raw=self.raw)
|
| 388 |
+
|
| 389 |
+
def decode(self, buf, out=None):
|
| 390 |
+
return imagecodecs.deflate_decode(buf, out=_flat(out), raw=self.raw)
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
class Delta(Codec):
|
| 394 |
+
"""Delta codec for numcodecs."""
|
| 395 |
+
|
| 396 |
+
codec_id = 'imagecodecs_delta'
|
| 397 |
+
|
| 398 |
+
def __init__(self, shape=None, dtype=None, axis=-1, dist=1):
|
| 399 |
+
self.shape = None if shape is None else tuple(shape)
|
| 400 |
+
self.dtype = None if dtype is None else numpy.dtype(dtype).str
|
| 401 |
+
self.axis = axis
|
| 402 |
+
self.dist = dist
|
| 403 |
+
|
| 404 |
+
def encode(self, buf):
|
| 405 |
+
if self.shape is not None or self.dtype is not None:
|
| 406 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 407 |
+
assert buf.shape == self.shape
|
| 408 |
+
assert buf.dtype == self.dtype
|
| 409 |
+
return imagecodecs.delta_encode(
|
| 410 |
+
buf, axis=self.axis, dist=self.dist
|
| 411 |
+
).tobytes()
|
| 412 |
+
|
| 413 |
+
def decode(self, buf, out=None):
|
| 414 |
+
if self.shape is not None or self.dtype is not None:
|
| 415 |
+
buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
|
| 416 |
+
return imagecodecs.delta_decode(
|
| 417 |
+
buf, axis=self.axis, dist=self.dist, out=out
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
class Float24(Codec):
|
| 422 |
+
"""Float24 codec for numcodecs."""
|
| 423 |
+
|
| 424 |
+
codec_id = 'imagecodecs_float24'
|
| 425 |
+
|
| 426 |
+
def __init__(self, byteorder=None, rounding=None):
|
| 427 |
+
self.byteorder = byteorder
|
| 428 |
+
self.rounding = rounding
|
| 429 |
+
|
| 430 |
+
def encode(self, buf):
|
| 431 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 432 |
+
return imagecodecs.float24_encode(
|
| 433 |
+
buf, byteorder=self.byteorder, rounding=self.rounding
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
def decode(self, buf, out=None):
|
| 437 |
+
return imagecodecs.float24_decode(
|
| 438 |
+
buf, byteorder=self.byteorder, out=out
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
class FloatPred(Codec):
|
| 443 |
+
"""Floating Point Predictor codec for numcodecs."""
|
| 444 |
+
|
| 445 |
+
codec_id = 'imagecodecs_floatpred'
|
| 446 |
+
|
| 447 |
+
def __init__(self, shape, dtype, axis=-1, dist=1):
|
| 448 |
+
self.shape = tuple(shape)
|
| 449 |
+
self.dtype = numpy.dtype(dtype).str
|
| 450 |
+
self.axis = axis
|
| 451 |
+
self.dist = dist
|
| 452 |
+
|
| 453 |
+
def encode(self, buf):
|
| 454 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 455 |
+
assert buf.shape == self.shape
|
| 456 |
+
assert buf.dtype == self.dtype
|
| 457 |
+
return imagecodecs.floatpred_encode(
|
| 458 |
+
buf, axis=self.axis, dist=self.dist
|
| 459 |
+
).tobytes()
|
| 460 |
+
|
| 461 |
+
def decode(self, buf, out=None):
|
| 462 |
+
if not isinstance(buf, numpy.ndarray):
|
| 463 |
+
buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
|
| 464 |
+
return imagecodecs.floatpred_decode(
|
| 465 |
+
buf, axis=self.axis, dist=self.dist, out=out
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
class Gif(Codec):
|
| 470 |
+
"""GIF codec for numcodecs."""
|
| 471 |
+
|
| 472 |
+
codec_id = 'imagecodecs_gif'
|
| 473 |
+
|
| 474 |
+
def encode(self, buf):
|
| 475 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 476 |
+
return imagecodecs.gif_encode(buf)
|
| 477 |
+
|
| 478 |
+
def decode(self, buf, out=None):
|
| 479 |
+
return imagecodecs.gif_decode(buf, asrgb=False, out=out)
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
class Heif(Codec):
|
| 483 |
+
"""HEIF codec for numcodecs."""
|
| 484 |
+
|
| 485 |
+
codec_id = 'imagecodecs_heif'
|
| 486 |
+
|
| 487 |
+
def __init__(
|
| 488 |
+
self,
|
| 489 |
+
level=None,
|
| 490 |
+
bitspersample=None,
|
| 491 |
+
photometric=None,
|
| 492 |
+
compression=None,
|
| 493 |
+
numthreads=None,
|
| 494 |
+
index=None,
|
| 495 |
+
):
|
| 496 |
+
self.level = level
|
| 497 |
+
self.bitspersample = bitspersample
|
| 498 |
+
self.photometric = photometric
|
| 499 |
+
self.compression = compression
|
| 500 |
+
self.numthreads = numthreads
|
| 501 |
+
self.index = index
|
| 502 |
+
|
| 503 |
+
def encode(self, buf):
|
| 504 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 505 |
+
return imagecodecs.heif_encode(
|
| 506 |
+
buf,
|
| 507 |
+
level=self.level,
|
| 508 |
+
bitspersample=self.bitspersample,
|
| 509 |
+
photometric=self.photometric,
|
| 510 |
+
compression=self.compression,
|
| 511 |
+
numthreads=self.numthreads,
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
def decode(self, buf, out=None):
|
| 515 |
+
return imagecodecs.heif_decode(
|
| 516 |
+
buf,
|
| 517 |
+
index=self.index,
|
| 518 |
+
photometric=self.photometric,
|
| 519 |
+
numthreads=self.numthreads,
|
| 520 |
+
out=out,
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
class Jetraw(Codec):
|
| 525 |
+
"""Jetraw codec for numcodecs."""
|
| 526 |
+
|
| 527 |
+
codec_id = 'imagecodecs_jetraw'
|
| 528 |
+
|
| 529 |
+
def __init__(
|
| 530 |
+
self,
|
| 531 |
+
shape,
|
| 532 |
+
identifier,
|
| 533 |
+
parameters=None,
|
| 534 |
+
verbosity=None,
|
| 535 |
+
errorbound=None,
|
| 536 |
+
):
|
| 537 |
+
self.shape = shape
|
| 538 |
+
self.identifier = identifier
|
| 539 |
+
self.errorbound = errorbound
|
| 540 |
+
imagecodecs.jetraw_init(parameters, verbosity)
|
| 541 |
+
|
| 542 |
+
def encode(self, buf):
|
| 543 |
+
return imagecodecs.jetraw_encode(
|
| 544 |
+
buf, identifier=self.identifier, errorbound=self.errorbound
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
def decode(self, buf, out=None):
|
| 548 |
+
if out is None:
|
| 549 |
+
out = numpy.empty(self.shape, numpy.uint16)
|
| 550 |
+
return imagecodecs.jetraw_decode(buf, out=out)
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
class Jpeg(Codec):
|
| 554 |
+
"""JPEG codec for numcodecs."""
|
| 555 |
+
|
| 556 |
+
codec_id = 'imagecodecs_jpeg'
|
| 557 |
+
|
| 558 |
+
def __init__(
|
| 559 |
+
self,
|
| 560 |
+
bitspersample=None,
|
| 561 |
+
tables=None,
|
| 562 |
+
header=None,
|
| 563 |
+
colorspace_data=None,
|
| 564 |
+
colorspace_jpeg=None,
|
| 565 |
+
level=None,
|
| 566 |
+
subsampling=None,
|
| 567 |
+
optimize=None,
|
| 568 |
+
smoothing=None,
|
| 569 |
+
):
|
| 570 |
+
self.tables = tables
|
| 571 |
+
self.header = header
|
| 572 |
+
self.bitspersample = bitspersample
|
| 573 |
+
self.colorspace_data = colorspace_data
|
| 574 |
+
self.colorspace_jpeg = colorspace_jpeg
|
| 575 |
+
self.level = level
|
| 576 |
+
self.subsampling = subsampling
|
| 577 |
+
self.optimize = optimize
|
| 578 |
+
self.smoothing = smoothing
|
| 579 |
+
|
| 580 |
+
def encode(self, buf):
|
| 581 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 582 |
+
return imagecodecs.jpeg_encode(
|
| 583 |
+
buf,
|
| 584 |
+
level=self.level,
|
| 585 |
+
colorspace=self.colorspace_data,
|
| 586 |
+
outcolorspace=self.colorspace_jpeg,
|
| 587 |
+
subsampling=self.subsampling,
|
| 588 |
+
optimize=self.optimize,
|
| 589 |
+
smoothing=self.smoothing,
|
| 590 |
+
)
|
| 591 |
+
|
| 592 |
+
def decode(self, buf, out=None):
|
| 593 |
+
out_shape = None
|
| 594 |
+
if out is not None:
|
| 595 |
+
out_shape = out.shape
|
| 596 |
+
out = protective_squeeze(out)
|
| 597 |
+
img = imagecodecs.jpeg_decode(
|
| 598 |
+
buf,
|
| 599 |
+
bitspersample=self.bitspersample,
|
| 600 |
+
tables=self.tables,
|
| 601 |
+
header=self.header,
|
| 602 |
+
colorspace=self.colorspace_jpeg,
|
| 603 |
+
outcolorspace=self.colorspace_data,
|
| 604 |
+
out=out,
|
| 605 |
+
)
|
| 606 |
+
if out_shape is not None:
|
| 607 |
+
img = img.reshape(out_shape)
|
| 608 |
+
return img
|
| 609 |
+
|
| 610 |
+
def get_config(self):
|
| 611 |
+
"""Return dictionary holding configuration parameters."""
|
| 612 |
+
config = dict(id=self.codec_id)
|
| 613 |
+
for key in self.__dict__:
|
| 614 |
+
if not key.startswith('_'):
|
| 615 |
+
value = getattr(self, key)
|
| 616 |
+
if value is not None and key in ('header', 'tables'):
|
| 617 |
+
import base64
|
| 618 |
+
|
| 619 |
+
value = base64.b64encode(value).decode()
|
| 620 |
+
config[key] = value
|
| 621 |
+
return config
|
| 622 |
+
|
| 623 |
+
@classmethod
|
| 624 |
+
def from_config(cls, config):
|
| 625 |
+
"""Instantiate codec from configuration object."""
|
| 626 |
+
for key in ('header', 'tables'):
|
| 627 |
+
value = config.get(key, None)
|
| 628 |
+
if value is not None and isinstance(value, str):
|
| 629 |
+
import base64
|
| 630 |
+
|
| 631 |
+
config[key] = base64.b64decode(value.encode())
|
| 632 |
+
return cls(**config)
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
class Jpeg2k(Codec):
|
| 636 |
+
"""JPEG 2000 codec for numcodecs."""
|
| 637 |
+
|
| 638 |
+
codec_id = 'imagecodecs_jpeg2k'
|
| 639 |
+
|
| 640 |
+
def __init__(
|
| 641 |
+
self,
|
| 642 |
+
level=None,
|
| 643 |
+
codecformat=None,
|
| 644 |
+
colorspace=None,
|
| 645 |
+
tile=None,
|
| 646 |
+
reversible=None,
|
| 647 |
+
bitspersample=None,
|
| 648 |
+
resolutions=None,
|
| 649 |
+
numthreads=None,
|
| 650 |
+
verbose=0,
|
| 651 |
+
):
|
| 652 |
+
self.level = level
|
| 653 |
+
self.codecformat = codecformat
|
| 654 |
+
self.colorspace = colorspace
|
| 655 |
+
self.tile = None if tile is None else tuple(tile)
|
| 656 |
+
self.reversible = reversible
|
| 657 |
+
self.bitspersample = bitspersample
|
| 658 |
+
self.resolutions = resolutions
|
| 659 |
+
self.numthreads = numthreads
|
| 660 |
+
self.verbose = verbose
|
| 661 |
+
|
| 662 |
+
def encode(self, buf):
|
| 663 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 664 |
+
return imagecodecs.jpeg2k_encode(
|
| 665 |
+
buf,
|
| 666 |
+
level=self.level,
|
| 667 |
+
codecformat=self.codecformat,
|
| 668 |
+
colorspace=self.colorspace,
|
| 669 |
+
tile=self.tile,
|
| 670 |
+
reversible=self.reversible,
|
| 671 |
+
bitspersample=self.bitspersample,
|
| 672 |
+
resolutions=self.resolutions,
|
| 673 |
+
numthreads=self.numthreads,
|
| 674 |
+
verbose=self.verbose,
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
def decode(self, buf, out=None):
|
| 678 |
+
return imagecodecs.jpeg2k_decode(
|
| 679 |
+
buf, verbose=self.verbose, numthreads=self.numthreads, out=out
|
| 680 |
+
)
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
class JpegLs(Codec):
|
| 684 |
+
"""JPEG LS codec for numcodecs."""
|
| 685 |
+
|
| 686 |
+
codec_id = 'imagecodecs_jpegls'
|
| 687 |
+
|
| 688 |
+
def __init__(self, level=None):
|
| 689 |
+
self.level = level
|
| 690 |
+
|
| 691 |
+
def encode(self, buf):
|
| 692 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 693 |
+
return imagecodecs.jpegls_encode(buf, level=self.level)
|
| 694 |
+
|
| 695 |
+
def decode(self, buf, out=None):
|
| 696 |
+
return imagecodecs.jpegls_decode(buf, out=out)
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
class JpegXl(Codec):
|
| 700 |
+
"""JPEG XL codec for numcodecs."""
|
| 701 |
+
|
| 702 |
+
codec_id = 'imagecodecs_jpegxl'
|
| 703 |
+
|
| 704 |
+
def __init__(
|
| 705 |
+
self,
|
| 706 |
+
# encode
|
| 707 |
+
level=None,
|
| 708 |
+
effort=None,
|
| 709 |
+
distance=None,
|
| 710 |
+
lossless=None,
|
| 711 |
+
decodingspeed=None,
|
| 712 |
+
photometric=None,
|
| 713 |
+
planar=None,
|
| 714 |
+
usecontainer=None,
|
| 715 |
+
# decode
|
| 716 |
+
index=None,
|
| 717 |
+
keeporientation=None,
|
| 718 |
+
# both
|
| 719 |
+
numthreads=None,
|
| 720 |
+
):
|
| 721 |
+
"""
|
| 722 |
+
Return JPEG XL image from numpy array.
|
| 723 |
+
Float must be in nominal range 0..1.
|
| 724 |
+
|
| 725 |
+
Currently L, LA, RGB, RGBA images are supported in contig mode.
|
| 726 |
+
Extra channels are only supported for grayscale images in planar mode.
|
| 727 |
+
|
| 728 |
+
Parameters
|
| 729 |
+
----------
|
| 730 |
+
level : Default to None, i.e. not overwriting lossess and decodingspeed options.
|
| 731 |
+
When < 0: Use lossless compression
|
| 732 |
+
When in [0,1,2,3,4]: Sets the decoding speed tier for the provided options.
|
| 733 |
+
Minimum is 0 (slowest to decode, best quality/density), and maximum
|
| 734 |
+
is 4 (fastest to decode, at the cost of some quality/density).
|
| 735 |
+
effort : Default to 3.
|
| 736 |
+
Sets encoder effort/speed level without affecting decoding speed.
|
| 737 |
+
Valid values are, from faster to slower speed: 1:lightning 2:thunder
|
| 738 |
+
3:falcon 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise.
|
| 739 |
+
Speed: lightning, thunder, falcon, cheetah, hare, wombat, squirrel, kitten, tortoise
|
| 740 |
+
control the encoder effort in ascending order.
|
| 741 |
+
This also affects memory usage: using lower effort will typically reduce memory
|
| 742 |
+
consumption during encoding.
|
| 743 |
+
lightning and thunder are fast modes useful for lossless mode (modular).
|
| 744 |
+
falcon disables all of the following tools.
|
| 745 |
+
cheetah enables coefficient reordering, context clustering, and heuristics for selecting DCT sizes and quantization steps.
|
| 746 |
+
hare enables Gaborish filtering, chroma from luma, and an initial estimate of quantization steps.
|
| 747 |
+
wombat enables error diffusion quantization and full DCT size selection heuristics.
|
| 748 |
+
squirrel (default) enables dots, patches, and spline detection, and full context clustering.
|
| 749 |
+
kitten optimizes the adaptive quantization for a psychovisual metric.
|
| 750 |
+
tortoise enables a more thorough adaptive quantization search.
|
| 751 |
+
distance : Default to 1.0
|
| 752 |
+
Sets the distance level for lossy compression: target max butteraugli distance,
|
| 753 |
+
lower = higher quality. Range: 0 .. 15. 0.0 = mathematically lossless
|
| 754 |
+
(however, use JxlEncoderSetFrameLossless instead to use true lossless,
|
| 755 |
+
as setting distance to 0 alone is not the only requirement).
|
| 756 |
+
1.0 = visually lossless. Recommended range: 0.5 .. 3.0.
|
| 757 |
+
lossess : Default to False.
|
| 758 |
+
Use lossess encoding.
|
| 759 |
+
decodingspeed : Default to 0.
|
| 760 |
+
Duplicate to level. [0,4]
|
| 761 |
+
photometric : Return JxlColorSpace value.
|
| 762 |
+
Default logic is quite complicated but works most of the time.
|
| 763 |
+
Accepted value:
|
| 764 |
+
int: [-1,3]
|
| 765 |
+
str: ['RGB',
|
| 766 |
+
'WHITEISZERO', 'MINISWHITE',
|
| 767 |
+
'BLACKISZERO', 'MINISBLACK', 'GRAY',
|
| 768 |
+
'XYB', 'KNOWN']
|
| 769 |
+
planar : Enable multi-channel mode.
|
| 770 |
+
Default to false.
|
| 771 |
+
usecontainer :
|
| 772 |
+
Forces the encoder to use the box-based container format (BMFF)
|
| 773 |
+
even when not necessary.
|
| 774 |
+
When using JxlEncoderUseBoxes, JxlEncoderStoreJPEGMetadata or
|
| 775 |
+
JxlEncoderSetCodestreamLevel with level 10, the encoder will
|
| 776 |
+
automatically also use the container format, it is not necessary
|
| 777 |
+
to use JxlEncoderUseContainer for those use cases.
|
| 778 |
+
By default this setting is disabled.
|
| 779 |
+
index : Selectively decode frames for animation.
|
| 780 |
+
Default to 0, decode all frames.
|
| 781 |
+
When set to > 0, decode that frame index only.
|
| 782 |
+
keeporientation :
|
| 783 |
+
Enables or disables preserving of as-in-bitstream pixeldata orientation.
|
| 784 |
+
Some images are encoded with an Orientation tag indicating that the
|
| 785 |
+
decoder must perform a rotation and/or mirroring to the encoded image data.
|
| 786 |
+
|
| 787 |
+
If skip_reorientation is JXL_FALSE (the default): the decoder will apply
|
| 788 |
+
the transformation from the orientation setting, hence rendering the image
|
| 789 |
+
according to its specified intent. When producing a JxlBasicInfo, the decoder
|
| 790 |
+
will always set the orientation field to JXL_ORIENT_IDENTITY (matching the
|
| 791 |
+
returned pixel data) and also align xsize and ysize so that they correspond
|
| 792 |
+
to the width and the height of the returned pixel data.
|
| 793 |
+
|
| 794 |
+
If skip_reorientation is JXL_TRUE: the decoder will skip applying the
|
| 795 |
+
transformation from the orientation setting, returning the image in
|
| 796 |
+
the as-in-bitstream pixeldata orientation. This may be faster to decode
|
| 797 |
+
since the decoder doesnt have to apply the transformation, but can
|
| 798 |
+
cause wrong display of the image if the orientation tag is not correctly
|
| 799 |
+
taken into account by the user.
|
| 800 |
+
|
| 801 |
+
By default, this option is disabled, and the returned pixel data is
|
| 802 |
+
re-oriented according to the images Orientation setting.
|
| 803 |
+
threads : Default to 1.
|
| 804 |
+
If <= 0, use all cores.
|
| 805 |
+
If > 32, clipped to 32.
|
| 806 |
+
"""
|
| 807 |
+
|
| 808 |
+
self.level = level
|
| 809 |
+
self.effort = effort
|
| 810 |
+
self.distance = distance
|
| 811 |
+
self.lossless = bool(lossless)
|
| 812 |
+
self.decodingspeed = decodingspeed
|
| 813 |
+
self.photometric = photometric
|
| 814 |
+
self.planar = planar
|
| 815 |
+
self.usecontainer = usecontainer
|
| 816 |
+
self.index = index
|
| 817 |
+
self.keeporientation = keeporientation
|
| 818 |
+
self.numthreads = numthreads
|
| 819 |
+
|
| 820 |
+
def encode(self, buf):
|
| 821 |
+
# TODO: only squeeze all but last dim
|
| 822 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 823 |
+
return imagecodecs.jpegxl_encode(
|
| 824 |
+
buf,
|
| 825 |
+
level=self.level,
|
| 826 |
+
effort=self.effort,
|
| 827 |
+
distance=self.distance,
|
| 828 |
+
lossless=self.lossless,
|
| 829 |
+
decodingspeed=self.decodingspeed,
|
| 830 |
+
photometric=self.photometric,
|
| 831 |
+
planar=self.planar,
|
| 832 |
+
usecontainer=self.usecontainer,
|
| 833 |
+
numthreads=self.numthreads,
|
| 834 |
+
)
|
| 835 |
+
|
| 836 |
+
def decode(self, buf, out=None):
|
| 837 |
+
return imagecodecs.jpegxl_decode(
|
| 838 |
+
buf,
|
| 839 |
+
index=self.index,
|
| 840 |
+
keeporientation=self.keeporientation,
|
| 841 |
+
numthreads=self.numthreads,
|
| 842 |
+
out=out,
|
| 843 |
+
)
|
| 844 |
+
|
| 845 |
+
|
| 846 |
+
class JpegXr(Codec):
|
| 847 |
+
"""JPEG XR codec for numcodecs."""
|
| 848 |
+
|
| 849 |
+
codec_id = 'imagecodecs_jpegxr'
|
| 850 |
+
|
| 851 |
+
def __init__(
|
| 852 |
+
self,
|
| 853 |
+
level=None,
|
| 854 |
+
photometric=None,
|
| 855 |
+
hasalpha=None,
|
| 856 |
+
resolution=None,
|
| 857 |
+
fp2int=None,
|
| 858 |
+
):
|
| 859 |
+
self.level = level
|
| 860 |
+
self.photometric = photometric
|
| 861 |
+
self.hasalpha = hasalpha
|
| 862 |
+
self.resolution = resolution
|
| 863 |
+
self.fp2int = fp2int
|
| 864 |
+
|
| 865 |
+
def encode(self, buf):
|
| 866 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 867 |
+
return imagecodecs.jpegxr_encode(
|
| 868 |
+
buf,
|
| 869 |
+
level=self.level,
|
| 870 |
+
photometric=self.photometric,
|
| 871 |
+
hasalpha=self.hasalpha,
|
| 872 |
+
resolution=self.resolution,
|
| 873 |
+
)
|
| 874 |
+
|
| 875 |
+
def decode(self, buf, out=None):
|
| 876 |
+
return imagecodecs.jpegxr_decode(buf, fp2int=self.fp2int, out=out)
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
class Lerc(Codec):
|
| 880 |
+
"""LERC codec for numcodecs."""
|
| 881 |
+
|
| 882 |
+
codec_id = 'imagecodecs_lerc'
|
| 883 |
+
|
| 884 |
+
def __init__(self, level=None, version=None, planar=None):
|
| 885 |
+
self.level = level
|
| 886 |
+
self.version = version
|
| 887 |
+
self.planar = bool(planar)
|
| 888 |
+
# TODO: support mask?
|
| 889 |
+
# self.mask = None
|
| 890 |
+
|
| 891 |
+
def encode(self, buf):
|
| 892 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 893 |
+
return imagecodecs.lerc_encode(
|
| 894 |
+
buf,
|
| 895 |
+
level=self.level,
|
| 896 |
+
version=self.version,
|
| 897 |
+
planar=self.planar,
|
| 898 |
+
)
|
| 899 |
+
|
| 900 |
+
def decode(self, buf, out=None):
|
| 901 |
+
return imagecodecs.lerc_decode(buf, out=out)
|
| 902 |
+
|
| 903 |
+
|
| 904 |
+
class Ljpeg(Codec):
|
| 905 |
+
"""LJPEG codec for numcodecs."""
|
| 906 |
+
|
| 907 |
+
codec_id = 'imagecodecs_ljpeg'
|
| 908 |
+
|
| 909 |
+
def __init__(self, bitspersample=None):
|
| 910 |
+
self.bitspersample = bitspersample
|
| 911 |
+
|
| 912 |
+
def encode(self, buf):
|
| 913 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 914 |
+
return imagecodecs.ljpeg_encode(buf, bitspersample=self.bitspersample)
|
| 915 |
+
|
| 916 |
+
def decode(self, buf, out=None):
|
| 917 |
+
return imagecodecs.ljpeg_decode(buf, out=out)
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
class Lz4(Codec):
|
| 921 |
+
"""LZ4 codec for numcodecs."""
|
| 922 |
+
|
| 923 |
+
codec_id = 'imagecodecs_lz4'
|
| 924 |
+
|
| 925 |
+
def __init__(self, level=None, hc=False, header=True):
|
| 926 |
+
self.level = level
|
| 927 |
+
self.hc = hc
|
| 928 |
+
self.header = bool(header)
|
| 929 |
+
|
| 930 |
+
def encode(self, buf):
|
| 931 |
+
return imagecodecs.lz4_encode(
|
| 932 |
+
buf, level=self.level, hc=self.hc, header=self.header
|
| 933 |
+
)
|
| 934 |
+
|
| 935 |
+
def decode(self, buf, out=None):
|
| 936 |
+
return imagecodecs.lz4_decode(buf, header=self.header, out=_flat(out))
|
| 937 |
+
|
| 938 |
+
|
| 939 |
+
class Lz4f(Codec):
|
| 940 |
+
"""LZ4F codec for numcodecs."""
|
| 941 |
+
|
| 942 |
+
codec_id = 'imagecodecs_lz4f'
|
| 943 |
+
|
| 944 |
+
def __init__(
|
| 945 |
+
self,
|
| 946 |
+
level=None,
|
| 947 |
+
blocksizeid=False,
|
| 948 |
+
contentchecksum=None,
|
| 949 |
+
blockchecksum=None,
|
| 950 |
+
):
|
| 951 |
+
self.level = level
|
| 952 |
+
self.blocksizeid = blocksizeid
|
| 953 |
+
self.contentchecksum = contentchecksum
|
| 954 |
+
self.blockchecksum = blockchecksum
|
| 955 |
+
|
| 956 |
+
def encode(self, buf):
|
| 957 |
+
return imagecodecs.lz4f_encode(
|
| 958 |
+
buf,
|
| 959 |
+
level=self.level,
|
| 960 |
+
blocksizeid=self.blocksizeid,
|
| 961 |
+
contentchecksum=self.contentchecksum,
|
| 962 |
+
blockchecksum=self.blockchecksum,
|
| 963 |
+
)
|
| 964 |
+
|
| 965 |
+
def decode(self, buf, out=None):
|
| 966 |
+
return imagecodecs.lz4f_decode(buf, out=_flat(out))
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
class Lzf(Codec):
|
| 970 |
+
"""LZF codec for numcodecs."""
|
| 971 |
+
|
| 972 |
+
codec_id = 'imagecodecs_lzf'
|
| 973 |
+
|
| 974 |
+
def __init__(self, header=True):
|
| 975 |
+
self.header = bool(header)
|
| 976 |
+
|
| 977 |
+
def encode(self, buf):
|
| 978 |
+
return imagecodecs.lzf_encode(buf, header=self.header)
|
| 979 |
+
|
| 980 |
+
def decode(self, buf, out=None):
|
| 981 |
+
return imagecodecs.lzf_decode(buf, header=self.header, out=_flat(out))
|
| 982 |
+
|
| 983 |
+
|
| 984 |
+
class Lzma(Codec):
|
| 985 |
+
"""LZMA codec for numcodecs."""
|
| 986 |
+
|
| 987 |
+
codec_id = 'imagecodecs_lzma'
|
| 988 |
+
|
| 989 |
+
def __init__(self, level=None):
|
| 990 |
+
self.level = level
|
| 991 |
+
|
| 992 |
+
def encode(self, buf):
|
| 993 |
+
return imagecodecs.lzma_encode(buf, level=self.level)
|
| 994 |
+
|
| 995 |
+
def decode(self, buf, out=None):
|
| 996 |
+
return imagecodecs.lzma_decode(buf, out=_flat(out))
|
| 997 |
+
|
| 998 |
+
|
| 999 |
+
class Lzw(Codec):
|
| 1000 |
+
"""LZW codec for numcodecs."""
|
| 1001 |
+
|
| 1002 |
+
codec_id = 'imagecodecs_lzw'
|
| 1003 |
+
|
| 1004 |
+
def encode(self, buf):
|
| 1005 |
+
return imagecodecs.lzw_encode(buf)
|
| 1006 |
+
|
| 1007 |
+
def decode(self, buf, out=None):
|
| 1008 |
+
return imagecodecs.lzw_decode(buf, out=_flat(out))
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
class PackBits(Codec):
|
| 1012 |
+
"""PackBits codec for numcodecs."""
|
| 1013 |
+
|
| 1014 |
+
codec_id = 'imagecodecs_packbits'
|
| 1015 |
+
|
| 1016 |
+
def __init__(self, axis=None):
|
| 1017 |
+
self.axis = axis
|
| 1018 |
+
|
| 1019 |
+
def encode(self, buf):
|
| 1020 |
+
if not isinstance(buf, (bytes, bytearray)):
|
| 1021 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1022 |
+
return imagecodecs.packbits_encode(buf, axis=self.axis)
|
| 1023 |
+
|
| 1024 |
+
def decode(self, buf, out=None):
|
| 1025 |
+
return imagecodecs.packbits_decode(buf, out=_flat(out))
|
| 1026 |
+
|
| 1027 |
+
|
| 1028 |
+
class Pglz(Codec):
|
| 1029 |
+
"""PGLZ codec for numcodecs."""
|
| 1030 |
+
|
| 1031 |
+
codec_id = 'imagecodecs_pglz'
|
| 1032 |
+
|
| 1033 |
+
def __init__(self, header=True, strategy=None):
|
| 1034 |
+
self.header = bool(header)
|
| 1035 |
+
self.strategy = strategy
|
| 1036 |
+
|
| 1037 |
+
def encode(self, buf):
|
| 1038 |
+
return imagecodecs.pglz_encode(
|
| 1039 |
+
buf, strategy=self.strategy, header=self.header
|
| 1040 |
+
)
|
| 1041 |
+
|
| 1042 |
+
def decode(self, buf, out=None):
|
| 1043 |
+
return imagecodecs.pglz_decode(buf, header=self.header, out=_flat(out))
|
| 1044 |
+
|
| 1045 |
+
|
| 1046 |
+
class Png(Codec):
|
| 1047 |
+
"""PNG codec for numcodecs."""
|
| 1048 |
+
|
| 1049 |
+
codec_id = 'imagecodecs_png'
|
| 1050 |
+
|
| 1051 |
+
def __init__(self, level=None):
|
| 1052 |
+
self.level = level
|
| 1053 |
+
|
| 1054 |
+
def encode(self, buf):
|
| 1055 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1056 |
+
return imagecodecs.png_encode(buf, level=self.level)
|
| 1057 |
+
|
| 1058 |
+
def decode(self, buf, out=None):
|
| 1059 |
+
return imagecodecs.png_decode(buf, out=out)
|
| 1060 |
+
|
| 1061 |
+
|
| 1062 |
+
class Qoi(Codec):
|
| 1063 |
+
"""QOI codec for numcodecs."""
|
| 1064 |
+
|
| 1065 |
+
codec_id = 'imagecodecs_qoi'
|
| 1066 |
+
|
| 1067 |
+
def __init__(self):
|
| 1068 |
+
pass
|
| 1069 |
+
|
| 1070 |
+
def encode(self, buf):
|
| 1071 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1072 |
+
return imagecodecs.qoi_encode(buf)
|
| 1073 |
+
|
| 1074 |
+
def decode(self, buf, out=None):
|
| 1075 |
+
return imagecodecs.qoi_decode(buf, out=out)
|
| 1076 |
+
|
| 1077 |
+
|
| 1078 |
+
class Rgbe(Codec):
|
| 1079 |
+
"""RGBE codec for numcodecs."""
|
| 1080 |
+
|
| 1081 |
+
codec_id = 'imagecodecs_rgbe'
|
| 1082 |
+
|
| 1083 |
+
def __init__(self, header=False, shape=None, rle=None):
|
| 1084 |
+
if not header and shape is None:
|
| 1085 |
+
raise ValueError('must specify data shape if no header')
|
| 1086 |
+
if shape and shape[-1] != 3:
|
| 1087 |
+
raise ValueError('invalid shape')
|
| 1088 |
+
self.shape = shape
|
| 1089 |
+
self.header = bool(header)
|
| 1090 |
+
self.rle = None if rle is None else bool(rle)
|
| 1091 |
+
|
| 1092 |
+
def encode(self, buf):
|
| 1093 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1094 |
+
return imagecodecs.rgbe_encode(buf, header=self.header, rle=self.rle)
|
| 1095 |
+
|
| 1096 |
+
def decode(self, buf, out=None):
|
| 1097 |
+
if out is None and not self.header:
|
| 1098 |
+
out = numpy.empty(self.shape, numpy.float32)
|
| 1099 |
+
return imagecodecs.rgbe_decode(
|
| 1100 |
+
buf, header=self.header, rle=self.rle, out=out
|
| 1101 |
+
)
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
class Rcomp(Codec):
|
| 1105 |
+
"""Rcomp codec for numcodecs."""
|
| 1106 |
+
|
| 1107 |
+
codec_id = 'imagecodecs_rcomp'
|
| 1108 |
+
|
| 1109 |
+
def __init__(self, shape, dtype, nblock=None):
|
| 1110 |
+
self.shape = tuple(shape)
|
| 1111 |
+
self.dtype = numpy.dtype(dtype).str
|
| 1112 |
+
self.nblock = nblock
|
| 1113 |
+
|
| 1114 |
+
def encode(self, buf):
|
| 1115 |
+
return imagecodecs.rcomp_encode(buf, nblock=self.nblock)
|
| 1116 |
+
|
| 1117 |
+
def decode(self, buf, out=None):
|
| 1118 |
+
return imagecodecs.rcomp_decode(
|
| 1119 |
+
buf,
|
| 1120 |
+
shape=self.shape,
|
| 1121 |
+
dtype=self.dtype,
|
| 1122 |
+
nblock=self.nblock,
|
| 1123 |
+
out=out,
|
| 1124 |
+
)
|
| 1125 |
+
|
| 1126 |
+
|
| 1127 |
+
class Snappy(Codec):
|
| 1128 |
+
"""Snappy codec for numcodecs."""
|
| 1129 |
+
|
| 1130 |
+
codec_id = 'imagecodecs_snappy'
|
| 1131 |
+
|
| 1132 |
+
def encode(self, buf):
|
| 1133 |
+
return imagecodecs.snappy_encode(buf)
|
| 1134 |
+
|
| 1135 |
+
def decode(self, buf, out=None):
|
| 1136 |
+
return imagecodecs.snappy_decode(buf, out=_flat(out))
|
| 1137 |
+
|
| 1138 |
+
|
| 1139 |
+
class Spng(Codec):
|
| 1140 |
+
"""SPNG codec for numcodecs."""
|
| 1141 |
+
|
| 1142 |
+
codec_id = 'imagecodecs_spng'
|
| 1143 |
+
|
| 1144 |
+
def __init__(self, level=None):
|
| 1145 |
+
self.level = level
|
| 1146 |
+
|
| 1147 |
+
def encode(self, buf):
|
| 1148 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1149 |
+
return imagecodecs.spng_encode(buf, level=self.level)
|
| 1150 |
+
|
| 1151 |
+
def decode(self, buf, out=None):
|
| 1152 |
+
return imagecodecs.spng_decode(buf, out=out)
|
| 1153 |
+
|
| 1154 |
+
|
| 1155 |
+
class Tiff(Codec):
|
| 1156 |
+
"""TIFF codec for numcodecs."""
|
| 1157 |
+
|
| 1158 |
+
codec_id = 'imagecodecs_tiff'
|
| 1159 |
+
|
| 1160 |
+
def __init__(self, index=None, asrgb=None, verbose=0):
|
| 1161 |
+
self.index = index
|
| 1162 |
+
self.asrgb = bool(asrgb)
|
| 1163 |
+
self.verbose = verbose
|
| 1164 |
+
|
| 1165 |
+
def encode(self, buf):
|
| 1166 |
+
# TODO: not implemented
|
| 1167 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1168 |
+
return imagecodecs.tiff_encode(buf)
|
| 1169 |
+
|
| 1170 |
+
def decode(self, buf, out=None):
|
| 1171 |
+
return imagecodecs.tiff_decode(
|
| 1172 |
+
buf,
|
| 1173 |
+
index=self.index,
|
| 1174 |
+
asrgb=self.asrgb,
|
| 1175 |
+
verbose=self.verbose,
|
| 1176 |
+
out=out,
|
| 1177 |
+
)
|
| 1178 |
+
|
| 1179 |
+
|
| 1180 |
+
class Webp(Codec):
|
| 1181 |
+
"""WebP codec for numcodecs."""
|
| 1182 |
+
|
| 1183 |
+
codec_id = 'imagecodecs_webp'
|
| 1184 |
+
|
| 1185 |
+
def __init__(self, level=None, lossless=None, method=None, hasalpha=None):
|
| 1186 |
+
self.level = level
|
| 1187 |
+
self.hasalpha = bool(hasalpha)
|
| 1188 |
+
self.method = method
|
| 1189 |
+
self.lossless = lossless
|
| 1190 |
+
|
| 1191 |
+
def encode(self, buf):
|
| 1192 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1193 |
+
return imagecodecs.webp_encode(
|
| 1194 |
+
buf, level=self.level, lossless=self.lossless, method=self.method
|
| 1195 |
+
)
|
| 1196 |
+
|
| 1197 |
+
def decode(self, buf, out=None):
|
| 1198 |
+
return imagecodecs.webp_decode(buf, hasalpha=self.hasalpha, out=out)
|
| 1199 |
+
|
| 1200 |
+
|
| 1201 |
+
class Xor(Codec):
|
| 1202 |
+
"""XOR codec for numcodecs."""
|
| 1203 |
+
|
| 1204 |
+
codec_id = 'imagecodecs_xor'
|
| 1205 |
+
|
| 1206 |
+
def __init__(self, shape=None, dtype=None, axis=-1):
|
| 1207 |
+
self.shape = None if shape is None else tuple(shape)
|
| 1208 |
+
self.dtype = None if dtype is None else numpy.dtype(dtype).str
|
| 1209 |
+
self.axis = axis
|
| 1210 |
+
|
| 1211 |
+
def encode(self, buf):
|
| 1212 |
+
if self.shape is not None or self.dtype is not None:
|
| 1213 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1214 |
+
assert buf.shape == self.shape
|
| 1215 |
+
assert buf.dtype == self.dtype
|
| 1216 |
+
return imagecodecs.xor_encode(buf, axis=self.axis).tobytes()
|
| 1217 |
+
|
| 1218 |
+
def decode(self, buf, out=None):
|
| 1219 |
+
if self.shape is not None or self.dtype is not None:
|
| 1220 |
+
buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
|
| 1221 |
+
return imagecodecs.xor_decode(buf, axis=self.axis, out=_flat(out))
|
| 1222 |
+
|
| 1223 |
+
|
| 1224 |
+
class Zfp(Codec):
|
| 1225 |
+
"""ZFP codec for numcodecs."""
|
| 1226 |
+
|
| 1227 |
+
codec_id = 'imagecodecs_zfp'
|
| 1228 |
+
|
| 1229 |
+
def __init__(
|
| 1230 |
+
self,
|
| 1231 |
+
shape=None,
|
| 1232 |
+
dtype=None,
|
| 1233 |
+
strides=None,
|
| 1234 |
+
level=None,
|
| 1235 |
+
mode=None,
|
| 1236 |
+
execution=None,
|
| 1237 |
+
numthreads=None,
|
| 1238 |
+
chunksize=None,
|
| 1239 |
+
header=True,
|
| 1240 |
+
):
|
| 1241 |
+
if header:
|
| 1242 |
+
self.shape = None
|
| 1243 |
+
self.dtype = None
|
| 1244 |
+
self.strides = None
|
| 1245 |
+
elif shape is None or dtype is None:
|
| 1246 |
+
raise ValueError('invalid shape or dtype')
|
| 1247 |
+
else:
|
| 1248 |
+
self.shape = tuple(shape)
|
| 1249 |
+
self.dtype = numpy.dtype(dtype).str
|
| 1250 |
+
self.strides = None if strides is None else tuple(strides)
|
| 1251 |
+
self.level = level
|
| 1252 |
+
self.mode = mode
|
| 1253 |
+
self.execution = execution
|
| 1254 |
+
self.numthreads = numthreads
|
| 1255 |
+
self.chunksize = chunksize
|
| 1256 |
+
self.header = bool(header)
|
| 1257 |
+
|
| 1258 |
+
def encode(self, buf):
|
| 1259 |
+
buf = protective_squeeze(numpy.asarray(buf))
|
| 1260 |
+
if not self.header:
|
| 1261 |
+
assert buf.shape == self.shape
|
| 1262 |
+
assert buf.dtype == self.dtype
|
| 1263 |
+
return imagecodecs.zfp_encode(
|
| 1264 |
+
buf,
|
| 1265 |
+
level=self.level,
|
| 1266 |
+
mode=self.mode,
|
| 1267 |
+
execution=self.execution,
|
| 1268 |
+
header=self.header,
|
| 1269 |
+
numthreads=self.numthreads,
|
| 1270 |
+
chunksize=self.chunksize,
|
| 1271 |
+
)
|
| 1272 |
+
|
| 1273 |
+
def decode(self, buf, out=None):
|
| 1274 |
+
if self.header:
|
| 1275 |
+
return imagecodecs.zfp_decode(buf, out=out)
|
| 1276 |
+
return imagecodecs.zfp_decode(
|
| 1277 |
+
buf,
|
| 1278 |
+
shape=self.shape,
|
| 1279 |
+
dtype=numpy.dtype(self.dtype),
|
| 1280 |
+
strides=self.strides,
|
| 1281 |
+
numthreads=self.numthreads,
|
| 1282 |
+
out=out,
|
| 1283 |
+
)
|
| 1284 |
+
|
| 1285 |
+
|
| 1286 |
+
class Zlib(Codec):
|
| 1287 |
+
"""Zlib codec for numcodecs."""
|
| 1288 |
+
|
| 1289 |
+
codec_id = 'imagecodecs_zlib'
|
| 1290 |
+
|
| 1291 |
+
def __init__(self, level=None):
|
| 1292 |
+
self.level = level
|
| 1293 |
+
|
| 1294 |
+
def encode(self, buf):
|
| 1295 |
+
return imagecodecs.zlib_encode(buf, level=self.level)
|
| 1296 |
+
|
| 1297 |
+
def decode(self, buf, out=None):
|
| 1298 |
+
return imagecodecs.zlib_decode(buf, out=_flat(out))
|
| 1299 |
+
|
| 1300 |
+
|
| 1301 |
+
class Zlibng(Codec):
|
| 1302 |
+
"""Zlibng codec for numcodecs."""
|
| 1303 |
+
|
| 1304 |
+
codec_id = 'imagecodecs_zlibng'
|
| 1305 |
+
|
| 1306 |
+
def __init__(self, level=None):
|
| 1307 |
+
self.level = level
|
| 1308 |
+
|
| 1309 |
+
def encode(self, buf):
|
| 1310 |
+
return imagecodecs.zlibng_encode(buf, level=self.level)
|
| 1311 |
+
|
| 1312 |
+
def decode(self, buf, out=None):
|
| 1313 |
+
return imagecodecs.zlibng_decode(buf, out=_flat(out))
|
| 1314 |
+
|
| 1315 |
+
|
| 1316 |
+
class Zopfli(Codec):
|
| 1317 |
+
"""Zopfli codec for numcodecs."""
|
| 1318 |
+
|
| 1319 |
+
codec_id = 'imagecodecs_zopfli'
|
| 1320 |
+
|
| 1321 |
+
def encode(self, buf):
|
| 1322 |
+
return imagecodecs.zopfli_encode(buf)
|
| 1323 |
+
|
| 1324 |
+
def decode(self, buf, out=None):
|
| 1325 |
+
return imagecodecs.zopfli_decode(buf, out=_flat(out))
|
| 1326 |
+
|
| 1327 |
+
|
| 1328 |
+
class Zstd(Codec):
|
| 1329 |
+
"""ZStandard codec for numcodecs."""
|
| 1330 |
+
|
| 1331 |
+
codec_id = 'imagecodecs_zstd'
|
| 1332 |
+
|
| 1333 |
+
def __init__(self, level=None):
|
| 1334 |
+
self.level = level
|
| 1335 |
+
|
| 1336 |
+
def encode(self, buf):
|
| 1337 |
+
return imagecodecs.zstd_encode(buf, level=self.level)
|
| 1338 |
+
|
| 1339 |
+
def decode(self, buf, out=None):
|
| 1340 |
+
return imagecodecs.zstd_decode(buf, out=_flat(out))
|
| 1341 |
+
|
| 1342 |
+
|
| 1343 |
+
def _flat(out):
|
| 1344 |
+
"""Return numpy array as contiguous view of bytes if possible."""
|
| 1345 |
+
if out is None:
|
| 1346 |
+
return None
|
| 1347 |
+
view = memoryview(out)
|
| 1348 |
+
if view.readonly or not view.contiguous:
|
| 1349 |
+
return None
|
| 1350 |
+
return view.cast('B')
|
| 1351 |
+
|
| 1352 |
+
|
| 1353 |
+
def register_codecs(codecs=None, force=False, verbose=True):
|
| 1354 |
+
"""Register codecs in this module with numcodecs."""
|
| 1355 |
+
for name, cls in globals().items():
|
| 1356 |
+
if not hasattr(cls, 'codec_id') or name == 'Codec':
|
| 1357 |
+
continue
|
| 1358 |
+
if codecs is not None and cls.codec_id not in codecs:
|
| 1359 |
+
continue
|
| 1360 |
+
try:
|
| 1361 |
+
try:
|
| 1362 |
+
get_codec({'id': cls.codec_id})
|
| 1363 |
+
except TypeError:
|
| 1364 |
+
# registered, but failed
|
| 1365 |
+
pass
|
| 1366 |
+
except ValueError:
|
| 1367 |
+
# not registered yet
|
| 1368 |
+
pass
|
| 1369 |
+
else:
|
| 1370 |
+
if not force:
|
| 1371 |
+
if verbose:
|
| 1372 |
+
log_warning(
|
| 1373 |
+
f'numcodec {cls.codec_id!r} already registered'
|
| 1374 |
+
)
|
| 1375 |
+
continue
|
| 1376 |
+
if verbose:
|
| 1377 |
+
log_warning(f'replacing registered numcodec {cls.codec_id!r}')
|
| 1378 |
+
register_codec(cls)
|
| 1379 |
+
|
| 1380 |
+
|
| 1381 |
+
def log_warning(msg, *args, **kwargs):
|
| 1382 |
+
"""Log message with level WARNING."""
|
| 1383 |
+
import logging
|
| 1384 |
+
|
| 1385 |
+
logging.getLogger(__name__).warning(msg, *args, **kwargs)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/kitchen_dataset.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import pathlib
|
| 3 |
+
from typing import Dict
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 10 |
+
from cleandiffuser.dataset.dataset_utils import SequenceSampler, dict_apply
|
| 11 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 12 |
+
from cleandiffuser.env.kitchen.kitchen_util import parse_mjl_logs
|
| 13 |
+
from cleandiffuser.utils import EmptyNormalizer, MinMaxNormalizer
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class KitchenDataset(BaseDataset):
|
| 17 |
+
def __init__(self,
|
| 18 |
+
dataset_dir,
|
| 19 |
+
horizon=1,
|
| 20 |
+
pad_before=0,
|
| 21 |
+
pad_after=0,
|
| 22 |
+
):
|
| 23 |
+
super().__init__()
|
| 24 |
+
|
| 25 |
+
data_directory = pathlib.Path(dataset_dir)
|
| 26 |
+
observations = np.load(data_directory / "observations_seq.npy")
|
| 27 |
+
actions = np.load(data_directory / "actions_seq.npy")
|
| 28 |
+
masks = np.load(data_directory / "existence_mask.npy")
|
| 29 |
+
|
| 30 |
+
self.replay_buffer = ReplayBuffer.create_empty_numpy()
|
| 31 |
+
for i in range(len(masks)):
|
| 32 |
+
eps_len = int(masks[i].sum())
|
| 33 |
+
obs = observations[i, :eps_len].astype(np.float32)
|
| 34 |
+
action = actions[i, :eps_len].astype(np.float32)
|
| 35 |
+
data = {
|
| 36 |
+
'state': obs,
|
| 37 |
+
'action': action
|
| 38 |
+
}
|
| 39 |
+
self.replay_buffer.add_episode(data)
|
| 40 |
+
|
| 41 |
+
self.sampler = SequenceSampler(
|
| 42 |
+
replay_buffer=self.replay_buffer,
|
| 43 |
+
sequence_length=horizon,
|
| 44 |
+
pad_before=pad_before,
|
| 45 |
+
pad_after=pad_after)
|
| 46 |
+
|
| 47 |
+
self.horizon = horizon
|
| 48 |
+
self.pad_before = pad_before
|
| 49 |
+
self.pad_after = pad_after
|
| 50 |
+
|
| 51 |
+
self.normalizer = self.get_normalizer()
|
| 52 |
+
|
| 53 |
+
def get_normalizer(self):
|
| 54 |
+
state_normalizer = MinMaxNormalizer(
|
| 55 |
+
self.replay_buffer['state'][:]) # (N, obs_dim)
|
| 56 |
+
action_normalizer = MinMaxNormalizer(
|
| 57 |
+
self.replay_buffer['action'][:]) # (N, action_dim)
|
| 58 |
+
return {
|
| 59 |
+
"obs": {
|
| 60 |
+
"state": state_normalizer
|
| 61 |
+
},
|
| 62 |
+
"action": action_normalizer
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
def sample_to_data(self, sample):
|
| 66 |
+
state = sample['state'].astype(np.float32)
|
| 67 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 68 |
+
|
| 69 |
+
action = sample['action'].astype(np.float32)
|
| 70 |
+
action = self.normalizer['action'].normalize(action)
|
| 71 |
+
data = {
|
| 72 |
+
'obs': {
|
| 73 |
+
'state': state,
|
| 74 |
+
},
|
| 75 |
+
'action': action,
|
| 76 |
+
}
|
| 77 |
+
return data
|
| 78 |
+
|
| 79 |
+
def __str__(self) -> str:
|
| 80 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 81 |
+
|
| 82 |
+
def __len__(self) -> int:
|
| 83 |
+
return len(self.sampler)
|
| 84 |
+
|
| 85 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 86 |
+
sample = self.sampler.sample_sequence(idx)
|
| 87 |
+
data = self.sample_to_data(sample)
|
| 88 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 89 |
+
return torch_data
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class KitchenDatasetV2(BaseDataset):
|
| 93 |
+
def __init__(self,
|
| 94 |
+
dataset_dir,
|
| 95 |
+
horizon=1,
|
| 96 |
+
pad_before=0,
|
| 97 |
+
pad_after=0,
|
| 98 |
+
):
|
| 99 |
+
super().__init__()
|
| 100 |
+
|
| 101 |
+
data_directory = pathlib.Path(dataset_dir)
|
| 102 |
+
observations = np.load(
|
| 103 |
+
data_directory / "observations_seq.npy").astype(np.float32)
|
| 104 |
+
actions = np.load(
|
| 105 |
+
data_directory / "actions_seq.npy").astype(np.float32)
|
| 106 |
+
masks = np.load(data_directory / "existence_mask.npy").astype(np.int64)
|
| 107 |
+
|
| 108 |
+
self.state_normalizer = MinMaxNormalizer(observations)
|
| 109 |
+
self.action_normalizer = MinMaxNormalizer(actions)
|
| 110 |
+
|
| 111 |
+
normed_observations = self.state_normalizer.normalize(observations)
|
| 112 |
+
normed_actions = self.action_normalizer.normalize(actions)
|
| 113 |
+
|
| 114 |
+
self.replay_buffer = ReplayBuffer.create_empty_numpy()
|
| 115 |
+
for i in range(len(masks)):
|
| 116 |
+
eps_len = int(masks[i].sum())
|
| 117 |
+
obs = normed_observations[i, :eps_len]
|
| 118 |
+
action = normed_actions[i, :eps_len]
|
| 119 |
+
data = {'state': obs, 'action': action}
|
| 120 |
+
self.replay_buffer.add_episode(data)
|
| 121 |
+
|
| 122 |
+
self.sampler = SequenceSampler(
|
| 123 |
+
replay_buffer=self.replay_buffer,
|
| 124 |
+
sequence_length=horizon,
|
| 125 |
+
pad_before=pad_before,
|
| 126 |
+
pad_after=pad_after)
|
| 127 |
+
|
| 128 |
+
self.horizon = horizon
|
| 129 |
+
self.pad_before = pad_before
|
| 130 |
+
self.pad_after = pad_after
|
| 131 |
+
|
| 132 |
+
self.normalizer = self.get_normalizer()
|
| 133 |
+
self.obs_dim, self.act_dim = observations.shape[-1], actions.shape[-1]
|
| 134 |
+
|
| 135 |
+
self.normed_observations = normed_observations[np.where(masks)].reshape(-1, self.obs_dim)
|
| 136 |
+
self.normed_actions = normed_actions[np.where(masks)].reshape(-1, self.act_dim)
|
| 137 |
+
self.observations = observations
|
| 138 |
+
self.actions = actions
|
| 139 |
+
|
| 140 |
+
def get_normalizer(self):
|
| 141 |
+
return {
|
| 142 |
+
"state": self.state_normalizer,
|
| 143 |
+
"action": self.action_normalizer}
|
| 144 |
+
|
| 145 |
+
def __str__(self) -> str:
|
| 146 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 147 |
+
|
| 148 |
+
def __len__(self) -> int:
|
| 149 |
+
return len(self.sampler)
|
| 150 |
+
|
| 151 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 152 |
+
data = self.sampler.sample_sequence(idx)
|
| 153 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 154 |
+
return torch_data
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class KitchenMjlDataset(BaseDataset):
|
| 158 |
+
def __init__(self,
|
| 159 |
+
dataset_dir,
|
| 160 |
+
horizon=1,
|
| 161 |
+
pad_before=0,
|
| 162 |
+
pad_after=0,
|
| 163 |
+
abs_action=True,
|
| 164 |
+
robot_noise_ratio=0.1,
|
| 165 |
+
):
|
| 166 |
+
super().__init__()
|
| 167 |
+
|
| 168 |
+
data_directory = pathlib.Path(dataset_dir)
|
| 169 |
+
robot_pos_noise_amp = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
|
| 170 |
+
0.1, 0.005, 0.005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005,
|
| 171 |
+
0.0005, 0.005, 0.005, 0.005, 0.1, 0.1, 0.1, 0.005,
|
| 172 |
+
0.005, 0.005, 0.1, 0.1, 0.1, 0.005], dtype=np.float32)
|
| 173 |
+
rng = np.random.default_rng(seed=42)
|
| 174 |
+
|
| 175 |
+
data_directory = pathlib.Path(dataset_dir)
|
| 176 |
+
self.replay_buffer = ReplayBuffer.create_empty_numpy()
|
| 177 |
+
for i, mjl_path in enumerate(tqdm(list(data_directory.glob('*/*.mjl')))):
|
| 178 |
+
try:
|
| 179 |
+
data = parse_mjl_logs(str(mjl_path.absolute()), skipamount=40)
|
| 180 |
+
qpos = data['qpos'].astype(np.float32)
|
| 181 |
+
obs = np.concatenate([
|
| 182 |
+
qpos[:, :9],
|
| 183 |
+
qpos[:, -21:],
|
| 184 |
+
np.zeros((len(qpos), 30), dtype=np.float32)
|
| 185 |
+
], axis=-1)
|
| 186 |
+
if robot_noise_ratio > 0:
|
| 187 |
+
# add observation noise to match real robot
|
| 188 |
+
noise = robot_noise_ratio * robot_pos_noise_amp * rng.uniform(
|
| 189 |
+
low=-1., high=1., size=(obs.shape[0], 30))
|
| 190 |
+
obs[:, :30] += noise
|
| 191 |
+
episode = {
|
| 192 |
+
'state': obs,
|
| 193 |
+
'action': data['ctrl'].astype(np.float32)
|
| 194 |
+
}
|
| 195 |
+
self.replay_buffer.add_episode(episode)
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(i, e)
|
| 198 |
+
|
| 199 |
+
self.sampler = SequenceSampler(
|
| 200 |
+
replay_buffer=self.replay_buffer,
|
| 201 |
+
sequence_length=horizon,
|
| 202 |
+
pad_before=pad_before,
|
| 203 |
+
pad_after=pad_after)
|
| 204 |
+
|
| 205 |
+
self.horizon = horizon
|
| 206 |
+
self.pad_before = pad_before
|
| 207 |
+
self.pad_after = pad_after
|
| 208 |
+
|
| 209 |
+
self.normalizer = self.get_normalizer()
|
| 210 |
+
|
| 211 |
+
def get_normalizer(self):
|
| 212 |
+
state_normalizer = MinMaxNormalizer(
|
| 213 |
+
self.replay_buffer['state'][:]) # (N, obs_dim)
|
| 214 |
+
action_normalizer = MinMaxNormalizer(
|
| 215 |
+
self.replay_buffer['action'][:]) # (N, action_dim)
|
| 216 |
+
return {
|
| 217 |
+
"obs": {
|
| 218 |
+
"state": state_normalizer
|
| 219 |
+
},
|
| 220 |
+
"action": action_normalizer
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
def sample_to_data(self, sample):
|
| 224 |
+
state = sample['state'].astype(np.float32)
|
| 225 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 226 |
+
|
| 227 |
+
action = sample['action'].astype(np.float32)
|
| 228 |
+
action = self.normalizer['action'].normalize(action)
|
| 229 |
+
data = {
|
| 230 |
+
'obs': {
|
| 231 |
+
'state': state,
|
| 232 |
+
},
|
| 233 |
+
'action': action,
|
| 234 |
+
}
|
| 235 |
+
return data
|
| 236 |
+
|
| 237 |
+
def __str__(self) -> str:
|
| 238 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 239 |
+
|
| 240 |
+
def __len__(self) -> int:
|
| 241 |
+
return len(self.sampler)
|
| 242 |
+
|
| 243 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 244 |
+
sample = self.sampler.sample_sequence(idx)
|
| 245 |
+
data = self.sample_to_data(sample)
|
| 246 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 247 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/pusht_dataset.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import copy
|
| 5 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 6 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 7 |
+
from cleandiffuser.dataset.dataset_utils import SequenceSampler, MinMaxNormalizer, ImageNormalizer, dict_apply
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# data/pusht_cchi_v7_replay.zarr
|
| 11 |
+
# ├── data
|
| 12 |
+
# │ ├── action (25650, 2) float32
|
| 13 |
+
# │ ├── img (25650, 96, 96, 3) float32
|
| 14 |
+
# │ ├── keypoint (25650, 9, 2) float32
|
| 15 |
+
# │ ├── n_contacts (25650, 1) float32
|
| 16 |
+
# │ └── state (25650, 5) float32
|
| 17 |
+
# └── meta
|
| 18 |
+
# └── episode_ends (206,) int64
|
| 19 |
+
# assert('data' in root)
|
| 20 |
+
# assert('meta' in root)
|
| 21 |
+
# assert('episode_ends' in root['meta'])
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class PushTStateDataset(BaseDataset):
|
| 25 |
+
def __init__(self,
|
| 26 |
+
dataset_path,
|
| 27 |
+
obs_keys=["state", "action"],
|
| 28 |
+
horizon=1,
|
| 29 |
+
pad_before=0,
|
| 30 |
+
pad_after=0,
|
| 31 |
+
abs_action=False
|
| 32 |
+
):
|
| 33 |
+
|
| 34 |
+
super().__init__()
|
| 35 |
+
self.replay_buffer = ReplayBuffer.copy_from_path(
|
| 36 |
+
dataset_path, keys=obs_keys)
|
| 37 |
+
|
| 38 |
+
self.sampler = SequenceSampler(
|
| 39 |
+
replay_buffer=self.replay_buffer,
|
| 40 |
+
sequence_length=horizon,
|
| 41 |
+
pad_before=pad_before,
|
| 42 |
+
pad_after=pad_after)
|
| 43 |
+
|
| 44 |
+
self.horizon = horizon
|
| 45 |
+
self.pad_before = pad_before
|
| 46 |
+
self.pad_after = pad_after
|
| 47 |
+
|
| 48 |
+
self.normalizer = self.get_normalizer()
|
| 49 |
+
|
| 50 |
+
def get_normalizer(self):
|
| 51 |
+
state_normalizer = MinMaxNormalizer(self.replay_buffer['state'][:]) # (N, obs_dim)
|
| 52 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
|
| 53 |
+
return {
|
| 54 |
+
"obs": {
|
| 55 |
+
"state": state_normalizer
|
| 56 |
+
},
|
| 57 |
+
"action": action_normalizer
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
def __str__(self) -> str:
|
| 61 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 62 |
+
|
| 63 |
+
def __len__(self) -> int:
|
| 64 |
+
return len(self.sampler)
|
| 65 |
+
|
| 66 |
+
def _sample_to_data(self, sample):
|
| 67 |
+
state = sample['state'].astype(np.float32) # (T, 5)
|
| 68 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 69 |
+
|
| 70 |
+
action = sample['action'].astype(np.float32) # (T, 2)
|
| 71 |
+
action = self.normalizer['action'].normalize(action)
|
| 72 |
+
data = {
|
| 73 |
+
'obs': {
|
| 74 |
+
'state': state, # T, 5
|
| 75 |
+
},
|
| 76 |
+
'action': action, # T, 2
|
| 77 |
+
}
|
| 78 |
+
return data
|
| 79 |
+
|
| 80 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 81 |
+
sample = self.sampler.sample_sequence(idx)
|
| 82 |
+
data = self._sample_to_data(sample)
|
| 83 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 84 |
+
return torch_data
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class PushTKeypointDataset(BaseDataset):
|
| 88 |
+
def __init__(self,
|
| 89 |
+
dataset_path,
|
| 90 |
+
obs_keys=['keypoint', 'state', 'action'],
|
| 91 |
+
horizon=1,
|
| 92 |
+
pad_before=0,
|
| 93 |
+
pad_after=0,
|
| 94 |
+
abs_action=False
|
| 95 |
+
):
|
| 96 |
+
|
| 97 |
+
super().__init__()
|
| 98 |
+
self.replay_buffer = ReplayBuffer.copy_from_path(
|
| 99 |
+
dataset_path, keys=obs_keys)
|
| 100 |
+
|
| 101 |
+
self.sampler = SequenceSampler(
|
| 102 |
+
replay_buffer=self.replay_buffer,
|
| 103 |
+
sequence_length=horizon,
|
| 104 |
+
pad_before=pad_before,
|
| 105 |
+
pad_after=pad_after)
|
| 106 |
+
|
| 107 |
+
self.horizon = horizon
|
| 108 |
+
self.pad_before = pad_before
|
| 109 |
+
self.pad_after = pad_after
|
| 110 |
+
|
| 111 |
+
self.normalizer = self.get_normalizer()
|
| 112 |
+
|
| 113 |
+
def get_normalizer(self):
|
| 114 |
+
agent_pos_normalizer = MinMaxNormalizer(self.replay_buffer['state'][:, :2]) # (N, 2)
|
| 115 |
+
keypoint_normalizer = MinMaxNormalizer(self.replay_buffer['keypoint'][:]) # (N, 9, 2)
|
| 116 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, 2)
|
| 117 |
+
return {
|
| 118 |
+
"obs": {
|
| 119 |
+
"keypoint": keypoint_normalizer,
|
| 120 |
+
"agent_pos": agent_pos_normalizer
|
| 121 |
+
},
|
| 122 |
+
"action": action_normalizer
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
def __str__(self) -> str:
|
| 126 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 127 |
+
|
| 128 |
+
def __len__(self) -> int:
|
| 129 |
+
return len(self.sampler)
|
| 130 |
+
|
| 131 |
+
def _sample_to_data(self, sample):
|
| 132 |
+
# keypoint
|
| 133 |
+
data_size = sample['keypoint'].shape[0] # (T, 9, 2)
|
| 134 |
+
keypoint = sample['keypoint'].reshape(-1, sample['keypoint'].shape[-1]).astype(np.float32)
|
| 135 |
+
keypoint = self.normalizer['obs']['keypoint'].normalize(keypoint)
|
| 136 |
+
keypoint = keypoint.reshape(data_size, -1) # (T, 18)
|
| 137 |
+
|
| 138 |
+
# agent_pos
|
| 139 |
+
agent_pos = sample['state'][:,:2].astype(np.float32) # (T, 2)
|
| 140 |
+
agent_pos = self.normalizer['obs']['agent_pos'].normalize(agent_pos)
|
| 141 |
+
|
| 142 |
+
# action
|
| 143 |
+
action = sample['action'].astype(np.float32) # (T, 2)
|
| 144 |
+
action = self.normalizer['action'].normalize(action)
|
| 145 |
+
|
| 146 |
+
data = {
|
| 147 |
+
'obs': {
|
| 148 |
+
'keypoint': keypoint, # T, 18
|
| 149 |
+
'agent_pos': agent_pos, # T, 2
|
| 150 |
+
},
|
| 151 |
+
'action': action, # T, 2
|
| 152 |
+
}
|
| 153 |
+
return data
|
| 154 |
+
|
| 155 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 156 |
+
sample = self.sampler.sample_sequence(idx)
|
| 157 |
+
data = self._sample_to_data(sample)
|
| 158 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 159 |
+
return torch_data
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class PushTImageDataset(BaseDataset):
|
| 163 |
+
def __init__(self,
|
| 164 |
+
zarr_path,
|
| 165 |
+
obs_keys=['img', 'state', 'action'],
|
| 166 |
+
horizon=1,
|
| 167 |
+
pad_before=0,
|
| 168 |
+
pad_after=0,
|
| 169 |
+
abs_action=False
|
| 170 |
+
):
|
| 171 |
+
|
| 172 |
+
super().__init__()
|
| 173 |
+
self.replay_buffer = ReplayBuffer.copy_from_path(
|
| 174 |
+
zarr_path, keys=obs_keys)
|
| 175 |
+
|
| 176 |
+
self.sampler = SequenceSampler(
|
| 177 |
+
replay_buffer=self.replay_buffer,
|
| 178 |
+
sequence_length=horizon,
|
| 179 |
+
pad_before=pad_before,
|
| 180 |
+
pad_after=pad_after)
|
| 181 |
+
|
| 182 |
+
self.horizon = horizon
|
| 183 |
+
self.pad_before = pad_before
|
| 184 |
+
self.pad_after = pad_after
|
| 185 |
+
|
| 186 |
+
self.normalizer = self.get_normalizer()
|
| 187 |
+
|
| 188 |
+
def get_normalizer(self):
|
| 189 |
+
agent_pos_normalizer = MinMaxNormalizer(self.replay_buffer['state'][...,:2])
|
| 190 |
+
image_normalizer = ImageNormalizer()
|
| 191 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:])
|
| 192 |
+
|
| 193 |
+
return {
|
| 194 |
+
"obs": {
|
| 195 |
+
"image": image_normalizer,
|
| 196 |
+
"agent_pos": agent_pos_normalizer
|
| 197 |
+
},
|
| 198 |
+
"action": action_normalizer
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
def __str__(self) -> str:
|
| 202 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 203 |
+
|
| 204 |
+
def __len__(self) -> int:
|
| 205 |
+
return len(self.sampler)
|
| 206 |
+
|
| 207 |
+
def _sample_to_data(self, sample):
|
| 208 |
+
# image
|
| 209 |
+
image = np.moveaxis(sample['img'], -1, 1) / 255
|
| 210 |
+
image = self.normalizer['obs']['image'].normalize(image)
|
| 211 |
+
|
| 212 |
+
# agent_pos
|
| 213 |
+
agent_pos = sample['state'][:,:2].astype(np.float32) # (T, 2)
|
| 214 |
+
agent_pos = self.normalizer['obs']['agent_pos'].normalize(agent_pos)
|
| 215 |
+
|
| 216 |
+
# action
|
| 217 |
+
action = sample['action'].astype(np.float32) # (T, 2)
|
| 218 |
+
action = self.normalizer['action'].normalize(action)
|
| 219 |
+
|
| 220 |
+
data = {
|
| 221 |
+
'obs': {
|
| 222 |
+
'image': image, # T, 3, 96, 96
|
| 223 |
+
'agent_pos': agent_pos, # T, 2
|
| 224 |
+
},
|
| 225 |
+
'action': action, # T, 2
|
| 226 |
+
}
|
| 227 |
+
return data
|
| 228 |
+
|
| 229 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 230 |
+
sample = self.sampler.sample_sequence(idx)
|
| 231 |
+
data = self._sample_to_data(sample)
|
| 232 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 233 |
+
return torch_data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/replay_buffer.py
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union, Dict, Optional
|
| 2 |
+
import os
|
| 3 |
+
import math
|
| 4 |
+
import numbers
|
| 5 |
+
import zarr
|
| 6 |
+
import numcodecs
|
| 7 |
+
import numpy as np
|
| 8 |
+
from functools import cached_property
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def check_chunks_compatible(chunks: tuple, shape: tuple):
|
| 12 |
+
assert len(shape) == len(chunks)
|
| 13 |
+
for c in chunks:
|
| 14 |
+
assert isinstance(c, numbers.Integral)
|
| 15 |
+
assert c > 0
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def rechunk_recompress_array(group, name,
|
| 19 |
+
chunks=None, chunk_length=None,
|
| 20 |
+
compressor=None, tmp_key='_temp'):
|
| 21 |
+
old_arr = group[name]
|
| 22 |
+
if chunks is None:
|
| 23 |
+
if chunk_length is not None:
|
| 24 |
+
chunks = (chunk_length,) + old_arr.chunks[1:]
|
| 25 |
+
else:
|
| 26 |
+
chunks = old_arr.chunks
|
| 27 |
+
check_chunks_compatible(chunks, old_arr.shape)
|
| 28 |
+
|
| 29 |
+
if compressor is None:
|
| 30 |
+
compressor = old_arr.compressor
|
| 31 |
+
|
| 32 |
+
if (chunks == old_arr.chunks) and (compressor == old_arr.compressor):
|
| 33 |
+
# no change
|
| 34 |
+
return old_arr
|
| 35 |
+
|
| 36 |
+
# rechunk recompress
|
| 37 |
+
group.move(name, tmp_key)
|
| 38 |
+
old_arr = group[tmp_key]
|
| 39 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy(
|
| 40 |
+
source=old_arr,
|
| 41 |
+
dest=group,
|
| 42 |
+
name=name,
|
| 43 |
+
chunks=chunks,
|
| 44 |
+
compressor=compressor,
|
| 45 |
+
)
|
| 46 |
+
del group[tmp_key]
|
| 47 |
+
arr = group[name]
|
| 48 |
+
return arr
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_optimal_chunks(shape, dtype,
|
| 52 |
+
target_chunk_bytes=2e6,
|
| 53 |
+
max_chunk_length=None):
|
| 54 |
+
"""
|
| 55 |
+
Common shapes
|
| 56 |
+
T,D
|
| 57 |
+
T,N,D
|
| 58 |
+
T,H,W,C
|
| 59 |
+
T,N,H,W,C
|
| 60 |
+
"""
|
| 61 |
+
itemsize = np.dtype(dtype).itemsize
|
| 62 |
+
# reversed
|
| 63 |
+
rshape = list(shape[::-1])
|
| 64 |
+
if max_chunk_length is not None:
|
| 65 |
+
rshape[-1] = int(max_chunk_length)
|
| 66 |
+
split_idx = len(shape) - 1
|
| 67 |
+
for i in range(len(shape) - 1):
|
| 68 |
+
this_chunk_bytes = itemsize * np.prod(rshape[:i])
|
| 69 |
+
next_chunk_bytes = itemsize * np.prod(rshape[:i + 1])
|
| 70 |
+
if this_chunk_bytes <= target_chunk_bytes \
|
| 71 |
+
and next_chunk_bytes > target_chunk_bytes:
|
| 72 |
+
split_idx = i
|
| 73 |
+
|
| 74 |
+
rchunks = rshape[:split_idx]
|
| 75 |
+
item_chunk_bytes = itemsize * np.prod(rshape[:split_idx])
|
| 76 |
+
this_max_chunk_length = rshape[split_idx]
|
| 77 |
+
next_chunk_length = min(this_max_chunk_length, math.ceil(
|
| 78 |
+
target_chunk_bytes / item_chunk_bytes))
|
| 79 |
+
rchunks.append(next_chunk_length)
|
| 80 |
+
len_diff = len(shape) - len(rchunks)
|
| 81 |
+
rchunks.extend([1] * len_diff)
|
| 82 |
+
chunks = tuple(rchunks[::-1])
|
| 83 |
+
# print(np.prod(chunks) * itemsize / target_chunk_bytes)
|
| 84 |
+
return chunks
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class ReplayBuffer:
|
| 88 |
+
"""
|
| 89 |
+
Zarr-based temporal datastructure.
|
| 90 |
+
Assumes first dimension to be time. Only chunk in time dimension.
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
def __init__(self, root: Union[zarr.Group, Dict[str, dict]]):
|
| 94 |
+
"""
|
| 95 |
+
Dummy constructor. Use copy_from* and create_from* class methods instead.
|
| 96 |
+
"""
|
| 97 |
+
assert ('data' in root)
|
| 98 |
+
assert ('meta' in root)
|
| 99 |
+
assert ('episode_ends' in root['meta'])
|
| 100 |
+
for key, value in root['data'].items():
|
| 101 |
+
assert (value.shape[0] == root['meta']['episode_ends'][-1])
|
| 102 |
+
self.root = root
|
| 103 |
+
|
| 104 |
+
# ============= create constructors ===============
|
| 105 |
+
@classmethod
|
| 106 |
+
def create_empty_zarr(cls, storage=None, root=None):
|
| 107 |
+
if root is None:
|
| 108 |
+
if storage is None:
|
| 109 |
+
storage = zarr.MemoryStore()
|
| 110 |
+
root = zarr.group(store=storage)
|
| 111 |
+
data = root.require_group('data', overwrite=False)
|
| 112 |
+
meta = root.require_group('meta', overwrite=False)
|
| 113 |
+
if 'episode_ends' not in meta:
|
| 114 |
+
episode_ends = meta.zeros('episode_ends', shape=(0,), dtype=np.int64,
|
| 115 |
+
compressor=None, overwrite=False)
|
| 116 |
+
return cls(root=root)
|
| 117 |
+
|
| 118 |
+
@classmethod
|
| 119 |
+
def create_empty_numpy(cls):
|
| 120 |
+
root = {
|
| 121 |
+
'data': dict(),
|
| 122 |
+
'meta': {
|
| 123 |
+
'episode_ends': np.zeros((0,), dtype=np.int64)
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
return cls(root=root)
|
| 127 |
+
|
| 128 |
+
@classmethod
|
| 129 |
+
def create_from_group(cls, group, **kwargs):
|
| 130 |
+
if 'data' not in group:
|
| 131 |
+
# create from stratch
|
| 132 |
+
buffer = cls.create_empty_zarr(root=group, **kwargs)
|
| 133 |
+
else:
|
| 134 |
+
# already exist
|
| 135 |
+
buffer = cls(root=group, **kwargs)
|
| 136 |
+
return buffer
|
| 137 |
+
|
| 138 |
+
@classmethod
|
| 139 |
+
def create_from_path(cls, zarr_path, mode='r', **kwargs):
|
| 140 |
+
"""
|
| 141 |
+
Open an on-disk zarr directly (for dataset larger than memory).
|
| 142 |
+
Slower.
|
| 143 |
+
"""
|
| 144 |
+
group = zarr.open(os.path.expanduser(zarr_path), mode)
|
| 145 |
+
return cls.create_from_group(group, **kwargs)
|
| 146 |
+
|
| 147 |
+
# ============= copy constructors ===============
|
| 148 |
+
@classmethod
|
| 149 |
+
def copy_from_store(cls, src_store, store=None, keys=None,
|
| 150 |
+
chunks: Dict[str, tuple] = dict(),
|
| 151 |
+
compressors: Union[dict, str, numcodecs.abc.Codec] = dict(),
|
| 152 |
+
if_exists='replace',
|
| 153 |
+
**kwargs):
|
| 154 |
+
"""
|
| 155 |
+
Load to memory.
|
| 156 |
+
"""
|
| 157 |
+
src_root = zarr.group(src_store)
|
| 158 |
+
root = None
|
| 159 |
+
if store is None:
|
| 160 |
+
# numpy backend
|
| 161 |
+
meta = dict()
|
| 162 |
+
for key, value in src_root['meta'].items():
|
| 163 |
+
if len(value.shape) == 0:
|
| 164 |
+
meta[key] = np.array(value)
|
| 165 |
+
else:
|
| 166 |
+
meta[key] = value[:]
|
| 167 |
+
|
| 168 |
+
if keys is None:
|
| 169 |
+
keys = src_root['data'].keys()
|
| 170 |
+
data = dict()
|
| 171 |
+
for key in keys:
|
| 172 |
+
arr = src_root['data'][key]
|
| 173 |
+
data[key] = arr[:]
|
| 174 |
+
|
| 175 |
+
root = {
|
| 176 |
+
'meta': meta,
|
| 177 |
+
'data': data
|
| 178 |
+
}
|
| 179 |
+
else:
|
| 180 |
+
root = zarr.group(store=store)
|
| 181 |
+
# copy without recompression
|
| 182 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy_store(source=src_store, dest=store,
|
| 183 |
+
source_path='/meta', dest_path='/meta',
|
| 184 |
+
if_exists=if_exists)
|
| 185 |
+
data_group = root.create_group('data', overwrite=True)
|
| 186 |
+
if keys is None:
|
| 187 |
+
keys = src_root['data'].keys()
|
| 188 |
+
for key in keys:
|
| 189 |
+
value = src_root['data'][key]
|
| 190 |
+
cks = cls._resolve_array_chunks(
|
| 191 |
+
chunks=chunks, key=key, array=value)
|
| 192 |
+
cpr = cls._resolve_array_compressor(
|
| 193 |
+
compressors=compressors, key=key, array=value)
|
| 194 |
+
if cks == value.chunks and cpr == value.compressor:
|
| 195 |
+
# copy without recompression
|
| 196 |
+
this_path = '/data/' + key
|
| 197 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
|
| 198 |
+
source=src_store, dest=store,
|
| 199 |
+
source_path=this_path, dest_path=this_path,
|
| 200 |
+
if_exists=if_exists
|
| 201 |
+
)
|
| 202 |
+
else:
|
| 203 |
+
# copy with recompression
|
| 204 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy(
|
| 205 |
+
source=value, dest=data_group, name=key,
|
| 206 |
+
chunks=cks, compressor=cpr, if_exists=if_exists
|
| 207 |
+
)
|
| 208 |
+
buffer = cls(root=root)
|
| 209 |
+
return buffer
|
| 210 |
+
|
| 211 |
+
@classmethod
|
| 212 |
+
def copy_from_path(cls, zarr_path, backend=None, store=None, keys=None,
|
| 213 |
+
chunks: Dict[str, tuple] = dict(),
|
| 214 |
+
compressors: Union[dict, str, numcodecs.abc.Codec] = dict(),
|
| 215 |
+
if_exists='replace',
|
| 216 |
+
**kwargs):
|
| 217 |
+
"""
|
| 218 |
+
Copy a on-disk zarr to in-memory compressed.
|
| 219 |
+
Recommended
|
| 220 |
+
"""
|
| 221 |
+
if backend == 'numpy':
|
| 222 |
+
print('backend argument is deprecated!')
|
| 223 |
+
store = None
|
| 224 |
+
group = zarr.open(os.path.expanduser(zarr_path), 'r')
|
| 225 |
+
return cls.copy_from_store(src_store=group.store, store=store,
|
| 226 |
+
keys=keys, chunks=chunks, compressors=compressors,
|
| 227 |
+
if_exists=if_exists, **kwargs)
|
| 228 |
+
|
| 229 |
+
# ============= save methods ===============
|
| 230 |
+
def save_to_store(self, store,
|
| 231 |
+
chunks: Optional[Dict[str, tuple]] = dict(),
|
| 232 |
+
compressors: Union[str, numcodecs.abc.Codec, dict] = dict(),
|
| 233 |
+
if_exists='replace',
|
| 234 |
+
**kwargs):
|
| 235 |
+
|
| 236 |
+
root = zarr.group(store)
|
| 237 |
+
if self.backend == 'zarr':
|
| 238 |
+
# recompression free copy
|
| 239 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
|
| 240 |
+
source=self.root.store, dest=store,
|
| 241 |
+
source_path='/meta', dest_path='/meta', if_exists=if_exists)
|
| 242 |
+
else:
|
| 243 |
+
meta_group = root.create_group('meta', overwrite=True)
|
| 244 |
+
# save meta, no chunking
|
| 245 |
+
for key, value in self.root['meta'].items():
|
| 246 |
+
_ = meta_group.array(
|
| 247 |
+
name=key,
|
| 248 |
+
data=value,
|
| 249 |
+
shape=value.shape,
|
| 250 |
+
chunks=value.shape)
|
| 251 |
+
|
| 252 |
+
# save data, chunk
|
| 253 |
+
data_group = root.create_group('data', overwrite=True)
|
| 254 |
+
for key, value in self.root['data'].items():
|
| 255 |
+
cks = self._resolve_array_chunks(
|
| 256 |
+
chunks=chunks, key=key, array=value)
|
| 257 |
+
cpr = self._resolve_array_compressor(
|
| 258 |
+
compressors=compressors, key=key, array=value)
|
| 259 |
+
if isinstance(value, zarr.Array):
|
| 260 |
+
if cks == value.chunks and cpr == value.compressor:
|
| 261 |
+
# copy without recompression
|
| 262 |
+
this_path = '/data/' + key
|
| 263 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
|
| 264 |
+
source=self.root.store, dest=store,
|
| 265 |
+
source_path=this_path, dest_path=this_path, if_exists=if_exists)
|
| 266 |
+
else:
|
| 267 |
+
# copy with recompression
|
| 268 |
+
n_copied, n_skipped, n_bytes_copied = zarr.copy(
|
| 269 |
+
source=value, dest=data_group, name=key,
|
| 270 |
+
chunks=cks, compressor=cpr, if_exists=if_exists
|
| 271 |
+
)
|
| 272 |
+
else:
|
| 273 |
+
# numpy
|
| 274 |
+
_ = data_group.array(
|
| 275 |
+
name=key,
|
| 276 |
+
data=value,
|
| 277 |
+
chunks=cks,
|
| 278 |
+
compressor=cpr
|
| 279 |
+
)
|
| 280 |
+
return store
|
| 281 |
+
|
| 282 |
+
def save_to_path(self, zarr_path,
|
| 283 |
+
chunks: Optional[Dict[str, tuple]] = dict(),
|
| 284 |
+
compressors: Union[str, numcodecs.abc.Codec, dict] = dict(),
|
| 285 |
+
if_exists='replace',
|
| 286 |
+
**kwargs):
|
| 287 |
+
store = zarr.DirectoryStore(os.path.expanduser(zarr_path))
|
| 288 |
+
return self.save_to_store(store, chunks=chunks,
|
| 289 |
+
compressors=compressors, if_exists=if_exists, **kwargs)
|
| 290 |
+
|
| 291 |
+
@staticmethod
|
| 292 |
+
def resolve_compressor(compressor='default'):
|
| 293 |
+
if compressor == 'default':
|
| 294 |
+
compressor = numcodecs.Blosc(cname='lz4', clevel=5,
|
| 295 |
+
shuffle=numcodecs.Blosc.NOSHUFFLE)
|
| 296 |
+
elif compressor == 'disk':
|
| 297 |
+
compressor = numcodecs.Blosc('zstd', clevel=5,
|
| 298 |
+
shuffle=numcodecs.Blosc.BITSHUFFLE)
|
| 299 |
+
return compressor
|
| 300 |
+
|
| 301 |
+
@classmethod
|
| 302 |
+
def _resolve_array_compressor(cls,
|
| 303 |
+
compressors: Union[dict, str, numcodecs.abc.Codec], key, array):
|
| 304 |
+
# allows compressor to be explicitly set to None
|
| 305 |
+
cpr = 'nil'
|
| 306 |
+
if isinstance(compressors, dict):
|
| 307 |
+
if key in compressors:
|
| 308 |
+
cpr = cls.resolve_compressor(compressors[key])
|
| 309 |
+
elif isinstance(array, zarr.Array):
|
| 310 |
+
cpr = array.compressor
|
| 311 |
+
else:
|
| 312 |
+
cpr = cls.resolve_compressor(compressors)
|
| 313 |
+
# backup default
|
| 314 |
+
if cpr == 'nil':
|
| 315 |
+
cpr = cls.resolve_compressor('default')
|
| 316 |
+
return cpr
|
| 317 |
+
|
| 318 |
+
@classmethod
|
| 319 |
+
def _resolve_array_chunks(cls,
|
| 320 |
+
chunks: Union[dict, tuple], key, array):
|
| 321 |
+
cks = None
|
| 322 |
+
if isinstance(chunks, dict):
|
| 323 |
+
if key in chunks:
|
| 324 |
+
cks = chunks[key]
|
| 325 |
+
elif isinstance(array, zarr.Array):
|
| 326 |
+
cks = array.chunks
|
| 327 |
+
elif isinstance(chunks, tuple):
|
| 328 |
+
cks = chunks
|
| 329 |
+
else:
|
| 330 |
+
raise TypeError(f"Unsupported chunks type {type(chunks)}")
|
| 331 |
+
# backup default
|
| 332 |
+
if cks is None:
|
| 333 |
+
cks = get_optimal_chunks(shape=array.shape, dtype=array.dtype)
|
| 334 |
+
# check
|
| 335 |
+
check_chunks_compatible(chunks=cks, shape=array.shape)
|
| 336 |
+
return cks
|
| 337 |
+
|
| 338 |
+
# ============= properties =================
|
| 339 |
+
@cached_property
|
| 340 |
+
def data(self):
|
| 341 |
+
return self.root['data']
|
| 342 |
+
|
| 343 |
+
@cached_property
|
| 344 |
+
def meta(self):
|
| 345 |
+
return self.root['meta']
|
| 346 |
+
|
| 347 |
+
def update_meta(self, data):
|
| 348 |
+
# sanitize data
|
| 349 |
+
np_data = dict()
|
| 350 |
+
for key, value in data.items():
|
| 351 |
+
if isinstance(value, np.ndarray):
|
| 352 |
+
np_data[key] = value
|
| 353 |
+
else:
|
| 354 |
+
arr = np.array(value)
|
| 355 |
+
if arr.dtype == object:
|
| 356 |
+
raise TypeError(f"Invalid value type {type(value)}")
|
| 357 |
+
np_data[key] = arr
|
| 358 |
+
|
| 359 |
+
meta_group = self.meta
|
| 360 |
+
if self.backend == 'zarr':
|
| 361 |
+
for key, value in np_data.items():
|
| 362 |
+
_ = meta_group.array(
|
| 363 |
+
name=key,
|
| 364 |
+
data=value,
|
| 365 |
+
shape=value.shape,
|
| 366 |
+
chunks=value.shape,
|
| 367 |
+
overwrite=True)
|
| 368 |
+
else:
|
| 369 |
+
meta_group.update(np_data)
|
| 370 |
+
|
| 371 |
+
return meta_group
|
| 372 |
+
|
| 373 |
+
@property
|
| 374 |
+
def episode_ends(self):
|
| 375 |
+
return self.meta['episode_ends']
|
| 376 |
+
|
| 377 |
+
def get_episode_idxs(self):
|
| 378 |
+
import numba
|
| 379 |
+
numba.jit(nopython=True)
|
| 380 |
+
|
| 381 |
+
def _get_episode_idxs(episode_ends):
|
| 382 |
+
result = np.zeros((episode_ends[-1],), dtype=np.int64)
|
| 383 |
+
for i in range(len(episode_ends)):
|
| 384 |
+
start = 0
|
| 385 |
+
if i > 0:
|
| 386 |
+
start = episode_ends[i - 1]
|
| 387 |
+
end = episode_ends[i]
|
| 388 |
+
for idx in range(start, end):
|
| 389 |
+
result[idx] = i
|
| 390 |
+
return result
|
| 391 |
+
|
| 392 |
+
return _get_episode_idxs(self.episode_ends)
|
| 393 |
+
|
| 394 |
+
@property
|
| 395 |
+
def backend(self):
|
| 396 |
+
backend = 'numpy'
|
| 397 |
+
if isinstance(self.root, zarr.Group):
|
| 398 |
+
backend = 'zarr'
|
| 399 |
+
return backend
|
| 400 |
+
|
| 401 |
+
# =========== dict-like API ==============
|
| 402 |
+
def __repr__(self) -> str:
|
| 403 |
+
if self.backend == 'zarr':
|
| 404 |
+
return str(self.root.tree())
|
| 405 |
+
else:
|
| 406 |
+
return super().__repr__()
|
| 407 |
+
|
| 408 |
+
def keys(self):
|
| 409 |
+
return self.data.keys()
|
| 410 |
+
|
| 411 |
+
def values(self):
|
| 412 |
+
return self.data.values()
|
| 413 |
+
|
| 414 |
+
def items(self):
|
| 415 |
+
return self.data.items()
|
| 416 |
+
|
| 417 |
+
def __getitem__(self, key):
|
| 418 |
+
return self.data[key]
|
| 419 |
+
|
| 420 |
+
def __contains__(self, key):
|
| 421 |
+
return key in self.data
|
| 422 |
+
|
| 423 |
+
# =========== our API ==============
|
| 424 |
+
@property
|
| 425 |
+
def n_steps(self):
|
| 426 |
+
if len(self.episode_ends) == 0:
|
| 427 |
+
return 0
|
| 428 |
+
return self.episode_ends[-1]
|
| 429 |
+
|
| 430 |
+
@property
|
| 431 |
+
def n_episodes(self):
|
| 432 |
+
return len(self.episode_ends)
|
| 433 |
+
|
| 434 |
+
@property
|
| 435 |
+
def chunk_size(self):
|
| 436 |
+
if self.backend == 'zarr':
|
| 437 |
+
return next(iter(self.data.arrays()))[-1].chunks[0]
|
| 438 |
+
return None
|
| 439 |
+
|
| 440 |
+
@property
|
| 441 |
+
def episode_lengths(self):
|
| 442 |
+
ends = self.episode_ends[:]
|
| 443 |
+
ends = np.insert(ends, 0, 0)
|
| 444 |
+
lengths = np.diff(ends)
|
| 445 |
+
return lengths
|
| 446 |
+
|
| 447 |
+
def add_episode(self,
|
| 448 |
+
data: Dict[str, np.ndarray],
|
| 449 |
+
chunks: Optional[Dict[str, tuple]] = dict(),
|
| 450 |
+
compressors: Union[str, numcodecs.abc.Codec, dict] = dict()):
|
| 451 |
+
assert (len(data) > 0)
|
| 452 |
+
is_zarr = (self.backend == 'zarr')
|
| 453 |
+
|
| 454 |
+
curr_len = self.n_steps
|
| 455 |
+
episode_length = None
|
| 456 |
+
for key, value in data.items():
|
| 457 |
+
assert (len(value.shape) >= 1)
|
| 458 |
+
if episode_length is None:
|
| 459 |
+
episode_length = len(value)
|
| 460 |
+
else:
|
| 461 |
+
assert (episode_length == len(value))
|
| 462 |
+
new_len = curr_len + episode_length
|
| 463 |
+
|
| 464 |
+
for key, value in data.items():
|
| 465 |
+
new_shape = (new_len,) + value.shape[1:]
|
| 466 |
+
# create array
|
| 467 |
+
if key not in self.data:
|
| 468 |
+
if is_zarr:
|
| 469 |
+
cks = self._resolve_array_chunks(
|
| 470 |
+
chunks=chunks, key=key, array=value)
|
| 471 |
+
cpr = self._resolve_array_compressor(
|
| 472 |
+
compressors=compressors, key=key, array=value)
|
| 473 |
+
arr = self.data.zeros(name=key,
|
| 474 |
+
shape=new_shape,
|
| 475 |
+
chunks=cks,
|
| 476 |
+
dtype=value.dtype,
|
| 477 |
+
compressor=cpr)
|
| 478 |
+
else:
|
| 479 |
+
# copy data to prevent modify
|
| 480 |
+
arr = np.zeros(shape=new_shape, dtype=value.dtype)
|
| 481 |
+
self.data[key] = arr
|
| 482 |
+
else:
|
| 483 |
+
arr = self.data[key]
|
| 484 |
+
assert (value.shape[1:] == arr.shape[1:])
|
| 485 |
+
# same method for both zarr and numpy
|
| 486 |
+
if is_zarr:
|
| 487 |
+
arr.resize(new_shape)
|
| 488 |
+
else:
|
| 489 |
+
arr.resize(new_shape, refcheck=False)
|
| 490 |
+
# copy data
|
| 491 |
+
arr[-value.shape[0]:] = value
|
| 492 |
+
|
| 493 |
+
# append to episode ends
|
| 494 |
+
episode_ends = self.episode_ends
|
| 495 |
+
if is_zarr:
|
| 496 |
+
episode_ends.resize(episode_ends.shape[0] + 1)
|
| 497 |
+
else:
|
| 498 |
+
episode_ends.resize(episode_ends.shape[0] + 1, refcheck=False)
|
| 499 |
+
episode_ends[-1] = new_len
|
| 500 |
+
|
| 501 |
+
# rechunk
|
| 502 |
+
if is_zarr:
|
| 503 |
+
if episode_ends.chunks[0] < episode_ends.shape[0]:
|
| 504 |
+
rechunk_recompress_array(self.meta, 'episode_ends',
|
| 505 |
+
chunk_length=int(episode_ends.shape[0] * 1.5))
|
| 506 |
+
|
| 507 |
+
def drop_episode(self):
|
| 508 |
+
is_zarr = (self.backend == 'zarr')
|
| 509 |
+
episode_ends = self.episode_ends[:].copy()
|
| 510 |
+
assert (len(episode_ends) > 0)
|
| 511 |
+
start_idx = 0
|
| 512 |
+
if len(episode_ends) > 1:
|
| 513 |
+
start_idx = episode_ends[-2]
|
| 514 |
+
for key, value in self.data.items():
|
| 515 |
+
new_shape = (start_idx,) + value.shape[1:]
|
| 516 |
+
if is_zarr:
|
| 517 |
+
value.resize(new_shape)
|
| 518 |
+
else:
|
| 519 |
+
value.resize(new_shape, refcheck=False)
|
| 520 |
+
if is_zarr:
|
| 521 |
+
self.episode_ends.resize(len(episode_ends) - 1)
|
| 522 |
+
else:
|
| 523 |
+
self.episode_ends.resize(len(episode_ends) - 1, refcheck=False)
|
| 524 |
+
|
| 525 |
+
def pop_episode(self):
|
| 526 |
+
assert (self.n_episodes > 0)
|
| 527 |
+
episode = self.get_episode(self.n_episodes - 1, copy=True)
|
| 528 |
+
self.drop_episode()
|
| 529 |
+
return episode
|
| 530 |
+
|
| 531 |
+
def extend(self, data):
|
| 532 |
+
self.add_episode(data)
|
| 533 |
+
|
| 534 |
+
def get_episode(self, idx, copy=False):
|
| 535 |
+
idx = list(range(len(self.episode_ends)))[idx]
|
| 536 |
+
start_idx = 0
|
| 537 |
+
if idx > 0:
|
| 538 |
+
start_idx = self.episode_ends[idx - 1]
|
| 539 |
+
end_idx = self.episode_ends[idx]
|
| 540 |
+
result = self.get_steps_slice(start_idx, end_idx, copy=copy)
|
| 541 |
+
return result
|
| 542 |
+
|
| 543 |
+
def get_episode_slice(self, idx):
|
| 544 |
+
start_idx = 0
|
| 545 |
+
if idx > 0:
|
| 546 |
+
start_idx = self.episode_ends[idx - 1]
|
| 547 |
+
end_idx = self.episode_ends[idx]
|
| 548 |
+
return slice(start_idx, end_idx)
|
| 549 |
+
|
| 550 |
+
def get_steps_slice(self, start, stop, step=None, copy=False):
|
| 551 |
+
_slice = slice(start, stop, step)
|
| 552 |
+
|
| 553 |
+
result = dict()
|
| 554 |
+
for key, value in self.data.items():
|
| 555 |
+
x = value[_slice]
|
| 556 |
+
if copy and isinstance(value, np.ndarray):
|
| 557 |
+
x = x.copy()
|
| 558 |
+
result[key] = x
|
| 559 |
+
return result
|
| 560 |
+
|
| 561 |
+
# =========== chunking =============
|
| 562 |
+
def get_chunks(self) -> dict:
|
| 563 |
+
assert self.backend == 'zarr'
|
| 564 |
+
chunks = dict()
|
| 565 |
+
for key, value in self.data.items():
|
| 566 |
+
chunks[key] = value.chunks
|
| 567 |
+
return chunks
|
| 568 |
+
|
| 569 |
+
def set_chunks(self, chunks: dict):
|
| 570 |
+
assert self.backend == 'zarr'
|
| 571 |
+
for key, value in chunks.items():
|
| 572 |
+
if key in self.data:
|
| 573 |
+
arr = self.data[key]
|
| 574 |
+
if value != arr.chunks:
|
| 575 |
+
check_chunks_compatible(chunks=value, shape=arr.shape)
|
| 576 |
+
rechunk_recompress_array(self.data, key, chunks=value)
|
| 577 |
+
|
| 578 |
+
def get_compressors(self) -> dict:
|
| 579 |
+
assert self.backend == 'zarr'
|
| 580 |
+
compressors = dict()
|
| 581 |
+
for key, value in self.data.items():
|
| 582 |
+
compressors[key] = value.compressor
|
| 583 |
+
return compressors
|
| 584 |
+
|
| 585 |
+
def set_compressors(self, compressors: dict):
|
| 586 |
+
assert self.backend == 'zarr'
|
| 587 |
+
for key, value in compressors.items():
|
| 588 |
+
if key in self.data:
|
| 589 |
+
arr = self.data[key]
|
| 590 |
+
compressor = self.resolve_compressor(value)
|
| 591 |
+
if compressor != arr.compressor:
|
| 592 |
+
rechunk_recompress_array(self.data, key, compressor=compressor)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_dataset.py
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import copy
|
| 5 |
+
import pathlib
|
| 6 |
+
import h5py
|
| 7 |
+
import zarr
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
import concurrent.futures
|
| 10 |
+
from collections import defaultdict
|
| 11 |
+
from cleandiffuser.dataset.imagecodecs import register_codecs, Jpeg2k
|
| 12 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 13 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 14 |
+
from cleandiffuser.dataset.dataset_utils import SequenceSampler, EmptyNormalizer, RotationTransformer, dict_apply, \
|
| 15 |
+
MinMaxNormalizer, ImageNormalizer
|
| 16 |
+
|
| 17 |
+
register_codecs()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class RobomimicDataset(BaseDataset):
|
| 21 |
+
def __init__(self,
|
| 22 |
+
dataset_dir,
|
| 23 |
+
horizon=1,
|
| 24 |
+
pad_before=0,
|
| 25 |
+
pad_after=0,
|
| 26 |
+
obs_keys=('object', 'robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos'),
|
| 27 |
+
abs_action=False,
|
| 28 |
+
rotation_rep='rotation_6d',
|
| 29 |
+
):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.rotation_transformer = RotationTransformer(
|
| 32 |
+
from_rep='axis_angle', to_rep=rotation_rep)
|
| 33 |
+
|
| 34 |
+
self.replay_buffer = ReplayBuffer.create_empty_numpy()
|
| 35 |
+
with h5py.File(dataset_dir) as file:
|
| 36 |
+
demos = file['data']
|
| 37 |
+
for i in tqdm(range(len(demos)), desc="Loading hdf5 to ReplayBuffer"):
|
| 38 |
+
demo = demos[f'demo_{i}']
|
| 39 |
+
episode = _data_to_obs(
|
| 40 |
+
raw_obs=demo['obs'],
|
| 41 |
+
raw_actions=demo['actions'][:].astype(np.float32),
|
| 42 |
+
obs_keys=obs_keys,
|
| 43 |
+
abs_action=abs_action,
|
| 44 |
+
rotation_transformer=self.rotation_transformer)
|
| 45 |
+
self.replay_buffer.add_episode(episode)
|
| 46 |
+
|
| 47 |
+
self.sampler = SequenceSampler(
|
| 48 |
+
replay_buffer=self.replay_buffer,
|
| 49 |
+
sequence_length=horizon,
|
| 50 |
+
pad_before=pad_before,
|
| 51 |
+
pad_after=pad_after)
|
| 52 |
+
|
| 53 |
+
self.horizon = horizon
|
| 54 |
+
self.pad_before = pad_before
|
| 55 |
+
self.pad_after = pad_after
|
| 56 |
+
self.abs_action = abs_action
|
| 57 |
+
self.normalizer = self.get_normalizer()
|
| 58 |
+
|
| 59 |
+
def undo_transform_action(self, action):
|
| 60 |
+
raw_shape = action.shape
|
| 61 |
+
if raw_shape[-1] == 20:
|
| 62 |
+
# dual arm
|
| 63 |
+
action = action.reshape(-1, 2, 10)
|
| 64 |
+
|
| 65 |
+
d_rot = action.shape[-1] - 4
|
| 66 |
+
pos = action[..., :3]
|
| 67 |
+
rot = action[..., 3:3 + d_rot]
|
| 68 |
+
gripper = action[..., [-1]]
|
| 69 |
+
rot = self.rotation_transformer.inverse(rot)
|
| 70 |
+
uaction = np.concatenate([
|
| 71 |
+
pos, rot, gripper
|
| 72 |
+
], axis=-1)
|
| 73 |
+
|
| 74 |
+
if raw_shape[-1] == 20:
|
| 75 |
+
# dual arm
|
| 76 |
+
uaction = uaction.reshape(*raw_shape[:-1], 14)
|
| 77 |
+
|
| 78 |
+
return uaction
|
| 79 |
+
|
| 80 |
+
def get_normalizer(self):
|
| 81 |
+
if self.abs_action:
|
| 82 |
+
state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
|
| 83 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
|
| 84 |
+
else:
|
| 85 |
+
state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
|
| 86 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
|
| 87 |
+
return {
|
| 88 |
+
"obs": {
|
| 89 |
+
"state": state_normalizer
|
| 90 |
+
},
|
| 91 |
+
"action": action_normalizer
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
def sample_to_data(self, sample):
|
| 95 |
+
state = sample['obs'].astype(np.float32)
|
| 96 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 97 |
+
|
| 98 |
+
action = sample['action'].astype(np.float32)
|
| 99 |
+
action = self.normalizer['action'].normalize(action)
|
| 100 |
+
data = {
|
| 101 |
+
'obs': {
|
| 102 |
+
'state': state
|
| 103 |
+
},
|
| 104 |
+
'action': action,
|
| 105 |
+
}
|
| 106 |
+
return data
|
| 107 |
+
|
| 108 |
+
def __str__(self) -> str:
|
| 109 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 110 |
+
|
| 111 |
+
def __len__(self) -> int:
|
| 112 |
+
return len(self.sampler)
|
| 113 |
+
|
| 114 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 115 |
+
sample = self.sampler.sample_sequence(idx)
|
| 116 |
+
data = self.sample_to_data(sample)
|
| 117 |
+
torch_data = dict_apply(data, torch.tensor)
|
| 118 |
+
return torch_data
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def _data_to_obs(raw_obs, raw_actions, obs_keys, abs_action, rotation_transformer):
|
| 122 |
+
obs = np.concatenate([
|
| 123 |
+
raw_obs[key] for key in obs_keys
|
| 124 |
+
], axis=-1).astype(np.float32)
|
| 125 |
+
|
| 126 |
+
if abs_action:
|
| 127 |
+
is_dual_arm = False
|
| 128 |
+
if raw_actions.shape[-1] == 14:
|
| 129 |
+
# dual arm
|
| 130 |
+
raw_actions = raw_actions.reshape(-1, 2, 7)
|
| 131 |
+
is_dual_arm = True
|
| 132 |
+
|
| 133 |
+
pos = raw_actions[..., :3]
|
| 134 |
+
rot = raw_actions[..., 3:6]
|
| 135 |
+
gripper = raw_actions[..., 6:]
|
| 136 |
+
rot = rotation_transformer.forward(rot)
|
| 137 |
+
raw_actions = np.concatenate([
|
| 138 |
+
pos, rot, gripper
|
| 139 |
+
], axis=-1).astype(np.float32)
|
| 140 |
+
|
| 141 |
+
if is_dual_arm:
|
| 142 |
+
raw_actions = raw_actions.reshape(-1, 20)
|
| 143 |
+
|
| 144 |
+
data = {
|
| 145 |
+
'obs': obs,
|
| 146 |
+
'action': raw_actions
|
| 147 |
+
}
|
| 148 |
+
return data
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class RobomimicImageDataset(BaseDataset):
|
| 152 |
+
def __init__(self,
|
| 153 |
+
dataset_dir,
|
| 154 |
+
shape_meta: dict,
|
| 155 |
+
n_obs_steps=None,
|
| 156 |
+
horizon=1,
|
| 157 |
+
pad_before=0,
|
| 158 |
+
pad_after=0,
|
| 159 |
+
abs_action=False,
|
| 160 |
+
rotation_rep='rotation_6d',
|
| 161 |
+
):
|
| 162 |
+
super().__init__()
|
| 163 |
+
self.rotation_transformer = RotationTransformer(
|
| 164 |
+
from_rep='axis_angle', to_rep=rotation_rep)
|
| 165 |
+
|
| 166 |
+
self.replay_buffer = _convert_robomimic_to_replay(
|
| 167 |
+
store=zarr.MemoryStore(),
|
| 168 |
+
shape_meta=shape_meta,
|
| 169 |
+
dataset_path=dataset_dir,
|
| 170 |
+
abs_action=abs_action,
|
| 171 |
+
rotation_transformer=self.rotation_transformer)
|
| 172 |
+
|
| 173 |
+
rgb_keys = list()
|
| 174 |
+
lowdim_keys = list()
|
| 175 |
+
obs_shape_meta = shape_meta['obs']
|
| 176 |
+
for key, attr in obs_shape_meta.items():
|
| 177 |
+
type = attr.get('type', 'low_dim')
|
| 178 |
+
if type == 'rgb':
|
| 179 |
+
rgb_keys.append(key)
|
| 180 |
+
elif type == 'low_dim':
|
| 181 |
+
lowdim_keys.append(key)
|
| 182 |
+
|
| 183 |
+
key_first_k = dict()
|
| 184 |
+
if n_obs_steps is not None:
|
| 185 |
+
# only take first k obs from images
|
| 186 |
+
for key in rgb_keys + lowdim_keys:
|
| 187 |
+
key_first_k[key] = n_obs_steps
|
| 188 |
+
self.sampler = SequenceSampler(
|
| 189 |
+
replay_buffer=self.replay_buffer,
|
| 190 |
+
sequence_length=horizon,
|
| 191 |
+
pad_before=pad_before,
|
| 192 |
+
pad_after=pad_after,
|
| 193 |
+
key_first_k=key_first_k
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
self.shape_meta = shape_meta
|
| 197 |
+
self.rgb_keys = rgb_keys
|
| 198 |
+
self.lowdim_keys = lowdim_keys
|
| 199 |
+
self.abs_action = abs_action
|
| 200 |
+
self.horizon = horizon
|
| 201 |
+
self.pad_before = pad_before
|
| 202 |
+
self.pad_after = pad_after
|
| 203 |
+
self.n_obs_steps = n_obs_steps
|
| 204 |
+
|
| 205 |
+
self.normalizer = self.get_normalizer()
|
| 206 |
+
|
| 207 |
+
def get_normalizer(self):
|
| 208 |
+
normalizer = defaultdict(dict)
|
| 209 |
+
for key in self.lowdim_keys:
|
| 210 |
+
normalizer['obs'][key] = MinMaxNormalizer(self.replay_buffer[key][:])
|
| 211 |
+
for key in self.rgb_keys:
|
| 212 |
+
normalizer['obs'][key] = ImageNormalizer()
|
| 213 |
+
normalizer['action'] = MinMaxNormalizer(self.replay_buffer['action'][:])
|
| 214 |
+
|
| 215 |
+
return normalizer
|
| 216 |
+
|
| 217 |
+
def __str__(self) -> str:
|
| 218 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 219 |
+
|
| 220 |
+
def __len__(self) -> int:
|
| 221 |
+
return len(self.sampler)
|
| 222 |
+
|
| 223 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 224 |
+
sample = self.sampler.sample_sequence(idx)
|
| 225 |
+
|
| 226 |
+
# obs
|
| 227 |
+
# to save RAM, only return first n_obs_steps of OBS
|
| 228 |
+
# since the rest will be discarded anyway.
|
| 229 |
+
# when self.n_obs_steps is None
|
| 230 |
+
# this slice does nothing (takes all)
|
| 231 |
+
T_slice = slice(self.n_obs_steps)
|
| 232 |
+
|
| 233 |
+
obs_dict = dict()
|
| 234 |
+
for key in self.rgb_keys:
|
| 235 |
+
# move channel last to channel first
|
| 236 |
+
# T,H,W,C
|
| 237 |
+
# convert uint8 image to float32
|
| 238 |
+
obs_dict[key] = np.moveaxis(sample[key][T_slice], -1, 1
|
| 239 |
+
).astype(np.float32) / 255.
|
| 240 |
+
# T,C,H,W
|
| 241 |
+
del sample[key]
|
| 242 |
+
obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
|
| 243 |
+
|
| 244 |
+
for key in self.lowdim_keys:
|
| 245 |
+
obs_dict[key] = sample[key][T_slice].astype(np.float32)
|
| 246 |
+
del sample[key]
|
| 247 |
+
obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
|
| 248 |
+
|
| 249 |
+
# action
|
| 250 |
+
action = sample['action'].astype(np.float32)
|
| 251 |
+
action = self.normalizer['action'].normalize(action)
|
| 252 |
+
|
| 253 |
+
torch_data = {
|
| 254 |
+
'obs': dict_apply(obs_dict, torch.tensor),
|
| 255 |
+
'action': torch.tensor(action)
|
| 256 |
+
}
|
| 257 |
+
return torch_data
|
| 258 |
+
|
| 259 |
+
def undo_transform_action(self, action):
|
| 260 |
+
raw_shape = action.shape
|
| 261 |
+
if raw_shape[-1] == 20:
|
| 262 |
+
# dual arm
|
| 263 |
+
action = action.reshape(-1, 2, 10)
|
| 264 |
+
|
| 265 |
+
d_rot = action.shape[-1] - 4
|
| 266 |
+
pos = action[..., :3]
|
| 267 |
+
rot = action[..., 3:3 + d_rot]
|
| 268 |
+
gripper = action[..., [-1]]
|
| 269 |
+
rot = self.rotation_transformer.inverse(rot)
|
| 270 |
+
uaction = np.concatenate([
|
| 271 |
+
pos, rot, gripper
|
| 272 |
+
], axis=-1)
|
| 273 |
+
|
| 274 |
+
if raw_shape[-1] == 20:
|
| 275 |
+
# dual arm
|
| 276 |
+
uaction = uaction.reshape(*raw_shape[:-1], 14)
|
| 277 |
+
|
| 278 |
+
return uaction
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _convert_actions(raw_actions, abs_action, rotation_transformer):
|
| 282 |
+
actions = raw_actions
|
| 283 |
+
if abs_action:
|
| 284 |
+
is_dual_arm = False
|
| 285 |
+
if raw_actions.shape[-1] == 14:
|
| 286 |
+
# dual arm
|
| 287 |
+
raw_actions = raw_actions.reshape(-1, 2, 7)
|
| 288 |
+
is_dual_arm = True
|
| 289 |
+
|
| 290 |
+
pos = raw_actions[..., :3]
|
| 291 |
+
rot = raw_actions[..., 3:6]
|
| 292 |
+
gripper = raw_actions[..., 6:]
|
| 293 |
+
rot = rotation_transformer.forward(rot)
|
| 294 |
+
raw_actions = np.concatenate([
|
| 295 |
+
pos, rot, gripper
|
| 296 |
+
], axis=-1).astype(np.float32)
|
| 297 |
+
|
| 298 |
+
if is_dual_arm:
|
| 299 |
+
raw_actions = raw_actions.reshape(-1, 20)
|
| 300 |
+
actions = raw_actions
|
| 301 |
+
return actions
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def _convert_robomimic_to_replay(store, shape_meta, dataset_path, abs_action, rotation_transformer,
|
| 305 |
+
n_workers=None, max_inflight_tasks=None):
|
| 306 |
+
""" Convert Robomimic dataset to ReplayBuffer
|
| 307 |
+
|
| 308 |
+
A ReplayBuffer is a `zarr.Group` or Dict[str, dict] that contains the following keys:
|
| 309 |
+
- data: zarr.Group or Dict[str, dict]
|
| 310 |
+
Contains the data. All data should be stored as numpy arrays with the same length.
|
| 311 |
+
- meta: zarr.Group or Dict[str, dict]
|
| 312 |
+
Contains key "episode_ends", which is a numpy array of shape (n_episodes,) that contains the
|
| 313 |
+
end index of each episode in the data.
|
| 314 |
+
|
| 315 |
+
Args:
|
| 316 |
+
- store: zarr.Store
|
| 317 |
+
zarr.MemoryStore()
|
| 318 |
+
- shape_meta: dict
|
| 319 |
+
Shape metadata of the dataset. Should contain keys 'obs', 'action'.
|
| 320 |
+
For example:
|
| 321 |
+
shape_meta = {
|
| 322 |
+
"action": {"shape": [10, ]},
|
| 323 |
+
"obs": {
|
| 324 |
+
"agentview_image": {"shape": [84, 84, 3], "type": "rgb"},
|
| 325 |
+
"robot0_eef_pos": {"shape": [3, ], "type": "low_dim"},
|
| 326 |
+
}}
|
| 327 |
+
- dataset_path: str
|
| 328 |
+
Path to the Robomimic dataset
|
| 329 |
+
- abs_action: bool
|
| 330 |
+
Whether to use position or velocity control
|
| 331 |
+
- rotation_transformer: RotationTransformer
|
| 332 |
+
Rotation transformer to convert rotation representation
|
| 333 |
+
"""
|
| 334 |
+
|
| 335 |
+
""" Dataset structure of Can-PH, as an example:
|
| 336 |
+
- data
|
| 337 |
+
- demo_0
|
| 338 |
+
- actions (118, 7)
|
| 339 |
+
- dones (118, )
|
| 340 |
+
- next_obs
|
| 341 |
+
- agentview_image (118, 84, 84, 3)
|
| 342 |
+
- object (118, 14)
|
| 343 |
+
- robot0_eef_pos (118, 3)
|
| 344 |
+
- robot0_eef_quat
|
| 345 |
+
- robot0_eef_vel_ang
|
| 346 |
+
- robot0_eef_vel_lin
|
| 347 |
+
- robot0_eye_in_hand_image
|
| 348 |
+
- robot0_gripper_qpos
|
| 349 |
+
- robot0_gripper_qvel
|
| 350 |
+
- robot0_joint_pos
|
| 351 |
+
- robot0_joint_pos_cos
|
| 352 |
+
- robot0_joint_pos_sin
|
| 353 |
+
- robot0_joint_vel
|
| 354 |
+
- obs
|
| 355 |
+
...
|
| 356 |
+
- rewards (118, )
|
| 357 |
+
- states (118, 71)
|
| 358 |
+
- demo_1
|
| 359 |
+
...(x200 demos)
|
| 360 |
+
- mask
|
| 361 |
+
- 20_percent
|
| 362 |
+
- 20_percent_train
|
| 363 |
+
- 20_percent_valid
|
| 364 |
+
- 50_percent
|
| 365 |
+
- 50_percent_train
|
| 366 |
+
- 50_percent_valid
|
| 367 |
+
- train (180,)
|
| 368 |
+
- valid (20,)
|
| 369 |
+
|
| 370 |
+
Suppose that the `shape_meta` is:
|
| 371 |
+
shape_meta = {
|
| 372 |
+
"action": {"shape": [10, ]},
|
| 373 |
+
"obs": {
|
| 374 |
+
"agentview_image": {
|
| 375 |
+
"shape": [3, 84, 84], "type": "rgb", },
|
| 376 |
+
"robot0_eye_in_hand_image": {
|
| 377 |
+
"shape": [3, 84, 84], "type": "rgb", },
|
| 378 |
+
"robot0_eef_pos": {
|
| 379 |
+
"shape": [3, ], "type": "low_dim", },
|
| 380 |
+
"robot0_eef_quat": {
|
| 381 |
+
"shape": [4, ], "type": "low_dim", },
|
| 382 |
+
"robot0_gripper_qpos": {
|
| 383 |
+
"shape": [2, ], "type": "low_dim", }, }}
|
| 384 |
+
"""
|
| 385 |
+
|
| 386 |
+
import multiprocessing
|
| 387 |
+
if n_workers is None:
|
| 388 |
+
n_workers = multiprocessing.cpu_count()
|
| 389 |
+
if max_inflight_tasks is None:
|
| 390 |
+
max_inflight_tasks = n_workers * 5
|
| 391 |
+
|
| 392 |
+
# parse shape_meta
|
| 393 |
+
rgb_keys = list()
|
| 394 |
+
lowdim_keys = list()
|
| 395 |
+
# construct compressors and chunks
|
| 396 |
+
obs_shape_meta = shape_meta['obs']
|
| 397 |
+
for key, attr in obs_shape_meta.items():
|
| 398 |
+
shape = attr['shape']
|
| 399 |
+
type = attr.get('type', 'low_dim')
|
| 400 |
+
if type == 'rgb':
|
| 401 |
+
rgb_keys.append(key)
|
| 402 |
+
elif type == 'low_dim':
|
| 403 |
+
lowdim_keys.append(key)
|
| 404 |
+
# rgb_keys = ['agentview_image', 'robot0_eye_in_hand_image']
|
| 405 |
+
# lowdim_keys = ['robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos']
|
| 406 |
+
|
| 407 |
+
# create zarr group
|
| 408 |
+
root = zarr.group(store)
|
| 409 |
+
data_group = root.require_group('data', overwrite=True)
|
| 410 |
+
meta_group = root.require_group('meta', overwrite=True)
|
| 411 |
+
|
| 412 |
+
with h5py.File(dataset_path) as file:
|
| 413 |
+
|
| 414 |
+
# count total steps
|
| 415 |
+
demos = file['data']
|
| 416 |
+
episode_ends = list()
|
| 417 |
+
prev_end = 0
|
| 418 |
+
for i in range(len(demos)):
|
| 419 |
+
demo = demos[f'demo_{i}']
|
| 420 |
+
episode_length = demo['actions'].shape[0]
|
| 421 |
+
episode_end = prev_end + episode_length
|
| 422 |
+
prev_end = episode_end
|
| 423 |
+
episode_ends.append(episode_end)
|
| 424 |
+
n_steps = episode_ends[-1]
|
| 425 |
+
episode_starts = [0] + episode_ends[:-1]
|
| 426 |
+
_ = meta_group.array('episode_ends', episode_ends,
|
| 427 |
+
dtype=np.int64, compressor=None, overwrite=True)
|
| 428 |
+
|
| 429 |
+
# save lowdim data
|
| 430 |
+
for key in tqdm(lowdim_keys + ['action'], desc="Loading lowdim data"):
|
| 431 |
+
data_key = 'obs/' + key
|
| 432 |
+
if key == 'action':
|
| 433 |
+
data_key = 'actions'
|
| 434 |
+
this_data = list()
|
| 435 |
+
for i in range(len(demos)):
|
| 436 |
+
demo = demos[f'demo_{i}']
|
| 437 |
+
this_data.append(demo[data_key][:].astype(np.float32))
|
| 438 |
+
this_data = np.concatenate(this_data, axis=0)
|
| 439 |
+
if key == 'action':
|
| 440 |
+
this_data = _convert_actions(
|
| 441 |
+
raw_actions=this_data,
|
| 442 |
+
abs_action=abs_action,
|
| 443 |
+
rotation_transformer=rotation_transformer
|
| 444 |
+
)
|
| 445 |
+
assert this_data.shape == (n_steps,) + tuple(shape_meta['action']['shape'])
|
| 446 |
+
else:
|
| 447 |
+
assert this_data.shape == (n_steps,) + tuple(shape_meta['obs'][key]['shape'])
|
| 448 |
+
_ = data_group.array(
|
| 449 |
+
name=key,
|
| 450 |
+
data=this_data,
|
| 451 |
+
shape=this_data.shape,
|
| 452 |
+
chunks=this_data.shape,
|
| 453 |
+
compressor=None,
|
| 454 |
+
dtype=this_data.dtype
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
def img_copy(zarr_arr, zarr_idx, hdf5_arr, hdf5_idx):
|
| 458 |
+
try:
|
| 459 |
+
zarr_arr[zarr_idx] = hdf5_arr[hdf5_idx]
|
| 460 |
+
# make sure we can successfully decode
|
| 461 |
+
_ = zarr_arr[zarr_idx]
|
| 462 |
+
return True
|
| 463 |
+
except Exception as e:
|
| 464 |
+
return False
|
| 465 |
+
|
| 466 |
+
with tqdm(total=n_steps * len(rgb_keys), desc="Loading image data", mininterval=1.0) as pbar:
|
| 467 |
+
# one chunk per thread, therefore no synchronization needed
|
| 468 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=n_workers) as executor:
|
| 469 |
+
futures = set()
|
| 470 |
+
for key in rgb_keys:
|
| 471 |
+
data_key = 'obs/' + key
|
| 472 |
+
shape = tuple(shape_meta['obs'][key]['shape'])
|
| 473 |
+
c, h, w = shape
|
| 474 |
+
this_compressor = Jpeg2k(level=50)
|
| 475 |
+
img_arr = data_group.require_dataset(
|
| 476 |
+
name=key,
|
| 477 |
+
shape=(n_steps, h, w, c),
|
| 478 |
+
chunks=(1, h, w, c),
|
| 479 |
+
compressor=this_compressor,
|
| 480 |
+
dtype=np.uint8
|
| 481 |
+
)
|
| 482 |
+
for episode_idx in range(len(demos)):
|
| 483 |
+
demo = demos[f'demo_{episode_idx}']
|
| 484 |
+
hdf5_arr = demo['obs'][key]
|
| 485 |
+
for hdf5_idx in range(hdf5_arr.shape[0]):
|
| 486 |
+
if len(futures) >= max_inflight_tasks:
|
| 487 |
+
# limit number of inflight tasks
|
| 488 |
+
completed, futures = concurrent.futures.wait(futures,
|
| 489 |
+
return_when=concurrent.futures.FIRST_COMPLETED)
|
| 490 |
+
for f in completed:
|
| 491 |
+
if not f.result():
|
| 492 |
+
raise RuntimeError('Failed to encode image!')
|
| 493 |
+
pbar.update(len(completed))
|
| 494 |
+
|
| 495 |
+
zarr_idx = episode_starts[episode_idx] + hdf5_idx
|
| 496 |
+
futures.add(
|
| 497 |
+
executor.submit(img_copy,
|
| 498 |
+
img_arr, zarr_idx, hdf5_arr, hdf5_idx))
|
| 499 |
+
completed, futures = concurrent.futures.wait(futures)
|
| 500 |
+
for f in completed:
|
| 501 |
+
if not f.result():
|
| 502 |
+
raise RuntimeError('Failed to encode image!')
|
| 503 |
+
pbar.update(len(completed))
|
| 504 |
+
|
| 505 |
+
replay_buffer = ReplayBuffer(root)
|
| 506 |
+
return replay_buffer
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_rl_dataset.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import copy
|
| 5 |
+
import pathlib
|
| 6 |
+
import h5py
|
| 7 |
+
import zarr
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
import concurrent.futures
|
| 10 |
+
from collections import defaultdict
|
| 11 |
+
from cleandiffuser.dataset.imagecodecs import register_codecs, Jpeg2k
|
| 12 |
+
from cleandiffuser.dataset.base_dataset import BaseDataset
|
| 13 |
+
from cleandiffuser.dataset.replay_buffer import ReplayBuffer
|
| 14 |
+
from cleandiffuser.dataset.dataset_utils import SequenceSampler, EmptyNormalizer, RotationTransformer, dict_apply, \
|
| 15 |
+
MinMaxNormalizer, ImageNormalizer
|
| 16 |
+
|
| 17 |
+
register_codecs()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class RobomimicDataset(BaseDataset):
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
dataset_dir,
|
| 24 |
+
horizon=1,
|
| 25 |
+
pad_before=0,
|
| 26 |
+
pad_after=0,
|
| 27 |
+
obs_keys=['object', 'robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos'],
|
| 28 |
+
abs_action=False,
|
| 29 |
+
rotation_rep='rotation_6d',
|
| 30 |
+
):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.rotation_transformer = RotationTransformer(
|
| 33 |
+
from_rep='axis_angle', to_rep=rotation_rep)
|
| 34 |
+
|
| 35 |
+
self.replay_buffer = ReplayBuffer.create_empty_numpy()
|
| 36 |
+
with h5py.File(dataset_dir) as file:
|
| 37 |
+
demos = file['data']
|
| 38 |
+
for i in tqdm(range(len(demos)), desc="Loading hdf5 to ReplayBuffer"):
|
| 39 |
+
demo = demos[f'demo_{i}']
|
| 40 |
+
episode = _data_to_obs(
|
| 41 |
+
raw_obs=demo['obs'],
|
| 42 |
+
raw_actions=demo['actions'][:].astype(np.float32),
|
| 43 |
+
rewards=demo['rewards'],
|
| 44 |
+
next_raw_obs=demo['next_obs'],
|
| 45 |
+
dones=demo['dones'],
|
| 46 |
+
obs_keys=obs_keys,
|
| 47 |
+
abs_action=abs_action,
|
| 48 |
+
rotation_transformer=self.rotation_transformer)
|
| 49 |
+
self.replay_buffer.add_episode(episode)
|
| 50 |
+
|
| 51 |
+
self.sampler = SequenceSampler(
|
| 52 |
+
replay_buffer=self.replay_buffer,
|
| 53 |
+
sequence_length=horizon,
|
| 54 |
+
pad_before=pad_before,
|
| 55 |
+
pad_after=pad_after)
|
| 56 |
+
|
| 57 |
+
self.horizon = horizon
|
| 58 |
+
self.pad_before = pad_before
|
| 59 |
+
self.pad_after = pad_after
|
| 60 |
+
self.abs_action = abs_action
|
| 61 |
+
self.normalizer = self.get_normalizer()
|
| 62 |
+
|
| 63 |
+
def undo_transform_action(self, action):
|
| 64 |
+
raw_shape = action.shape
|
| 65 |
+
if raw_shape[-1] == 20:
|
| 66 |
+
# dual arm
|
| 67 |
+
action = action.reshape(-1, 2, 10)
|
| 68 |
+
|
| 69 |
+
d_rot = action.shape[-1] - 4
|
| 70 |
+
pos = action[..., :3]
|
| 71 |
+
rot = action[..., 3:3 + d_rot]
|
| 72 |
+
gripper = action[..., [-1]]
|
| 73 |
+
rot = self.rotation_transformer.inverse(rot)
|
| 74 |
+
uaction = np.concatenate([
|
| 75 |
+
pos, rot, gripper
|
| 76 |
+
], axis=-1)
|
| 77 |
+
|
| 78 |
+
if raw_shape[-1] == 20:
|
| 79 |
+
# dual arm
|
| 80 |
+
uaction = uaction.reshape(*raw_shape[:-1], 14)
|
| 81 |
+
|
| 82 |
+
return uaction
|
| 83 |
+
|
| 84 |
+
def get_normalizer(self):
|
| 85 |
+
state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
|
| 86 |
+
next_state_normalizer = MinMaxNormalizer(self.replay_buffer['next_obs'][:]) # (N, obs_dim)
|
| 87 |
+
action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
|
| 88 |
+
|
| 89 |
+
return {
|
| 90 |
+
"obs": {
|
| 91 |
+
"state": state_normalizer
|
| 92 |
+
},
|
| 93 |
+
"next_obs": {
|
| 94 |
+
"state": next_state_normalizer
|
| 95 |
+
},
|
| 96 |
+
"action": action_normalizer,
|
| 97 |
+
"reward": reward_normalizer,
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
def sample_to_data(self, sample):
|
| 101 |
+
state = sample['obs'].astype(np.float32)
|
| 102 |
+
state = self.normalizer['obs']['state'].normalize(state)
|
| 103 |
+
|
| 104 |
+
action = sample['action'].astype(np.float32)
|
| 105 |
+
action = self.normalizer['action'].normalize(action)
|
| 106 |
+
|
| 107 |
+
next_state = sample['next_obs'].astype(np.float32)
|
| 108 |
+
next_state = self.normalizer['next_obs']['state'].normalize(next_state)
|
| 109 |
+
|
| 110 |
+
reward = sample['reward'].astype(np.float32)
|
| 111 |
+
done = sample['done'].astype(np.float32)
|
| 112 |
+
|
| 113 |
+
data = {
|
| 114 |
+
'obs': {
|
| 115 |
+
'state': state
|
| 116 |
+
},
|
| 117 |
+
'next_obs': {
|
| 118 |
+
'state': next_state
|
| 119 |
+
},
|
| 120 |
+
'action': action,
|
| 121 |
+
'reward': reward,
|
| 122 |
+
'done': done,
|
| 123 |
+
}
|
| 124 |
+
return data
|
| 125 |
+
|
| 126 |
+
def __str__(self) -> str:
|
| 127 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 128 |
+
|
| 129 |
+
def __len__(self) -> int:
|
| 130 |
+
return len(self.sampler)
|
| 131 |
+
|
| 132 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 133 |
+
sample = self.sampler.sample_sequence(idx)
|
| 134 |
+
data = self.sample_to_data(sample)
|
| 135 |
+
torch_data = dict_apply(data, torch.from_numpy)
|
| 136 |
+
return torch_data
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def _data_to_obs(raw_obs, raw_actions, rewards, next_raw_obs, dones, obs_keys, abs_action, rotation_transformer):
|
| 140 |
+
# for key in obs_keys:
|
| 141 |
+
# print(key, raw_obs[key].shape)
|
| 142 |
+
# exit()
|
| 143 |
+
obs = np.concatenate([
|
| 144 |
+
raw_obs[key] for key in obs_keys
|
| 145 |
+
], axis=-1).astype(np.float32)
|
| 146 |
+
|
| 147 |
+
next_obs = np.concatenate([
|
| 148 |
+
next_raw_obs[key] for key in obs_keys
|
| 149 |
+
], axis=-1).astype(np.float32)
|
| 150 |
+
|
| 151 |
+
if abs_action:
|
| 152 |
+
is_dual_arm = False
|
| 153 |
+
if raw_actions.shape[-1] == 14:
|
| 154 |
+
# dual arm
|
| 155 |
+
raw_actions = raw_actions.reshape(-1, 2, 7)
|
| 156 |
+
is_dual_arm = True
|
| 157 |
+
|
| 158 |
+
pos = raw_actions[..., :3]
|
| 159 |
+
rot = raw_actions[..., 3:6]
|
| 160 |
+
gripper = raw_actions[..., 6:]
|
| 161 |
+
rot = rotation_transformer.forward(rot)
|
| 162 |
+
raw_actions = np.concatenate([
|
| 163 |
+
pos, rot, gripper
|
| 164 |
+
], axis=-1).astype(np.float32)
|
| 165 |
+
|
| 166 |
+
if is_dual_arm:
|
| 167 |
+
raw_actions = raw_actions.reshape(-1, 20)
|
| 168 |
+
|
| 169 |
+
data = {
|
| 170 |
+
'obs': obs,
|
| 171 |
+
'action': raw_actions,
|
| 172 |
+
'reward': rewards,
|
| 173 |
+
'next_obs': next_obs,
|
| 174 |
+
'done': dones,
|
| 175 |
+
}
|
| 176 |
+
return data
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class RobomimicImageDataset(BaseDataset):
|
| 180 |
+
def __init__(self,
|
| 181 |
+
dataset_dir,
|
| 182 |
+
shape_meta: dict,
|
| 183 |
+
n_obs_steps=None,
|
| 184 |
+
horizon=1,
|
| 185 |
+
pad_before=0,
|
| 186 |
+
pad_after=0,
|
| 187 |
+
abs_action=False,
|
| 188 |
+
rotation_rep='rotation_6d',
|
| 189 |
+
):
|
| 190 |
+
super().__init__()
|
| 191 |
+
self.rotation_transformer = RotationTransformer(
|
| 192 |
+
from_rep='axis_angle', to_rep=rotation_rep)
|
| 193 |
+
|
| 194 |
+
self.replay_buffer = _convert_robomimic_to_replay(
|
| 195 |
+
store=zarr.MemoryStore(),
|
| 196 |
+
shape_meta=shape_meta,
|
| 197 |
+
dataset_path=dataset_dir,
|
| 198 |
+
abs_action=abs_action,
|
| 199 |
+
rotation_transformer=self.rotation_transformer)
|
| 200 |
+
|
| 201 |
+
rgb_keys = list()
|
| 202 |
+
lowdim_keys = list()
|
| 203 |
+
obs_shape_meta = shape_meta['obs']
|
| 204 |
+
for key, attr in obs_shape_meta.items():
|
| 205 |
+
type = attr.get('type', 'low_dim')
|
| 206 |
+
if type == 'rgb':
|
| 207 |
+
rgb_keys.append(key)
|
| 208 |
+
elif type == 'low_dim':
|
| 209 |
+
lowdim_keys.append(key)
|
| 210 |
+
|
| 211 |
+
key_first_k = dict()
|
| 212 |
+
if n_obs_steps is not None:
|
| 213 |
+
# only take first k obs from images
|
| 214 |
+
for key in rgb_keys + lowdim_keys:
|
| 215 |
+
key_first_k[key] = n_obs_steps
|
| 216 |
+
self.sampler = SequenceSampler(
|
| 217 |
+
replay_buffer=self.replay_buffer,
|
| 218 |
+
sequence_length=horizon,
|
| 219 |
+
pad_before=pad_before,
|
| 220 |
+
pad_after=pad_after,
|
| 221 |
+
key_first_k=key_first_k
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
self.shape_meta = shape_meta
|
| 225 |
+
self.rgb_keys = rgb_keys
|
| 226 |
+
self.lowdim_keys = lowdim_keys
|
| 227 |
+
self.abs_action = abs_action
|
| 228 |
+
self.horizon = horizon
|
| 229 |
+
self.pad_before = pad_before
|
| 230 |
+
self.pad_after = pad_after
|
| 231 |
+
self.n_obs_steps = n_obs_steps
|
| 232 |
+
|
| 233 |
+
self.normalizer = self.get_normalizer()
|
| 234 |
+
|
| 235 |
+
def get_normalizer(self):
|
| 236 |
+
normalizer = defaultdict(dict)
|
| 237 |
+
for key in self.lowdim_keys:
|
| 238 |
+
normalizer['obs'][key] = MinMaxNormalizer(self.replay_buffer[key][:])
|
| 239 |
+
for key in self.rgb_keys:
|
| 240 |
+
normalizer['obs'][key] = ImageNormalizer()
|
| 241 |
+
normalizer['action'] = MinMaxNormalizer(self.replay_buffer['action'][:])
|
| 242 |
+
|
| 243 |
+
return normalizer
|
| 244 |
+
|
| 245 |
+
def __str__(self) -> str:
|
| 246 |
+
return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
|
| 247 |
+
|
| 248 |
+
def __len__(self) -> int:
|
| 249 |
+
return len(self.sampler)
|
| 250 |
+
|
| 251 |
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
| 252 |
+
sample = self.sampler.sample_sequence(idx)
|
| 253 |
+
|
| 254 |
+
# obs
|
| 255 |
+
# to save RAM, only return first n_obs_steps of OBS
|
| 256 |
+
# since the rest will be discarded anyway.
|
| 257 |
+
# when self.n_obs_steps is None
|
| 258 |
+
# this slice does nothing (takes all)
|
| 259 |
+
T_slice = slice(self.n_obs_steps)
|
| 260 |
+
|
| 261 |
+
obs_dict = dict()
|
| 262 |
+
for key in self.rgb_keys:
|
| 263 |
+
# move channel last to channel first
|
| 264 |
+
# T,H,W,C
|
| 265 |
+
# convert uint8 image to float32
|
| 266 |
+
obs_dict[key] = np.moveaxis(sample[key][T_slice], -1, 1
|
| 267 |
+
).astype(np.float32) / 255.
|
| 268 |
+
# T,C,H,W
|
| 269 |
+
del sample[key]
|
| 270 |
+
obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
|
| 271 |
+
|
| 272 |
+
for key in self.lowdim_keys:
|
| 273 |
+
obs_dict[key] = sample[key][T_slice].astype(np.float32)
|
| 274 |
+
del sample[key]
|
| 275 |
+
obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
|
| 276 |
+
|
| 277 |
+
# action
|
| 278 |
+
action = sample['action'].astype(np.float32)
|
| 279 |
+
action = self.normalizer['action'].normalize(action)
|
| 280 |
+
|
| 281 |
+
torch_data = {
|
| 282 |
+
'obs': dict_apply(obs_dict, torch.from_numpy),
|
| 283 |
+
'action': torch.from_numpy(action)
|
| 284 |
+
}
|
| 285 |
+
return torch_data
|
| 286 |
+
|
| 287 |
+
def undo_transform_action(self, action):
|
| 288 |
+
raw_shape = action.shape
|
| 289 |
+
if raw_shape[-1] == 20:
|
| 290 |
+
# dual arm
|
| 291 |
+
action = action.reshape(-1, 2, 10)
|
| 292 |
+
|
| 293 |
+
d_rot = action.shape[-1] - 4
|
| 294 |
+
pos = action[..., :3]
|
| 295 |
+
rot = action[..., 3:3 + d_rot]
|
| 296 |
+
gripper = action[..., [-1]]
|
| 297 |
+
rot = self.rotation_transformer.inverse(rot)
|
| 298 |
+
uaction = np.concatenate([
|
| 299 |
+
pos, rot, gripper
|
| 300 |
+
], axis=-1)
|
| 301 |
+
|
| 302 |
+
if raw_shape[-1] == 20:
|
| 303 |
+
# dual arm
|
| 304 |
+
uaction = uaction.reshape(*raw_shape[:-1], 14)
|
| 305 |
+
|
| 306 |
+
return uaction
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def _convert_actions(raw_actions, abs_action, rotation_transformer):
|
| 310 |
+
actions = raw_actions
|
| 311 |
+
if abs_action:
|
| 312 |
+
is_dual_arm = False
|
| 313 |
+
if raw_actions.shape[-1] == 14:
|
| 314 |
+
# dual arm
|
| 315 |
+
raw_actions = raw_actions.reshape(-1, 2, 7)
|
| 316 |
+
is_dual_arm = True
|
| 317 |
+
|
| 318 |
+
pos = raw_actions[..., :3]
|
| 319 |
+
rot = raw_actions[..., 3:6]
|
| 320 |
+
gripper = raw_actions[..., 6:]
|
| 321 |
+
rot = rotation_transformer.forward(rot)
|
| 322 |
+
raw_actions = np.concatenate([
|
| 323 |
+
pos, rot, gripper
|
| 324 |
+
], axis=-1).astype(np.float32)
|
| 325 |
+
|
| 326 |
+
if is_dual_arm:
|
| 327 |
+
raw_actions = raw_actions.reshape(-1, 20)
|
| 328 |
+
actions = raw_actions
|
| 329 |
+
return actions
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def _convert_robomimic_to_replay(store, shape_meta, dataset_path, abs_action, rotation_transformer,
|
| 333 |
+
n_workers=None, max_inflight_tasks=None):
|
| 334 |
+
import multiprocessing
|
| 335 |
+
if n_workers is None:
|
| 336 |
+
n_workers = multiprocessing.cpu_count()
|
| 337 |
+
if max_inflight_tasks is None:
|
| 338 |
+
max_inflight_tasks = n_workers * 5
|
| 339 |
+
|
| 340 |
+
# parse shape_meta
|
| 341 |
+
rgb_keys = list()
|
| 342 |
+
lowdim_keys = list()
|
| 343 |
+
# construct compressors and chunks
|
| 344 |
+
obs_shape_meta = shape_meta['obs']
|
| 345 |
+
for key, attr in obs_shape_meta.items():
|
| 346 |
+
shape = attr['shape']
|
| 347 |
+
type = attr.get('type', 'low_dim')
|
| 348 |
+
if type == 'rgb':
|
| 349 |
+
rgb_keys.append(key)
|
| 350 |
+
elif type == 'low_dim':
|
| 351 |
+
lowdim_keys.append(key)
|
| 352 |
+
|
| 353 |
+
root = zarr.group(store)
|
| 354 |
+
data_group = root.require_group('data', overwrite=True)
|
| 355 |
+
meta_group = root.require_group('meta', overwrite=True)
|
| 356 |
+
|
| 357 |
+
with h5py.File(dataset_path) as file:
|
| 358 |
+
# count total steps
|
| 359 |
+
demos = file['data']
|
| 360 |
+
episode_ends = list()
|
| 361 |
+
prev_end = 0
|
| 362 |
+
for i in range(len(demos)):
|
| 363 |
+
demo = demos[f'demo_{i}']
|
| 364 |
+
episode_length = demo['actions'].shape[0]
|
| 365 |
+
episode_end = prev_end + episode_length
|
| 366 |
+
prev_end = episode_end
|
| 367 |
+
episode_ends.append(episode_end)
|
| 368 |
+
n_steps = episode_ends[-1]
|
| 369 |
+
episode_starts = [0] + episode_ends[:-1]
|
| 370 |
+
_ = meta_group.array('episode_ends', episode_ends,
|
| 371 |
+
dtype=np.int64, compressor=None, overwrite=True)
|
| 372 |
+
|
| 373 |
+
# save lowdim data
|
| 374 |
+
for key in tqdm(lowdim_keys + ['action'], desc="Loading lowdim data"):
|
| 375 |
+
data_key = 'obs/' + key
|
| 376 |
+
if key == 'action':
|
| 377 |
+
data_key = 'actions'
|
| 378 |
+
this_data = list()
|
| 379 |
+
for i in range(len(demos)):
|
| 380 |
+
demo = demos[f'demo_{i}']
|
| 381 |
+
this_data.append(demo[data_key][:].astype(np.float32))
|
| 382 |
+
this_data = np.concatenate(this_data, axis=0)
|
| 383 |
+
if key == 'action':
|
| 384 |
+
this_data = _convert_actions(
|
| 385 |
+
raw_actions=this_data,
|
| 386 |
+
abs_action=abs_action,
|
| 387 |
+
rotation_transformer=rotation_transformer
|
| 388 |
+
)
|
| 389 |
+
assert this_data.shape == (n_steps,) + tuple(shape_meta['action']['shape'])
|
| 390 |
+
else:
|
| 391 |
+
assert this_data.shape == (n_steps,) + tuple(shape_meta['obs'][key]['shape'])
|
| 392 |
+
_ = data_group.array(
|
| 393 |
+
name=key,
|
| 394 |
+
data=this_data,
|
| 395 |
+
shape=this_data.shape,
|
| 396 |
+
chunks=this_data.shape,
|
| 397 |
+
compressor=None,
|
| 398 |
+
dtype=this_data.dtype
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
def img_copy(zarr_arr, zarr_idx, hdf5_arr, hdf5_idx):
|
| 402 |
+
try:
|
| 403 |
+
zarr_arr[zarr_idx] = hdf5_arr[hdf5_idx]
|
| 404 |
+
# make sure we can successfully decode
|
| 405 |
+
_ = zarr_arr[zarr_idx]
|
| 406 |
+
return True
|
| 407 |
+
except Exception as e:
|
| 408 |
+
return False
|
| 409 |
+
|
| 410 |
+
with tqdm(total=n_steps * len(rgb_keys), desc="Loading image data", mininterval=1.0) as pbar:
|
| 411 |
+
# one chunk per thread, therefore no synchronization needed
|
| 412 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=n_workers) as executor:
|
| 413 |
+
futures = set()
|
| 414 |
+
for key in rgb_keys:
|
| 415 |
+
data_key = 'obs/' + key
|
| 416 |
+
shape = tuple(shape_meta['obs'][key]['shape'])
|
| 417 |
+
c, h, w = shape
|
| 418 |
+
this_compressor = Jpeg2k(level=50)
|
| 419 |
+
img_arr = data_group.require_dataset(
|
| 420 |
+
name=key,
|
| 421 |
+
shape=(n_steps, h, w, c),
|
| 422 |
+
chunks=(1, h, w, c),
|
| 423 |
+
compressor=this_compressor,
|
| 424 |
+
dtype=np.uint8
|
| 425 |
+
)
|
| 426 |
+
for episode_idx in range(len(demos)):
|
| 427 |
+
demo = demos[f'demo_{episode_idx}']
|
| 428 |
+
hdf5_arr = demo['obs'][key]
|
| 429 |
+
for hdf5_idx in range(hdf5_arr.shape[0]):
|
| 430 |
+
if len(futures) >= max_inflight_tasks:
|
| 431 |
+
# limit number of inflight tasks
|
| 432 |
+
completed, futures = concurrent.futures.wait(futures,
|
| 433 |
+
return_when=concurrent.futures.FIRST_COMPLETED)
|
| 434 |
+
for f in completed:
|
| 435 |
+
if not f.result():
|
| 436 |
+
raise RuntimeError('Failed to encode image!')
|
| 437 |
+
pbar.update(len(completed))
|
| 438 |
+
|
| 439 |
+
zarr_idx = episode_starts[episode_idx] + hdf5_idx
|
| 440 |
+
futures.add(
|
| 441 |
+
executor.submit(img_copy,
|
| 442 |
+
img_arr, zarr_idx, hdf5_arr, hdf5_idx))
|
| 443 |
+
completed, futures = concurrent.futures.wait(futures)
|
| 444 |
+
for f in completed:
|
| 445 |
+
if not f.result():
|
| 446 |
+
raise RuntimeError('Failed to encode image!')
|
| 447 |
+
pbar.update(len(completed))
|
| 448 |
+
|
| 449 |
+
replay_buffer = ReplayBuffer(root)
|
| 450 |
+
return replay_buffer
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/rotation_conversions.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
# pyre-unsafe
|
| 8 |
+
|
| 9 |
+
from typing import Optional, Union
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
|
| 14 |
+
Device = Union[str, torch.device]
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
The transformation matrices returned from the functions in this file assume
|
| 18 |
+
the points on which the transformation will be applied are column vectors.
|
| 19 |
+
i.e. the R matrix is structured as
|
| 20 |
+
|
| 21 |
+
R = [
|
| 22 |
+
[Rxx, Rxy, Rxz],
|
| 23 |
+
[Ryx, Ryy, Ryz],
|
| 24 |
+
[Rzx, Rzy, Rzz],
|
| 25 |
+
] # (3, 3)
|
| 26 |
+
|
| 27 |
+
This matrix can be applied to column vectors by post multiplication
|
| 28 |
+
by the points e.g.
|
| 29 |
+
|
| 30 |
+
points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
|
| 31 |
+
transformed_points = R * points
|
| 32 |
+
|
| 33 |
+
To apply the same matrix to points which are row vectors, the R matrix
|
| 34 |
+
can be transposed and pre multiplied by the points:
|
| 35 |
+
|
| 36 |
+
e.g.
|
| 37 |
+
points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
|
| 38 |
+
transformed_points = points * R.transpose(1, 0)
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor:
|
| 43 |
+
"""
|
| 44 |
+
Convert rotations given as quaternions to rotation matrices.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
quaternions: quaternions with real part first,
|
| 48 |
+
as tensor of shape (..., 4).
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
| 52 |
+
"""
|
| 53 |
+
r, i, j, k = torch.unbind(quaternions, -1)
|
| 54 |
+
# pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
|
| 55 |
+
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
| 56 |
+
|
| 57 |
+
o = torch.stack(
|
| 58 |
+
(
|
| 59 |
+
1 - two_s * (j * j + k * k),
|
| 60 |
+
two_s * (i * j - k * r),
|
| 61 |
+
two_s * (i * k + j * r),
|
| 62 |
+
two_s * (i * j + k * r),
|
| 63 |
+
1 - two_s * (i * i + k * k),
|
| 64 |
+
two_s * (j * k - i * r),
|
| 65 |
+
two_s * (i * k - j * r),
|
| 66 |
+
two_s * (j * k + i * r),
|
| 67 |
+
1 - two_s * (i * i + j * j),
|
| 68 |
+
),
|
| 69 |
+
-1,
|
| 70 |
+
)
|
| 71 |
+
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
| 75 |
+
"""
|
| 76 |
+
Return a tensor where each element has the absolute value taken from the,
|
| 77 |
+
corresponding element of a, with sign taken from the corresponding
|
| 78 |
+
element of b. This is like the standard copysign floating-point operation,
|
| 79 |
+
but is not careful about negative 0 and NaN.
|
| 80 |
+
|
| 81 |
+
Args:
|
| 82 |
+
a: source tensor.
|
| 83 |
+
b: tensor whose signs will be used, of the same shape as a.
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
Tensor of the same shape as a with the signs of b.
|
| 87 |
+
"""
|
| 88 |
+
signs_differ = (a < 0) != (b < 0)
|
| 89 |
+
return torch.where(signs_differ, -a, a)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
|
| 93 |
+
"""
|
| 94 |
+
Returns torch.sqrt(torch.max(0, x))
|
| 95 |
+
but with a zero subgradient where x is 0.
|
| 96 |
+
"""
|
| 97 |
+
ret = torch.zeros_like(x)
|
| 98 |
+
positive_mask = x > 0
|
| 99 |
+
ret[positive_mask] = torch.sqrt(x[positive_mask])
|
| 100 |
+
return ret
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor:
|
| 104 |
+
"""
|
| 105 |
+
Convert rotations given as rotation matrices to quaternions.
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
quaternions with real part first, as tensor of shape (..., 4).
|
| 112 |
+
"""
|
| 113 |
+
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
| 114 |
+
raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
|
| 115 |
+
|
| 116 |
+
batch_dim = matrix.shape[:-2]
|
| 117 |
+
m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(
|
| 118 |
+
matrix.reshape(batch_dim + (9,)), dim=-1
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
q_abs = _sqrt_positive_part(
|
| 122 |
+
torch.stack(
|
| 123 |
+
[
|
| 124 |
+
1.0 + m00 + m11 + m22,
|
| 125 |
+
1.0 + m00 - m11 - m22,
|
| 126 |
+
1.0 - m00 + m11 - m22,
|
| 127 |
+
1.0 - m00 - m11 + m22,
|
| 128 |
+
],
|
| 129 |
+
dim=-1,
|
| 130 |
+
)
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# we produce the desired quaternion multiplied by each of r, i, j, k
|
| 134 |
+
quat_by_rijk = torch.stack(
|
| 135 |
+
[
|
| 136 |
+
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
|
| 137 |
+
# `int`.
|
| 138 |
+
torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
|
| 139 |
+
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
|
| 140 |
+
# `int`.
|
| 141 |
+
torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
|
| 142 |
+
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
|
| 143 |
+
# `int`.
|
| 144 |
+
torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
|
| 145 |
+
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
|
| 146 |
+
# `int`.
|
| 147 |
+
torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
|
| 148 |
+
],
|
| 149 |
+
dim=-2,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# We floor here at 0.1 but the exact level is not important; if q_abs is small,
|
| 153 |
+
# the candidate won't be picked.
|
| 154 |
+
flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device)
|
| 155 |
+
quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr))
|
| 156 |
+
|
| 157 |
+
# if not for numerical problems, quat_candidates[i] should be same (up to a sign),
|
| 158 |
+
# forall i; we pick the best-conditioned one (with the largest denominator)
|
| 159 |
+
out = quat_candidates[
|
| 160 |
+
F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :
|
| 161 |
+
].reshape(batch_dim + (4,))
|
| 162 |
+
return standardize_quaternion(out)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _axis_angle_rotation(axis: str, angle: torch.Tensor) -> torch.Tensor:
|
| 166 |
+
"""
|
| 167 |
+
Return the rotation matrices for one of the rotations about an axis
|
| 168 |
+
of which Euler angles describe, for each value of the angle given.
|
| 169 |
+
|
| 170 |
+
Args:
|
| 171 |
+
axis: Axis label "X" or "Y or "Z".
|
| 172 |
+
angle: any shape tensor of Euler angles in radians
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
cos = torch.cos(angle)
|
| 179 |
+
sin = torch.sin(angle)
|
| 180 |
+
one = torch.ones_like(angle)
|
| 181 |
+
zero = torch.zeros_like(angle)
|
| 182 |
+
|
| 183 |
+
if axis == "X":
|
| 184 |
+
R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
|
| 185 |
+
elif axis == "Y":
|
| 186 |
+
R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
|
| 187 |
+
elif axis == "Z":
|
| 188 |
+
R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
|
| 189 |
+
else:
|
| 190 |
+
raise ValueError("letter must be either X, Y or Z.")
|
| 191 |
+
|
| 192 |
+
return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def euler_angles_to_matrix(euler_angles: torch.Tensor, convention: str) -> torch.Tensor:
|
| 196 |
+
"""
|
| 197 |
+
Convert rotations given as Euler angles in radians to rotation matrices.
|
| 198 |
+
|
| 199 |
+
Args:
|
| 200 |
+
euler_angles: Euler angles in radians as tensor of shape (..., 3).
|
| 201 |
+
convention: Convention string of three uppercase letters from
|
| 202 |
+
{"X", "Y", and "Z"}.
|
| 203 |
+
|
| 204 |
+
Returns:
|
| 205 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
| 206 |
+
"""
|
| 207 |
+
if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
|
| 208 |
+
raise ValueError("Invalid input euler angles.")
|
| 209 |
+
if len(convention) != 3:
|
| 210 |
+
raise ValueError("Convention must have 3 letters.")
|
| 211 |
+
if convention[1] in (convention[0], convention[2]):
|
| 212 |
+
raise ValueError(f"Invalid convention {convention}.")
|
| 213 |
+
for letter in convention:
|
| 214 |
+
if letter not in ("X", "Y", "Z"):
|
| 215 |
+
raise ValueError(f"Invalid letter {letter} in convention string.")
|
| 216 |
+
matrices = [
|
| 217 |
+
_axis_angle_rotation(c, e)
|
| 218 |
+
for c, e in zip(convention, torch.unbind(euler_angles, -1))
|
| 219 |
+
]
|
| 220 |
+
# return functools.reduce(torch.matmul, matrices)
|
| 221 |
+
return torch.matmul(torch.matmul(matrices[0], matrices[1]), matrices[2])
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def _angle_from_tan(
|
| 225 |
+
axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
|
| 226 |
+
) -> torch.Tensor:
|
| 227 |
+
"""
|
| 228 |
+
Extract the first or third Euler angle from the two members of
|
| 229 |
+
the matrix which are positive constant times its sine and cosine.
|
| 230 |
+
|
| 231 |
+
Args:
|
| 232 |
+
axis: Axis label "X" or "Y or "Z" for the angle we are finding.
|
| 233 |
+
other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
|
| 234 |
+
convention.
|
| 235 |
+
data: Rotation matrices as tensor of shape (..., 3, 3).
|
| 236 |
+
horizontal: Whether we are looking for the angle for the third axis,
|
| 237 |
+
which means the relevant entries are in the same row of the
|
| 238 |
+
rotation matrix. If not, they are in the same column.
|
| 239 |
+
tait_bryan: Whether the first and third axes in the convention differ.
|
| 240 |
+
|
| 241 |
+
Returns:
|
| 242 |
+
Euler Angles in radians for each matrix in data as a tensor
|
| 243 |
+
of shape (...).
|
| 244 |
+
"""
|
| 245 |
+
|
| 246 |
+
i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
|
| 247 |
+
if horizontal:
|
| 248 |
+
i2, i1 = i1, i2
|
| 249 |
+
even = (axis + other_axis) in ["XY", "YZ", "ZX"]
|
| 250 |
+
if horizontal == even:
|
| 251 |
+
return torch.atan2(data[..., i1], data[..., i2])
|
| 252 |
+
if tait_bryan:
|
| 253 |
+
return torch.atan2(-data[..., i2], data[..., i1])
|
| 254 |
+
return torch.atan2(data[..., i2], -data[..., i1])
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def _index_from_letter(letter: str) -> int:
|
| 258 |
+
if letter == "X":
|
| 259 |
+
return 0
|
| 260 |
+
if letter == "Y":
|
| 261 |
+
return 1
|
| 262 |
+
if letter == "Z":
|
| 263 |
+
return 2
|
| 264 |
+
raise ValueError("letter must be either X, Y or Z.")
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def matrix_to_euler_angles(matrix: torch.Tensor, convention: str) -> torch.Tensor:
|
| 268 |
+
"""
|
| 269 |
+
Convert rotations given as rotation matrices to Euler angles in radians.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
| 273 |
+
convention: Convention string of three uppercase letters.
|
| 274 |
+
|
| 275 |
+
Returns:
|
| 276 |
+
Euler angles in radians as tensor of shape (..., 3).
|
| 277 |
+
"""
|
| 278 |
+
if len(convention) != 3:
|
| 279 |
+
raise ValueError("Convention must have 3 letters.")
|
| 280 |
+
if convention[1] in (convention[0], convention[2]):
|
| 281 |
+
raise ValueError(f"Invalid convention {convention}.")
|
| 282 |
+
for letter in convention:
|
| 283 |
+
if letter not in ("X", "Y", "Z"):
|
| 284 |
+
raise ValueError(f"Invalid letter {letter} in convention string.")
|
| 285 |
+
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
| 286 |
+
raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
|
| 287 |
+
i0 = _index_from_letter(convention[0])
|
| 288 |
+
i2 = _index_from_letter(convention[2])
|
| 289 |
+
tait_bryan = i0 != i2
|
| 290 |
+
if tait_bryan:
|
| 291 |
+
central_angle = torch.asin(
|
| 292 |
+
matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
|
| 293 |
+
)
|
| 294 |
+
else:
|
| 295 |
+
central_angle = torch.acos(matrix[..., i0, i0])
|
| 296 |
+
|
| 297 |
+
o = (
|
| 298 |
+
_angle_from_tan(
|
| 299 |
+
convention[0], convention[1], matrix[..., i2], False, tait_bryan
|
| 300 |
+
),
|
| 301 |
+
central_angle,
|
| 302 |
+
_angle_from_tan(
|
| 303 |
+
convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
|
| 304 |
+
),
|
| 305 |
+
)
|
| 306 |
+
return torch.stack(o, -1)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def random_quaternions(
|
| 310 |
+
n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
|
| 311 |
+
) -> torch.Tensor:
|
| 312 |
+
"""
|
| 313 |
+
Generate random quaternions representing rotations,
|
| 314 |
+
i.e. versors with nonnegative real part.
|
| 315 |
+
|
| 316 |
+
Args:
|
| 317 |
+
n: Number of quaternions in a batch to return.
|
| 318 |
+
dtype: Type to return.
|
| 319 |
+
device: Desired device of returned tensor. Default:
|
| 320 |
+
uses the current device for the default tensor type.
|
| 321 |
+
|
| 322 |
+
Returns:
|
| 323 |
+
Quaternions as tensor of shape (N, 4).
|
| 324 |
+
"""
|
| 325 |
+
if isinstance(device, str):
|
| 326 |
+
device = torch.device(device)
|
| 327 |
+
o = torch.randn((n, 4), dtype=dtype, device=device)
|
| 328 |
+
s = (o * o).sum(1)
|
| 329 |
+
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
| 330 |
+
return o
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def random_rotations(
|
| 334 |
+
n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
|
| 335 |
+
) -> torch.Tensor:
|
| 336 |
+
"""
|
| 337 |
+
Generate random rotations as 3x3 rotation matrices.
|
| 338 |
+
|
| 339 |
+
Args:
|
| 340 |
+
n: Number of rotation matrices in a batch to return.
|
| 341 |
+
dtype: Type to return.
|
| 342 |
+
device: Device of returned tensor. Default: if None,
|
| 343 |
+
uses the current device for the default tensor type.
|
| 344 |
+
|
| 345 |
+
Returns:
|
| 346 |
+
Rotation matrices as tensor of shape (n, 3, 3).
|
| 347 |
+
"""
|
| 348 |
+
quaternions = random_quaternions(n, dtype=dtype, device=device)
|
| 349 |
+
return quaternion_to_matrix(quaternions)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def random_rotation(
|
| 353 |
+
dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
|
| 354 |
+
) -> torch.Tensor:
|
| 355 |
+
"""
|
| 356 |
+
Generate a single random 3x3 rotation matrix.
|
| 357 |
+
|
| 358 |
+
Args:
|
| 359 |
+
dtype: Type to return
|
| 360 |
+
device: Device of returned tensor. Default: if None,
|
| 361 |
+
uses the current device for the default tensor type
|
| 362 |
+
|
| 363 |
+
Returns:
|
| 364 |
+
Rotation matrix as tensor of shape (3, 3).
|
| 365 |
+
"""
|
| 366 |
+
return random_rotations(1, dtype, device)[0]
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def standardize_quaternion(quaternions: torch.Tensor) -> torch.Tensor:
|
| 370 |
+
"""
|
| 371 |
+
Convert a unit quaternion to a standard form: one in which the real
|
| 372 |
+
part is non negative.
|
| 373 |
+
|
| 374 |
+
Args:
|
| 375 |
+
quaternions: Quaternions with real part first,
|
| 376 |
+
as tensor of shape (..., 4).
|
| 377 |
+
|
| 378 |
+
Returns:
|
| 379 |
+
Standardized quaternions as tensor of shape (..., 4).
|
| 380 |
+
"""
|
| 381 |
+
return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def quaternion_raw_multiply(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
| 385 |
+
"""
|
| 386 |
+
Multiply two quaternions.
|
| 387 |
+
Usual torch rules for broadcasting apply.
|
| 388 |
+
|
| 389 |
+
Args:
|
| 390 |
+
a: Quaternions as tensor of shape (..., 4), real part first.
|
| 391 |
+
b: Quaternions as tensor of shape (..., 4), real part first.
|
| 392 |
+
|
| 393 |
+
Returns:
|
| 394 |
+
The product of a and b, a tensor of quaternions shape (..., 4).
|
| 395 |
+
"""
|
| 396 |
+
aw, ax, ay, az = torch.unbind(a, -1)
|
| 397 |
+
bw, bx, by, bz = torch.unbind(b, -1)
|
| 398 |
+
ow = aw * bw - ax * bx - ay * by - az * bz
|
| 399 |
+
ox = aw * bx + ax * bw + ay * bz - az * by
|
| 400 |
+
oy = aw * by - ax * bz + ay * bw + az * bx
|
| 401 |
+
oz = aw * bz + ax * by - ay * bx + az * bw
|
| 402 |
+
return torch.stack((ow, ox, oy, oz), -1)
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def quaternion_multiply(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
| 406 |
+
"""
|
| 407 |
+
Multiply two quaternions representing rotations, returning the quaternion
|
| 408 |
+
representing their composition, i.e. the versor with nonnegative real part.
|
| 409 |
+
Usual torch rules for broadcasting apply.
|
| 410 |
+
|
| 411 |
+
Args:
|
| 412 |
+
a: Quaternions as tensor of shape (..., 4), real part first.
|
| 413 |
+
b: Quaternions as tensor of shape (..., 4), real part first.
|
| 414 |
+
|
| 415 |
+
Returns:
|
| 416 |
+
The product of a and b, a tensor of quaternions of shape (..., 4).
|
| 417 |
+
"""
|
| 418 |
+
ab = quaternion_raw_multiply(a, b)
|
| 419 |
+
return standardize_quaternion(ab)
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def quaternion_invert(quaternion: torch.Tensor) -> torch.Tensor:
|
| 423 |
+
"""
|
| 424 |
+
Given a quaternion representing rotation, get the quaternion representing
|
| 425 |
+
its inverse.
|
| 426 |
+
|
| 427 |
+
Args:
|
| 428 |
+
quaternion: Quaternions as tensor of shape (..., 4), with real part
|
| 429 |
+
first, which must be versors (unit quaternions).
|
| 430 |
+
|
| 431 |
+
Returns:
|
| 432 |
+
The inverse, a tensor of quaternions of shape (..., 4).
|
| 433 |
+
"""
|
| 434 |
+
|
| 435 |
+
scaling = torch.tensor([1, -1, -1, -1], device=quaternion.device)
|
| 436 |
+
return quaternion * scaling
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
def quaternion_apply(quaternion: torch.Tensor, point: torch.Tensor) -> torch.Tensor:
|
| 440 |
+
"""
|
| 441 |
+
Apply the rotation given by a quaternion to a 3D point.
|
| 442 |
+
Usual torch rules for broadcasting apply.
|
| 443 |
+
|
| 444 |
+
Args:
|
| 445 |
+
quaternion: Tensor of quaternions, real part first, of shape (..., 4).
|
| 446 |
+
point: Tensor of 3D points of shape (..., 3).
|
| 447 |
+
|
| 448 |
+
Returns:
|
| 449 |
+
Tensor of rotated points of shape (..., 3).
|
| 450 |
+
"""
|
| 451 |
+
if point.size(-1) != 3:
|
| 452 |
+
raise ValueError(f"Points are not in 3D, {point.shape}.")
|
| 453 |
+
real_parts = point.new_zeros(point.shape[:-1] + (1,))
|
| 454 |
+
point_as_quaternion = torch.cat((real_parts, point), -1)
|
| 455 |
+
out = quaternion_raw_multiply(
|
| 456 |
+
quaternion_raw_multiply(quaternion, point_as_quaternion),
|
| 457 |
+
quaternion_invert(quaternion),
|
| 458 |
+
)
|
| 459 |
+
return out[..., 1:]
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def axis_angle_to_matrix(axis_angle: torch.Tensor) -> torch.Tensor:
|
| 463 |
+
"""
|
| 464 |
+
Convert rotations given as axis/angle to rotation matrices.
|
| 465 |
+
|
| 466 |
+
Args:
|
| 467 |
+
axis_angle: Rotations given as a vector in axis angle form,
|
| 468 |
+
as a tensor of shape (..., 3), where the magnitude is
|
| 469 |
+
the angle turned anticlockwise in radians around the
|
| 470 |
+
vector's direction.
|
| 471 |
+
|
| 472 |
+
Returns:
|
| 473 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
| 474 |
+
"""
|
| 475 |
+
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def matrix_to_axis_angle(matrix: torch.Tensor) -> torch.Tensor:
|
| 479 |
+
"""
|
| 480 |
+
Convert rotations given as rotation matrices to axis/angle.
|
| 481 |
+
|
| 482 |
+
Args:
|
| 483 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
| 484 |
+
|
| 485 |
+
Returns:
|
| 486 |
+
Rotations given as a vector in axis angle form, as a tensor
|
| 487 |
+
of shape (..., 3), where the magnitude is the angle
|
| 488 |
+
turned anticlockwise in radians around the vector's
|
| 489 |
+
direction.
|
| 490 |
+
"""
|
| 491 |
+
return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
def axis_angle_to_quaternion(axis_angle: torch.Tensor) -> torch.Tensor:
|
| 495 |
+
"""
|
| 496 |
+
Convert rotations given as axis/angle to quaternions.
|
| 497 |
+
|
| 498 |
+
Args:
|
| 499 |
+
axis_angle: Rotations given as a vector in axis angle form,
|
| 500 |
+
as a tensor of shape (..., 3), where the magnitude is
|
| 501 |
+
the angle turned anticlockwise in radians around the
|
| 502 |
+
vector's direction.
|
| 503 |
+
|
| 504 |
+
Returns:
|
| 505 |
+
quaternions with real part first, as tensor of shape (..., 4).
|
| 506 |
+
"""
|
| 507 |
+
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
| 508 |
+
half_angles = angles * 0.5
|
| 509 |
+
eps = 1e-6
|
| 510 |
+
small_angles = angles.abs() < eps
|
| 511 |
+
sin_half_angles_over_angles = torch.empty_like(angles)
|
| 512 |
+
sin_half_angles_over_angles[~small_angles] = (
|
| 513 |
+
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
| 514 |
+
)
|
| 515 |
+
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
| 516 |
+
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
| 517 |
+
sin_half_angles_over_angles[small_angles] = (
|
| 518 |
+
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
| 519 |
+
)
|
| 520 |
+
quaternions = torch.cat(
|
| 521 |
+
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
|
| 522 |
+
)
|
| 523 |
+
return quaternions
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
def quaternion_to_axis_angle(quaternions: torch.Tensor) -> torch.Tensor:
|
| 527 |
+
"""
|
| 528 |
+
Convert rotations given as quaternions to axis/angle.
|
| 529 |
+
|
| 530 |
+
Args:
|
| 531 |
+
quaternions: quaternions with real part first,
|
| 532 |
+
as tensor of shape (..., 4).
|
| 533 |
+
|
| 534 |
+
Returns:
|
| 535 |
+
Rotations given as a vector in axis angle form, as a tensor
|
| 536 |
+
of shape (..., 3), where the magnitude is the angle
|
| 537 |
+
turned anticlockwise in radians around the vector's
|
| 538 |
+
direction.
|
| 539 |
+
"""
|
| 540 |
+
norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
|
| 541 |
+
half_angles = torch.atan2(norms, quaternions[..., :1])
|
| 542 |
+
angles = 2 * half_angles
|
| 543 |
+
eps = 1e-6
|
| 544 |
+
small_angles = angles.abs() < eps
|
| 545 |
+
sin_half_angles_over_angles = torch.empty_like(angles)
|
| 546 |
+
sin_half_angles_over_angles[~small_angles] = (
|
| 547 |
+
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
| 548 |
+
)
|
| 549 |
+
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
| 550 |
+
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
| 551 |
+
sin_half_angles_over_angles[small_angles] = (
|
| 552 |
+
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
| 553 |
+
)
|
| 554 |
+
return quaternions[..., 1:] / sin_half_angles_over_angles
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
|
| 558 |
+
"""
|
| 559 |
+
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
|
| 560 |
+
using Gram--Schmidt orthogonalization per Section B of [1].
|
| 561 |
+
Args:
|
| 562 |
+
d6: 6D rotation representation, of size (*, 6)
|
| 563 |
+
|
| 564 |
+
Returns:
|
| 565 |
+
batch of rotation matrices of size (*, 3, 3)
|
| 566 |
+
|
| 567 |
+
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
| 568 |
+
On the Continuity of Rotation Representations in Neural Networks.
|
| 569 |
+
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
| 570 |
+
Retrieved from http://arxiv.org/abs/1812.07035
|
| 571 |
+
"""
|
| 572 |
+
|
| 573 |
+
a1, a2 = d6[..., :3], d6[..., 3:]
|
| 574 |
+
b1 = F.normalize(a1, dim=-1)
|
| 575 |
+
b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
|
| 576 |
+
b2 = F.normalize(b2, dim=-1)
|
| 577 |
+
b3 = torch.cross(b1, b2, dim=-1)
|
| 578 |
+
return torch.stack((b1, b2, b3), dim=-2)
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
|
| 582 |
+
"""
|
| 583 |
+
Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
|
| 584 |
+
by dropping the last row. Note that 6D representation is not unique.
|
| 585 |
+
Args:
|
| 586 |
+
matrix: batch of rotation matrices of size (*, 3, 3)
|
| 587 |
+
|
| 588 |
+
Returns:
|
| 589 |
+
6D rotation representation, of size (*, 6)
|
| 590 |
+
|
| 591 |
+
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
| 592 |
+
On the Continuity of Rotation Representations in Neural Networks.
|
| 593 |
+
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
| 594 |
+
Retrieved from http://arxiv.org/abs/1812.07035
|
| 595 |
+
"""
|
| 596 |
+
batch_dim = matrix.size()[:-2]
|
| 597 |
+
return matrix[..., :2, :].clone().reshape(batch_dim + (6,))
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .basic import DiffusionModel
|
| 2 |
+
from .diffusionsde import DiscreteDiffusionSDE, ContinuousDiffusionSDE
|
| 3 |
+
from .newedm import ContinuousEDM
|
| 4 |
+
from .rectifiedflow import DiscreteRectifiedFlow, ContinuousRectifiedFlow
|
| 5 |
+
from .consistency_model import ContinuousConsistencyModel
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/basic.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from copy import deepcopy
|
| 2 |
+
from typing import Optional, Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from cleandiffuser.utils import to_tensor
|
| 9 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 10 |
+
from cleandiffuser.nn_condition import BaseNNCondition, IdentityCondition
|
| 11 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class DiffusionModel:
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
|
| 19 |
+
# ----------------- Neural Networks ----------------- #
|
| 20 |
+
# NN backbone for the diffusion model
|
| 21 |
+
nn_diffusion: BaseNNDiffusion,
|
| 22 |
+
# Add a condition-process NN to enable classifier-free-guidance
|
| 23 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 24 |
+
|
| 25 |
+
# ----------------- Masks ----------------- #
|
| 26 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 27 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 28 |
+
# Add loss weight
|
| 29 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 30 |
+
|
| 31 |
+
# ------------------ Plugs ---------------- #
|
| 32 |
+
# Add a classifier to enable classifier-guidance
|
| 33 |
+
classifier: Optional[BaseClassifier] = None,
|
| 34 |
+
|
| 35 |
+
# ------------------ Params ---------------- #
|
| 36 |
+
grad_clip_norm: Optional[float] = None,
|
| 37 |
+
diffusion_steps: int = 1000,
|
| 38 |
+
ema_rate: float = 0.995,
|
| 39 |
+
optim_params: Optional[dict] = None,
|
| 40 |
+
|
| 41 |
+
device: Union[torch.device, str] = "cpu"
|
| 42 |
+
):
|
| 43 |
+
if optim_params is None:
|
| 44 |
+
optim_params = {"lr": 2e-4, "weight_decay": 1e-5}
|
| 45 |
+
|
| 46 |
+
self.device = device
|
| 47 |
+
self.grad_clip_norm = grad_clip_norm
|
| 48 |
+
self.diffusion_steps = diffusion_steps
|
| 49 |
+
self.ema_rate = ema_rate
|
| 50 |
+
|
| 51 |
+
# nn_condition is None means that the model is not conditioned on any input.
|
| 52 |
+
if nn_condition is None:
|
| 53 |
+
nn_condition = IdentityCondition()
|
| 54 |
+
|
| 55 |
+
# In the code implementation of Diffusion models, it is common to maintain an exponential
|
| 56 |
+
# moving average (EMA) version of the model for inference, as it has been observed that
|
| 57 |
+
# this approach can result in more stable generation outcomes.
|
| 58 |
+
self.model = nn.ModuleDict({
|
| 59 |
+
"diffusion": nn_diffusion.to(self.device),
|
| 60 |
+
"condition": nn_condition.to(self.device)})
|
| 61 |
+
self.model_ema = deepcopy(self.model).requires_grad_(False)
|
| 62 |
+
|
| 63 |
+
self.model.train()
|
| 64 |
+
self.model_ema.eval()
|
| 65 |
+
|
| 66 |
+
self.optimizer = torch.optim.AdamW(self.model.parameters(), **optim_params)
|
| 67 |
+
|
| 68 |
+
self.classifier = classifier
|
| 69 |
+
|
| 70 |
+
self.fix_mask = to_tensor(fix_mask, self.device)[None, ] if fix_mask is not None else 0.
|
| 71 |
+
self.loss_weight = to_tensor(loss_weight, self.device)[None, ] if loss_weight is not None else 1.
|
| 72 |
+
|
| 73 |
+
def train(self):
|
| 74 |
+
self.model.train()
|
| 75 |
+
if self.classifier is not None:
|
| 76 |
+
self.classifier.model.train()
|
| 77 |
+
|
| 78 |
+
def eval(self):
|
| 79 |
+
self.model.eval()
|
| 80 |
+
if self.classifier is not None:
|
| 81 |
+
self.classifier.model.eval()
|
| 82 |
+
|
| 83 |
+
def ema_update(self):
|
| 84 |
+
with torch.no_grad():
|
| 85 |
+
for p, p_ema in zip(self.model.parameters(), self.model_ema.parameters()):
|
| 86 |
+
p_ema.data.mul_(self.ema_rate).add_(p.data, alpha=1. - self.ema_rate)
|
| 87 |
+
|
| 88 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 89 |
+
raise NotImplementedError
|
| 90 |
+
|
| 91 |
+
def sample(self, *args, **kwargs):
|
| 92 |
+
raise NotImplementedError
|
| 93 |
+
|
| 94 |
+
def save(self, path: str):
|
| 95 |
+
torch.save({
|
| 96 |
+
"model": self.model.state_dict(),
|
| 97 |
+
"model_ema": self.model_ema.state_dict(),
|
| 98 |
+
}, path)
|
| 99 |
+
|
| 100 |
+
def load(self, path: str):
|
| 101 |
+
checkpoint = torch.load(path, map_location=self.device)
|
| 102 |
+
self.model.load_state_dict(checkpoint["model"])
|
| 103 |
+
self.model_ema.load_state_dict(checkpoint["model_ema"])
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/consistency_model.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Callable, List
|
| 2 |
+
from typing import Optional, Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from cleandiffuser.diffusion import DiffusionModel
|
| 9 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 10 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 11 |
+
from cleandiffuser.utils import at_least_ndim
|
| 12 |
+
from .newedm import ContinuousEDM
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def erf(x):
|
| 16 |
+
a1 = 0.254829592
|
| 17 |
+
a2 = -0.284496736
|
| 18 |
+
a3 = 1.421413741
|
| 19 |
+
a4 = -1.453152027
|
| 20 |
+
a5 = 1.061405429
|
| 21 |
+
p = 0.3275911
|
| 22 |
+
|
| 23 |
+
sign = np.sign(x)
|
| 24 |
+
x = np.abs(x)
|
| 25 |
+
t = 1.0 / (1.0 + p * x)
|
| 26 |
+
y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * np.exp(-x * x)
|
| 27 |
+
return sign * y
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def compare_properties(obj1, obj2, properties: List[str]):
|
| 31 |
+
differences = []
|
| 32 |
+
for prop in properties:
|
| 33 |
+
obj1_prop = getattr(obj1, prop)
|
| 34 |
+
obj2_prop = getattr(obj2, prop)
|
| 35 |
+
if isinstance(obj1_prop, torch.Tensor):
|
| 36 |
+
if not torch.allclose(obj1_prop, obj2_prop):
|
| 37 |
+
differences.append(prop)
|
| 38 |
+
elif isinstance(obj1_prop, np.ndarray):
|
| 39 |
+
if not np.allclose(obj1_prop, obj2_prop):
|
| 40 |
+
differences.append(prop)
|
| 41 |
+
else:
|
| 42 |
+
if obj1_prop != obj2_prop:
|
| 43 |
+
differences.append(prop)
|
| 44 |
+
return differences
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def pseudo_huber_loss(source: torch.Tensor, target: torch.Tensor, c: float = 0.0):
|
| 48 |
+
return ((source - target) ** 2 + c ** 2).sqrt() - c
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class CMCurriculumLogger:
|
| 52 |
+
def __init__(
|
| 53 |
+
self, s0: int = 10, s1: int = 1280, curriculum_cycle: int = 100_000,
|
| 54 |
+
sigma_min: float = 0.002, sigma_max: float = 80., rho: float = 7.,
|
| 55 |
+
P_mean: float = -1.1, P_std: float = 2.0
|
| 56 |
+
):
|
| 57 |
+
self.Kprime = np.ceil(curriculum_cycle / (np.log2(np.ceil(s1 / s0)) + 1))
|
| 58 |
+
self.Nk = s0
|
| 59 |
+
self.s0, self.s1 = s0, s1
|
| 60 |
+
self.curriculum_cycle = curriculum_cycle
|
| 61 |
+
self.sigma_min, self.sigma_max, self.rho = sigma_min, sigma_max, rho
|
| 62 |
+
self.P_mean, self.P_std = P_mean, P_std
|
| 63 |
+
|
| 64 |
+
self.ceil_k_div_Kprime, self.k = None, None
|
| 65 |
+
|
| 66 |
+
self.update_k(0)
|
| 67 |
+
|
| 68 |
+
def update_k(self, k):
|
| 69 |
+
self.k = k
|
| 70 |
+
if np.ceil(k / self.Kprime) != self.ceil_k_div_Kprime:
|
| 71 |
+
self.ceil_k_div_Kprime = np.ceil(k / self.Kprime)
|
| 72 |
+
self.Nk = int(min(self.s0 * (2 ** self.ceil_k_div_Kprime), self.s1))
|
| 73 |
+
|
| 74 |
+
self.sigmas = ((self.sigma_min ** (1 / self.rho) + np.arange(self.Nk + 1, dtype=np.float32)
|
| 75 |
+
/ self.Nk * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (
|
| 76 |
+
1 / self.rho))) ** self.rho)
|
| 77 |
+
|
| 78 |
+
self.p_sigmas = (erf((np.log(self.sigmas[1:]) - self.P_mean) / (self.P_std * (2 ** 0.5))) -
|
| 79 |
+
erf((np.log(self.sigmas[:-1]) - self.P_mean) / (self.P_std * (2 ** 0.5))))
|
| 80 |
+
self.p_sigmas = self.p_sigmas / self.p_sigmas.sum()
|
| 81 |
+
|
| 82 |
+
def incremental_update_k(self):
|
| 83 |
+
self.update_k(self.k + 1)
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def curriculum_process(self):
|
| 87 |
+
return (self.k % self.curriculum_cycle) / self.curriculum_cycle
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class ContinuousConsistencyModel(DiffusionModel):
|
| 91 |
+
"""**Continuous-time Consistency Model**
|
| 92 |
+
|
| 93 |
+
The Consistency Model defines a consistency function.
|
| 94 |
+
A consistency function has the property of self-consistency:
|
| 95 |
+
its outputs are consistent for arbitrary pairs of (x_t, t) that belong to the same PF ODE trajectory.
|
| 96 |
+
To learn such a consistency function, the Consistency Model needs to be distilled either from a pre-trained EDM
|
| 97 |
+
or learned directly through consistency training loss.
|
| 98 |
+
This self-consistency property allows the Consistency Model in theory to achieve one-step generation.
|
| 99 |
+
|
| 100 |
+
The current implementation of Consistency Model only supports continuous-time ODEs.
|
| 101 |
+
The sampling steps are required to be greater than 0.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
nn_diffusion: BaseNNDiffusion,
|
| 105 |
+
The neural network backbone for the Diffusion model.
|
| 106 |
+
nn_condition: Optional[BaseNNCondition],
|
| 107 |
+
The neural network backbone for the condition embedding.
|
| 108 |
+
|
| 109 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor],
|
| 110 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 111 |
+
The mask should be in the shape of `x_shape`.
|
| 112 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor],
|
| 113 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 114 |
+
|
| 115 |
+
classifier: Optional[BaseClassifier],
|
| 116 |
+
The Consistency Model does not support classifier guidance; please set this option to `None`.
|
| 117 |
+
|
| 118 |
+
grad_clip_norm: Optional[float],
|
| 119 |
+
Gradient clipping norm.
|
| 120 |
+
ema_rate: float,
|
| 121 |
+
Exponential moving average rate.
|
| 122 |
+
optim_params: Optional[dict],
|
| 123 |
+
Optimizer parameters.
|
| 124 |
+
|
| 125 |
+
s0: int,
|
| 126 |
+
The minimum number of noise levels. Default: 10.
|
| 127 |
+
s1: int,
|
| 128 |
+
The maximum number of noise levels. Default: 1280.
|
| 129 |
+
data_dim: int,
|
| 130 |
+
The dimension of the data, which affects the `pseudo_huber_constant`.
|
| 131 |
+
As suggested in `improved Consistency Models`, `pseudo_huber_constant` = 0.00054 * np.sqrt(data_dim).
|
| 132 |
+
If `data_dim` is `None`, then `pseudo_huber_constant` = 0.01 will be used.
|
| 133 |
+
P_mean: float,
|
| 134 |
+
Hyperparameter for noise sampling during training. Default: -1.1.
|
| 135 |
+
P_std: float,
|
| 136 |
+
Hyperparameter for noise sampling during training. Default: 2.0.
|
| 137 |
+
sigma_min: float,
|
| 138 |
+
The minimum standard deviation of the noise. Default: 0.002.
|
| 139 |
+
sigma_max: float,
|
| 140 |
+
The maximum standard deviation of the noise. Default: 80.
|
| 141 |
+
sigma_data: float,
|
| 142 |
+
The standard deviation of the data. Default: 0.5.
|
| 143 |
+
rho: float,
|
| 144 |
+
The power of the noise schedule. Default: 7.
|
| 145 |
+
curriculum_cycle: int,
|
| 146 |
+
The cycle of the curriculum process.
|
| 147 |
+
It is best to set `curriculum_cycle` to the number of model training iterations. Default: 100_000.
|
| 148 |
+
|
| 149 |
+
x_max: Optional[torch.Tensor],
|
| 150 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 151 |
+
x_min: Optional[torch.Tensor],
|
| 152 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 153 |
+
|
| 154 |
+
device: Union[torch.device, str],
|
| 155 |
+
The device to run the model.
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
def __init__(
|
| 159 |
+
self,
|
| 160 |
+
|
| 161 |
+
# ----------------- Neural Networks ----------------- #
|
| 162 |
+
nn_diffusion: BaseNNDiffusion,
|
| 163 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 164 |
+
|
| 165 |
+
# ----------------- Masks ----------------- #
|
| 166 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 167 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 168 |
+
# Add loss weight
|
| 169 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 170 |
+
|
| 171 |
+
# ------------------ Plugins ---------------- #
|
| 172 |
+
# Do not support CG
|
| 173 |
+
classifier=None,
|
| 174 |
+
|
| 175 |
+
# ------------------ Training Params ---------------- #
|
| 176 |
+
grad_clip_norm: Optional[float] = None,
|
| 177 |
+
ema_rate: float = 0.9999,
|
| 178 |
+
optim_params: Optional[dict] = None,
|
| 179 |
+
|
| 180 |
+
# ------------------- Consistency Model Params ------------------- #
|
| 181 |
+
s0: int = 10,
|
| 182 |
+
s1: int = 1280,
|
| 183 |
+
data_dim: int = None,
|
| 184 |
+
P_mean: float = -1.1,
|
| 185 |
+
P_std: float = 2.0,
|
| 186 |
+
sigma_min: float = 0.002,
|
| 187 |
+
sigma_max: float = 80.,
|
| 188 |
+
sigma_data: float = 0.5,
|
| 189 |
+
rho: float = 7.0,
|
| 190 |
+
curriculum_cycle: int = 100_000,
|
| 191 |
+
|
| 192 |
+
x_max: Optional[torch.Tensor] = None,
|
| 193 |
+
x_min: Optional[torch.Tensor] = None,
|
| 194 |
+
|
| 195 |
+
device: Union[torch.device, str] = "cpu"
|
| 196 |
+
):
|
| 197 |
+
super().__init__(
|
| 198 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 199 |
+
0, ema_rate, optim_params, device)
|
| 200 |
+
|
| 201 |
+
self.cur_logger = CMCurriculumLogger(
|
| 202 |
+
s0, s1, curriculum_cycle, sigma_min, sigma_max, rho, P_mean, P_std)
|
| 203 |
+
|
| 204 |
+
self.pseudo_huber_constant = 0.01 if data_dim is None else 0.00054 * np.sqrt(data_dim)
|
| 205 |
+
|
| 206 |
+
self.rho = rho
|
| 207 |
+
self.sigma_data, self.sigma_max, self.sigma_min = sigma_data, sigma_max, sigma_min
|
| 208 |
+
self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
|
| 209 |
+
self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
|
| 210 |
+
|
| 211 |
+
self.edm = None
|
| 212 |
+
self.distillation_sigmas, self.distillation_N = None, None
|
| 213 |
+
|
| 214 |
+
def prepare_distillation(self, edm: ContinuousEDM, distillation_N: int = 18):
|
| 215 |
+
checklist = [
|
| 216 |
+
"sigma_data", "sigma_max", "sigma_min", "rho", "x_max", "x_min",
|
| 217 |
+
"fix_mask", "loss_weight", "device"]
|
| 218 |
+
differences = compare_properties(self, edm, checklist)
|
| 219 |
+
if len(differences) != 0:
|
| 220 |
+
raise ValueError(f"Properties {differences} are different between the EDM and the Consistency Model.")
|
| 221 |
+
self.edm = edm
|
| 222 |
+
self.model.load_state_dict(edm.model.state_dict())
|
| 223 |
+
self.model_ema.load_state_dict(edm.model_ema.state_dict())
|
| 224 |
+
self.distillation_N = distillation_N
|
| 225 |
+
self.distillation_sigmas = self.training_noise_schedule(distillation_N)
|
| 226 |
+
|
| 227 |
+
@property
|
| 228 |
+
def supported_solvers(self):
|
| 229 |
+
return ["none"]
|
| 230 |
+
|
| 231 |
+
@property
|
| 232 |
+
def clip_pred(self):
|
| 233 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 234 |
+
|
| 235 |
+
def training_noise_schedule(self, N):
|
| 236 |
+
sigma = ((self.sigma_min ** (1 / self.rho) + np.arange(N + 1)
|
| 237 |
+
/ N * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (1 / self.rho))) ** self.rho)
|
| 238 |
+
return torch.tensor(sigma, device=self.device, dtype=torch.float32)
|
| 239 |
+
|
| 240 |
+
# ===================== CM Pre-conditioning =======================
|
| 241 |
+
def c_skip(self, sigma):
|
| 242 |
+
return self.sigma_data ** 2 / (self.sigma_data ** 2 + (sigma - self.sigma_min) ** 2)
|
| 243 |
+
|
| 244 |
+
def c_out(self, sigma):
|
| 245 |
+
return (sigma - self.sigma_min) * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 246 |
+
|
| 247 |
+
def c_in(self, sigma):
|
| 248 |
+
return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 249 |
+
|
| 250 |
+
def c_noise(self, sigma):
|
| 251 |
+
return 0.25 * sigma.log()
|
| 252 |
+
|
| 253 |
+
def f(self, x, t, condition=None, model=None):
|
| 254 |
+
c_skip, c_out, c_in, c_noise = self.c_skip(t), self.c_out(t), self.c_in(t), self.c_noise(t)
|
| 255 |
+
if model is None:
|
| 256 |
+
model = self.model
|
| 257 |
+
c_skip, c_in, c_out = (
|
| 258 |
+
at_least_ndim(c_skip, x.dim()), at_least_ndim(c_in, x.dim()), at_least_ndim(c_out, x.dim()))
|
| 259 |
+
pred_x = c_skip * x + c_out * model["diffusion"](c_in * x, c_noise, condition)
|
| 260 |
+
if self.clip_pred:
|
| 261 |
+
pred_x = pred_x.clip(self.x_min, self.x_max)
|
| 262 |
+
return pred_x
|
| 263 |
+
|
| 264 |
+
def distillation_loss(self, x0, condition=None):
|
| 265 |
+
|
| 266 |
+
assert self.edm is not None, "Please call `prepare_distillation` before distillation."
|
| 267 |
+
|
| 268 |
+
idx = torch.randint(self.distillation_N, (x0.shape[0],), device=self.device)
|
| 269 |
+
t_m, t_n = self.distillation_sigmas[idx + 1], self.distillation_sigmas[idx]
|
| 270 |
+
x_m, t_m, eps = self.edm.add_noise(x0, t_m, None)
|
| 271 |
+
|
| 272 |
+
with torch.no_grad():
|
| 273 |
+
condition_vec_cfg = self.edm.model_ema["condition"](condition) if condition is not None else None
|
| 274 |
+
pred, logp = self.edm.guided_sampling(
|
| 275 |
+
x_m, t_m, None, self.edm.model_ema, condition_vec_cfg, 1.0, None, 0.0, False)
|
| 276 |
+
dot_x = (x_m - pred) / at_least_ndim(t_m, x_m.dim())
|
| 277 |
+
delta_t = t_m - t_n
|
| 278 |
+
x_n = x_m - dot_x * at_least_ndim(delta_t, x_m.dim())
|
| 279 |
+
x_n = x_n * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 280 |
+
|
| 281 |
+
condition_vec = self.model["condition"](condition) if condition is not None else None
|
| 282 |
+
pred_x_m = self.f(x_m, t_m, condition_vec, self.model)
|
| 283 |
+
with torch.no_grad():
|
| 284 |
+
condition_vec_ema = self.model_ema["condition"](condition) if condition is not None else None
|
| 285 |
+
pred_x_n = self.f(x_n, t_n, condition_vec_ema, self.model_ema)
|
| 286 |
+
|
| 287 |
+
loss = (((pred_x_n - pred_x_m) ** 2) * (1 - self.fix_mask) *
|
| 288 |
+
self.loss_weight * at_least_ndim((1 / (t_m - t_n)), pred_x_n.dim()))
|
| 289 |
+
|
| 290 |
+
return loss.mean(), None
|
| 291 |
+
|
| 292 |
+
def training_loss(self, x0, condition=None):
|
| 293 |
+
|
| 294 |
+
idx = np.random.choice(self.cur_logger.Nk, size=x0.shape[0], p=self.cur_logger.p_sigmas)
|
| 295 |
+
|
| 296 |
+
# m = n + 1
|
| 297 |
+
sigma_n = torch.tensor(self.cur_logger.sigmas[idx], device=self.device)
|
| 298 |
+
sigma_m = torch.tensor(self.cur_logger.sigmas[idx + 1], device=self.device)
|
| 299 |
+
|
| 300 |
+
eps = torch.randn_like(x0)
|
| 301 |
+
|
| 302 |
+
x_n = x0 + at_least_ndim(sigma_n, x0.dim()) * eps
|
| 303 |
+
x_m = x0 + at_least_ndim(sigma_m, x0.dim()) * eps
|
| 304 |
+
|
| 305 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 306 |
+
|
| 307 |
+
pred_x_m = self.f(x_m, sigma_m, condition, self.model)
|
| 308 |
+
with torch.no_grad():
|
| 309 |
+
pred_x_n = self.f(x_n, sigma_n, condition.detach(), self.model)
|
| 310 |
+
|
| 311 |
+
loss = pseudo_huber_loss(pred_x_m, pred_x_n, self.pseudo_huber_constant)
|
| 312 |
+
|
| 313 |
+
unweighted_loss = (loss * (1 - self.fix_mask) * self.loss_weight)
|
| 314 |
+
|
| 315 |
+
cm_loss_weight = at_least_ndim(1 / (sigma_m - sigma_n), x0.dim())
|
| 316 |
+
|
| 317 |
+
return (unweighted_loss * cm_loss_weight).mean(), unweighted_loss.mean().item()
|
| 318 |
+
|
| 319 |
+
def update(self, x0, condition=None, update_ema=True, loss_type="training", **kwargs):
|
| 320 |
+
""" One-step gradient update.
|
| 321 |
+
|
| 322 |
+
Args:
|
| 323 |
+
x0: torch.Tensor,
|
| 324 |
+
Samples from the target distribution.
|
| 325 |
+
condition: Optional,
|
| 326 |
+
Condition of x0. `None` indicates no condition.
|
| 327 |
+
update_ema: bool,
|
| 328 |
+
Whether to update the exponential moving average model.
|
| 329 |
+
loss_type: str,
|
| 330 |
+
The type of loss. `training` or `distillation`.
|
| 331 |
+
|
| 332 |
+
Returns:
|
| 333 |
+
log: dict,
|
| 334 |
+
The log dictionary.
|
| 335 |
+
|
| 336 |
+
Examples:
|
| 337 |
+
>>> model = ContinuousConsistencyModel(...)
|
| 338 |
+
>>> x0 = torch.randn(*x_shape)
|
| 339 |
+
>>> condition = torch.randn(*condition_shape)
|
| 340 |
+
>>> log = model.update(x0, condition, loss_type="training") # training
|
| 341 |
+
>>> log = model.update(x0, condition, loss_type="distillation") # distillation
|
| 342 |
+
"""
|
| 343 |
+
if loss_type == "training":
|
| 344 |
+
loss, unweighted_loss = self.training_loss(x0, condition)
|
| 345 |
+
elif loss_type == "distillation":
|
| 346 |
+
loss, unweighted_loss = self.distillation_loss(x0, condition)
|
| 347 |
+
else:
|
| 348 |
+
raise ValueError(f"Unknown loss type: {loss_type}")
|
| 349 |
+
|
| 350 |
+
loss.backward()
|
| 351 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 352 |
+
if self.grad_clip_norm else None
|
| 353 |
+
self.optimizer.step()
|
| 354 |
+
self.optimizer.zero_grad()
|
| 355 |
+
|
| 356 |
+
if update_ema:
|
| 357 |
+
self.ema_update()
|
| 358 |
+
|
| 359 |
+
if loss_type == "training":
|
| 360 |
+
self.cur_logger.incremental_update_k()
|
| 361 |
+
|
| 362 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm, "unweighted_loss": unweighted_loss}
|
| 363 |
+
|
| 364 |
+
return log
|
| 365 |
+
|
| 366 |
+
def sample(
|
| 367 |
+
self,
|
| 368 |
+
# ---------- the known fixed portion ---------- #
|
| 369 |
+
prior: torch.Tensor,
|
| 370 |
+
# ----------------- sampling ----------------- #
|
| 371 |
+
solver: str = "none",
|
| 372 |
+
n_samples: int = 1,
|
| 373 |
+
sample_steps: int = 5,
|
| 374 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 375 |
+
use_ema: bool = True,
|
| 376 |
+
temperature: float = 1.0,
|
| 377 |
+
# ------------------ guidance ------------------ #
|
| 378 |
+
condition_cfg=None,
|
| 379 |
+
mask_cfg=None,
|
| 380 |
+
w_cfg: float = 0.0,
|
| 381 |
+
condition_cg=None,
|
| 382 |
+
w_cg: float = 0.0,
|
| 383 |
+
# ----------- Diffusion-X sampling ----------
|
| 384 |
+
diffusion_x_sampling_steps: int = 0,
|
| 385 |
+
# ----------- Warm-Starting -----------
|
| 386 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 387 |
+
warm_start_forward_level: float = 0.3,
|
| 388 |
+
# ------------------ others ------------------ #
|
| 389 |
+
requires_grad: bool = False,
|
| 390 |
+
preserve_history: bool = False,
|
| 391 |
+
**kwargs,
|
| 392 |
+
):
|
| 393 |
+
assert w_cg == 0.0 and condition_cg is None, "Consistency Distillation does not support classifier guidance."
|
| 394 |
+
|
| 395 |
+
log = {
|
| 396 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 397 |
+
|
| 398 |
+
model = self.model if not use_ema else self.model_ema
|
| 399 |
+
|
| 400 |
+
prior = prior.to(self.device)
|
| 401 |
+
xt = torch.randn_like(prior) * self.sigma_max * temperature
|
| 402 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 403 |
+
|
| 404 |
+
if preserve_history:
|
| 405 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 406 |
+
|
| 407 |
+
with torch.set_grad_enabled(requires_grad):
|
| 408 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 409 |
+
|
| 410 |
+
# ===================== Sampling Schedule ====================
|
| 411 |
+
sample_step_schedule = ((self.sigma_min ** (1 / self.rho) + torch.arange(sample_steps + 1, device=self.device)
|
| 412 |
+
/ sample_steps * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (1 / self.rho)))
|
| 413 |
+
** self.rho)
|
| 414 |
+
|
| 415 |
+
t = torch.full((n_samples,), sample_step_schedule[-1], dtype=torch.float32, device=self.device)
|
| 416 |
+
pred_x = self.f(xt, t, condition_vec_cfg, model)
|
| 417 |
+
pred_x = pred_x * (1. - self.fix_mask) + prior * self.fix_mask
|
| 418 |
+
|
| 419 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps))
|
| 420 |
+
|
| 421 |
+
for i in reversed(loop_steps):
|
| 422 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
|
| 423 |
+
xt = pred_x + (at_least_ndim(t, xt.dim()) ** 2 - self.sigma_min ** 2).sqrt() * torch.randn_like(xt)
|
| 424 |
+
|
| 425 |
+
pred_x = self.f(xt, t, condition_vec_cfg, model)
|
| 426 |
+
pred_x = pred_x * (1. - self.fix_mask) + prior * self.fix_mask
|
| 427 |
+
|
| 428 |
+
return pred_x, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddim.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from typing import Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 9 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 10 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 11 |
+
from cleandiffuser.utils import at_least_ndim
|
| 12 |
+
from .basic import DiffusionModel
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class DDIM(DiffusionModel):
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
|
| 20 |
+
# ----------------- Neural Networks ----------------- #
|
| 21 |
+
nn_diffusion: BaseNNDiffusion,
|
| 22 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 23 |
+
|
| 24 |
+
# ----------------- Masks ----------------- #
|
| 25 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 26 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 27 |
+
# Add loss weight
|
| 28 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 29 |
+
|
| 30 |
+
# ------------------ Plugs ---------------- #
|
| 31 |
+
# Add a classifier to enable classifier-guidance
|
| 32 |
+
classifier: Optional[BaseClassifier] = None,
|
| 33 |
+
|
| 34 |
+
# ------------------ Params ---------------- #
|
| 35 |
+
grad_clip_norm: Optional[float] = None,
|
| 36 |
+
diffusion_steps: int = 1000,
|
| 37 |
+
ema_rate: float = 0.995,
|
| 38 |
+
optim_params: Optional[dict] = None,
|
| 39 |
+
|
| 40 |
+
# ------------------- DPM Params ------------------- #
|
| 41 |
+
noise_schedule: str = "linear", # or cosine
|
| 42 |
+
t_eps: float = 1e-3,
|
| 43 |
+
|
| 44 |
+
device: Union[torch.device, str] = "cpu"
|
| 45 |
+
):
|
| 46 |
+
super().__init__(
|
| 47 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 48 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 49 |
+
|
| 50 |
+
self.noise_schedule = noise_schedule
|
| 51 |
+
self.t_eps = t_eps
|
| 52 |
+
|
| 53 |
+
@property
|
| 54 |
+
def t_range(self):
|
| 55 |
+
if self.noise_schedule == "linear":
|
| 56 |
+
return self.t_eps, 1.
|
| 57 |
+
elif self.noise_schedule == "cosine":
|
| 58 |
+
return self.t_eps, 0.9946
|
| 59 |
+
else:
|
| 60 |
+
raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
|
| 61 |
+
|
| 62 |
+
def alpha_schedule(self, t):
|
| 63 |
+
if self.noise_schedule == "linear":
|
| 64 |
+
beta0, beta1 = 0.1, 20
|
| 65 |
+
return (-(beta1-beta0)/4*(t**2) - beta0/2*t).exp()
|
| 66 |
+
elif self.noise_schedule == "cosine":
|
| 67 |
+
s = 0.008
|
| 68 |
+
return ((torch.cos(np.pi/2*(t+s)/(1+s))).log() - np.log(np.cos(np.pi/2*s/(1+s)))).exp()
|
| 69 |
+
else:
|
| 70 |
+
raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
|
| 71 |
+
|
| 72 |
+
# ---------------------------------------------------------------------------
|
| 73 |
+
# Training
|
| 74 |
+
|
| 75 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 76 |
+
if t is None:
|
| 77 |
+
t = torch.rand((x0.shape[0], ), device=self.device)
|
| 78 |
+
t = self.t_range[0] + t * (self.t_range[1] - self.t_range[0])
|
| 79 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 80 |
+
alpha = self.alpha_schedule(at_least_ndim(t, x0.dim()))
|
| 81 |
+
sigma = (1 - alpha ** 2).sqrt()
|
| 82 |
+
xt = x0 * alpha + sigma * eps
|
| 83 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 84 |
+
return xt, t, eps
|
| 85 |
+
|
| 86 |
+
def loss(self, x0, condition=None):
|
| 87 |
+
xt, t, eps = self.add_noise(x0)
|
| 88 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 89 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 90 |
+
return (loss * self.loss_weight).mean()
|
| 91 |
+
|
| 92 |
+
def update(self, x0, condition=None, **kwargs):
|
| 93 |
+
self.optimizer.zero_grad()
|
| 94 |
+
loss = self.loss(x0, condition)
|
| 95 |
+
loss.backward()
|
| 96 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 97 |
+
if self.grad_clip_norm else None
|
| 98 |
+
self.optimizer.step()
|
| 99 |
+
self.ema_update()
|
| 100 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 101 |
+
return log
|
| 102 |
+
|
| 103 |
+
def update_classifier(self, x0, condition):
|
| 104 |
+
xt, t, eps = self.add_noise(x0)
|
| 105 |
+
log = self.classifier.update(xt, t, condition)
|
| 106 |
+
return log
|
| 107 |
+
|
| 108 |
+
# ---------------------------------------------------------------------------
|
| 109 |
+
# Inference
|
| 110 |
+
|
| 111 |
+
def sample(
|
| 112 |
+
self,
|
| 113 |
+
# ---------- the known fixed portion ---------- #
|
| 114 |
+
prior: Optional[torch.Tensor] = None,
|
| 115 |
+
# ----------------- sampling ----------------- #
|
| 116 |
+
n_samples: int = 1,
|
| 117 |
+
sample_steps: int = None,
|
| 118 |
+
use_ema: bool = True,
|
| 119 |
+
solver: str = "euler",
|
| 120 |
+
# ------------------ guidance ------------------ #
|
| 121 |
+
condition_cfg=None,
|
| 122 |
+
mask_cfg=None,
|
| 123 |
+
w_cfg: float = 0.0,
|
| 124 |
+
condition_cg=None,
|
| 125 |
+
w_cg: float = 0.0,
|
| 126 |
+
|
| 127 |
+
preserve_history: bool = False,
|
| 128 |
+
):
|
| 129 |
+
log, x_history = {}, None
|
| 130 |
+
model = self.model_ema if use_ema else self.model
|
| 131 |
+
|
| 132 |
+
t = torch.linspace(self.t_range[1], self.t_range[0], sample_steps + 1, device=self.device)
|
| 133 |
+
alphas = self.alpha_schedule(t)
|
| 134 |
+
sigmas = (1 - alphas ** 2).sqrt()
|
| 135 |
+
logSNRs = (alphas / sigmas).log()
|
| 136 |
+
|
| 137 |
+
if prior is None:
|
| 138 |
+
xt = torch.randn((n_samples, *self.default_x_shape), device=self.device)
|
| 139 |
+
else:
|
| 140 |
+
xt = torch.randn_like(prior, device=self.device) * 0.5
|
| 141 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 142 |
+
|
| 143 |
+
if preserve_history:
|
| 144 |
+
x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 145 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 146 |
+
|
| 147 |
+
with torch.no_grad():
|
| 148 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 149 |
+
condition_vec_cg = condition_cg
|
| 150 |
+
|
| 151 |
+
for i in range(sample_steps):
|
| 152 |
+
|
| 153 |
+
h = logSNRs[i + 1] - logSNRs[i]
|
| 154 |
+
|
| 155 |
+
t_batch = t[i].repeat(n_samples)
|
| 156 |
+
|
| 157 |
+
# ----------------- CFG ----------------- #
|
| 158 |
+
with torch.no_grad():
|
| 159 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 160 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 161 |
+
eps_theta = model["diffusion"](
|
| 162 |
+
torch.repeat_interleave(xt, 2, dim=0),
|
| 163 |
+
torch.repeat_interleave(t_batch, 2, dim=0),
|
| 164 |
+
condition_vec_cfg)
|
| 165 |
+
eps_theta = w_cfg * eps_theta[:n_samples] + (1. - w_cfg) * eps_theta[n_samples:]
|
| 166 |
+
else:
|
| 167 |
+
eps_theta = model["diffusion"](xt, t_batch, condition_vec_cfg)
|
| 168 |
+
# ----------------- CG ----------------- #
|
| 169 |
+
if self.classifier is not None and w_cg != 0.0:
|
| 170 |
+
log_p, grad = self.classifier.gradients(xt.clone(), t_batch, condition_vec_cg)
|
| 171 |
+
eps_theta = eps_theta - w_cg * sigmas[i] * grad
|
| 172 |
+
else:
|
| 173 |
+
log_p = None
|
| 174 |
+
|
| 175 |
+
# do not change the fixed portion
|
| 176 |
+
xt = alphas[i + 1] / alphas[i] * xt - sigmas[i + 1] * torch.expm1(h) * eps_theta
|
| 177 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 178 |
+
|
| 179 |
+
if preserve_history:
|
| 180 |
+
x_history[:, t] = xt.cpu().numpy()
|
| 181 |
+
log["log_p"] = log_p
|
| 182 |
+
|
| 183 |
+
log["sample_history"] = x_history
|
| 184 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 185 |
+
with torch.no_grad():
|
| 186 |
+
logp = self.classifier.logp(xt, t_batch, condition_vec_cg)
|
| 187 |
+
log["log_p"] = logp
|
| 188 |
+
|
| 189 |
+
return xt, log
|
| 190 |
+
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddpm.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import (
|
| 11 |
+
at_least_ndim,
|
| 12 |
+
cosine_beta_schedule,
|
| 13 |
+
linear_beta_schedule)
|
| 14 |
+
from .basic import DiffusionModel
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DDPM(DiffusionModel):
|
| 18 |
+
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
|
| 22 |
+
# ----------------- Neural Networks ----------------- #
|
| 23 |
+
nn_diffusion: BaseNNDiffusion,
|
| 24 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 25 |
+
|
| 26 |
+
# ----------------- Masks ----------------- #
|
| 27 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 28 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 29 |
+
# Add loss weight
|
| 30 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 31 |
+
|
| 32 |
+
# ------------------ Plugs ---------------- #
|
| 33 |
+
# Add a classifier to enable classifier-guidance
|
| 34 |
+
classifier: Optional[BaseClassifier] = None,
|
| 35 |
+
|
| 36 |
+
# ------------------ Params ---------------- #
|
| 37 |
+
grad_clip_norm: Optional[float] = None,
|
| 38 |
+
diffusion_steps: int = 1000,
|
| 39 |
+
ema_rate: float = 0.995,
|
| 40 |
+
optim_params: Optional[dict] = None,
|
| 41 |
+
x_max: Optional[torch.Tensor] = None,
|
| 42 |
+
x_min: Optional[torch.Tensor] = None,
|
| 43 |
+
|
| 44 |
+
# ------------------- DPM Params ------------------- #
|
| 45 |
+
predict_noise: bool = True,
|
| 46 |
+
beta_schedule: str = "cosine", # or cosine
|
| 47 |
+
beta_schedule_params: Optional[dict] = None,
|
| 48 |
+
|
| 49 |
+
device: Union[torch.device, str] = "cpu"
|
| 50 |
+
):
|
| 51 |
+
super().__init__(
|
| 52 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 53 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 54 |
+
|
| 55 |
+
self.predict_noise = predict_noise
|
| 56 |
+
|
| 57 |
+
if beta_schedule_params is None:
|
| 58 |
+
beta_schedule_params = {}
|
| 59 |
+
beta_schedule_params["T"] = self.diffusion_steps
|
| 60 |
+
|
| 61 |
+
if beta_schedule == "linear":
|
| 62 |
+
beta = linear_beta_schedule(**beta_schedule_params)
|
| 63 |
+
elif beta_schedule == "cosine":
|
| 64 |
+
beta = cosine_beta_schedule(**beta_schedule_params)
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(f"Unknown beta schedule: {beta_schedule}")
|
| 67 |
+
|
| 68 |
+
self.beta = torch.tensor(beta, device=self.device, dtype=torch.float32)
|
| 69 |
+
self.alpha = 1 - self.beta
|
| 70 |
+
self.bar_alpha = torch.cumprod(self.alpha.clone(), 0)
|
| 71 |
+
self.x_max, self.x_min = x_max, x_min
|
| 72 |
+
|
| 73 |
+
@property
|
| 74 |
+
def clip_pred(self):
|
| 75 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 76 |
+
|
| 77 |
+
# ---------------------------------------------------------------------------
|
| 78 |
+
# Training
|
| 79 |
+
|
| 80 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 81 |
+
t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
|
| 82 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 83 |
+
bar_alpha = at_least_ndim(self.bar_alpha[t], x0.dim())
|
| 84 |
+
xt = x0 * bar_alpha.sqrt() + eps * (1 - bar_alpha).sqrt()
|
| 85 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 86 |
+
return xt, t, eps
|
| 87 |
+
|
| 88 |
+
def loss(self, x0, condition=None):
|
| 89 |
+
xt, t, eps = self.add_noise(x0)
|
| 90 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 91 |
+
if self.predict_noise:
|
| 92 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 93 |
+
else:
|
| 94 |
+
loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
|
| 95 |
+
|
| 96 |
+
return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
|
| 97 |
+
|
| 98 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 99 |
+
loss = self.loss(x0, condition)
|
| 100 |
+
loss.backward()
|
| 101 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 102 |
+
if self.grad_clip_norm else None
|
| 103 |
+
self.optimizer.step()
|
| 104 |
+
self.optimizer.zero_grad()
|
| 105 |
+
if update_ema: self.ema_update()
|
| 106 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 107 |
+
return log
|
| 108 |
+
|
| 109 |
+
def update_classifier(self, x0, condition):
|
| 110 |
+
xt, t, eps = self.add_noise(x0)
|
| 111 |
+
log = self.classifier.update(xt, t, condition)
|
| 112 |
+
return log
|
| 113 |
+
|
| 114 |
+
# ---------------------------------------------------------------------------
|
| 115 |
+
# Inference
|
| 116 |
+
|
| 117 |
+
def predict_function(
|
| 118 |
+
self, x, t, bar_alpha,
|
| 119 |
+
use_ema=False, requires_grad=False,
|
| 120 |
+
# ----------------- CFG ----------------- #
|
| 121 |
+
condition_vec_cfg=None,
|
| 122 |
+
w_cfg: float = 0.0,
|
| 123 |
+
# ----------------- CG ----------------- #
|
| 124 |
+
condition_vec_cg=None,
|
| 125 |
+
w_cg: float = 1.0,
|
| 126 |
+
):
|
| 127 |
+
b = x.shape[0]
|
| 128 |
+
model = self.model_ema if use_ema else self.model
|
| 129 |
+
|
| 130 |
+
# ----------------- CFG ----------------- #
|
| 131 |
+
with torch.set_grad_enabled(requires_grad):
|
| 132 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 133 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
|
| 134 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 135 |
+
pred = model["diffusion"](
|
| 136 |
+
x.repeat(*repeat_dim), t.repeat(2), condition_vec_cfg)
|
| 137 |
+
pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
|
| 138 |
+
elif w_cfg == 0.0:
|
| 139 |
+
pred = model["diffusion"](x, t, None)
|
| 140 |
+
else:
|
| 141 |
+
pred = model["diffusion"](x, t, condition_vec_cfg)
|
| 142 |
+
|
| 143 |
+
# ----------------- CG ----------------- #
|
| 144 |
+
if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
|
| 145 |
+
log_p, grad = self.classifier.gradients(x.clone(), t, condition_vec_cg)
|
| 146 |
+
if self.predict_noise:
|
| 147 |
+
pred = pred - w_cg * (1 - bar_alpha).sqrt() * grad
|
| 148 |
+
else:
|
| 149 |
+
pred = pred + w_cg * (1 - bar_alpha) / bar_alpha.sqrt() * grad
|
| 150 |
+
else:
|
| 151 |
+
log_p = None
|
| 152 |
+
|
| 153 |
+
if self.predict_noise:
|
| 154 |
+
if self.clip_pred:
|
| 155 |
+
upper_bound = (x - bar_alpha.sqrt() * self.x_min) / (1 - bar_alpha).sqrt() \
|
| 156 |
+
if self.x_min is not None else None
|
| 157 |
+
lower_bound = (x - bar_alpha.sqrt() * self.x_max) / (1 - bar_alpha).sqrt() \
|
| 158 |
+
if self.x_max is not None else None
|
| 159 |
+
pred = pred.clip(lower_bound, upper_bound)
|
| 160 |
+
pred = pred * (1 - self.fix_mask)
|
| 161 |
+
else:
|
| 162 |
+
if self.clip_pred:
|
| 163 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 164 |
+
pred = pred * (1 - self.fix_mask) + x * self.fix_mask
|
| 165 |
+
|
| 166 |
+
return pred, {"log_p": log_p}
|
| 167 |
+
|
| 168 |
+
def sample(
|
| 169 |
+
self,
|
| 170 |
+
# ---------- the known fixed portion ---------- #
|
| 171 |
+
prior: Optional[torch.Tensor] = None,
|
| 172 |
+
# ----------------- sampling ----------------- #
|
| 173 |
+
n_samples: int = 1,
|
| 174 |
+
sample_steps: int = None,
|
| 175 |
+
use_ema: bool = True,
|
| 176 |
+
temperature: float = 1.0,
|
| 177 |
+
# ------------------ guidance ------------------ #
|
| 178 |
+
condition_cfg=None,
|
| 179 |
+
mask_cfg=None,
|
| 180 |
+
w_cfg: float = 0.0,
|
| 181 |
+
condition_cg=None,
|
| 182 |
+
w_cg: float = 0.0,
|
| 183 |
+
# ------------------ others ------------------ #
|
| 184 |
+
requires_grad: bool = False,
|
| 185 |
+
preserve_history: bool = False,
|
| 186 |
+
**kwargs,
|
| 187 |
+
):
|
| 188 |
+
# initialize logger
|
| 189 |
+
log = {
|
| 190 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 191 |
+
|
| 192 |
+
# choose the model
|
| 193 |
+
model = self.model_ema if use_ema else self.model
|
| 194 |
+
|
| 195 |
+
# check `sample_steps`
|
| 196 |
+
if sample_steps != self.diffusion_steps:
|
| 197 |
+
import warnings
|
| 198 |
+
warnings.warn(f"sample_steps != diffusion_steps, sample_steps will be set to diffusion_steps.")
|
| 199 |
+
sample_steps = self.diffusion_steps
|
| 200 |
+
|
| 201 |
+
# initialize the samples
|
| 202 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 203 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 204 |
+
if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 205 |
+
|
| 206 |
+
# preprocess the conditions
|
| 207 |
+
with torch.set_grad_enabled(requires_grad):
|
| 208 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 209 |
+
condition_vec_cg = condition_cg
|
| 210 |
+
|
| 211 |
+
# enter the sampling loop
|
| 212 |
+
for t in range(self.diffusion_steps - 1, -1, -1):
|
| 213 |
+
|
| 214 |
+
t_batch = torch.tensor(t, device=self.device, dtype=torch.long).repeat(n_samples)
|
| 215 |
+
bar_alpha = self.bar_alpha[t]
|
| 216 |
+
bar_alpha_prev = self.bar_alpha[t - 1] if t > 0 else torch.tensor(1.0, device=self.device)
|
| 217 |
+
alpha = self.alpha[t]
|
| 218 |
+
beta = self.beta[t]
|
| 219 |
+
|
| 220 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 221 |
+
pred_theta, log = self.predict_function(
|
| 222 |
+
xt, t_batch, bar_alpha,
|
| 223 |
+
use_ema=use_ema,
|
| 224 |
+
requires_grad=requires_grad,
|
| 225 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 226 |
+
condition_vec_cg=condition_vec_cg,
|
| 227 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 228 |
+
|
| 229 |
+
# one step denoise
|
| 230 |
+
if self.predict_noise:
|
| 231 |
+
|
| 232 |
+
xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
|
| 233 |
+
|
| 234 |
+
else:
|
| 235 |
+
|
| 236 |
+
xt = 1 / (1 - bar_alpha) * (
|
| 237 |
+
alpha.sqrt() * (1 - bar_alpha_prev) * xt +
|
| 238 |
+
beta * bar_alpha_prev.sqrt() * pred_theta)
|
| 239 |
+
|
| 240 |
+
if t != 0:
|
| 241 |
+
xt = xt + (beta * (1 - bar_alpha_prev) / (1 - bar_alpha)).sqrt() * torch.randn_like(xt)
|
| 242 |
+
|
| 243 |
+
# Fix the known portion
|
| 244 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 245 |
+
if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
|
| 246 |
+
|
| 247 |
+
# calculate the final log_p
|
| 248 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 249 |
+
with torch.no_grad():
|
| 250 |
+
logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 251 |
+
log["log_p"] = logp
|
| 252 |
+
|
| 253 |
+
return xt, log
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def sample_x(
|
| 257 |
+
self,
|
| 258 |
+
# ---------- the known fixed portion ---------- #
|
| 259 |
+
prior: Optional[torch.Tensor] = None,
|
| 260 |
+
# ----------------- sampling ----------------- #
|
| 261 |
+
n_samples: int = 1,
|
| 262 |
+
sample_steps: int = None,
|
| 263 |
+
extra_sample_steps: int = 8,
|
| 264 |
+
use_ema: bool = True,
|
| 265 |
+
temperature: float = 1.0,
|
| 266 |
+
# ------------------ guidance ------------------ #
|
| 267 |
+
condition_cfg=None,
|
| 268 |
+
mask_cfg=None,
|
| 269 |
+
w_cfg: float = 0.0,
|
| 270 |
+
condition_cg=None,
|
| 271 |
+
w_cg: float = 0.0,
|
| 272 |
+
# ------------------ others ------------------ #
|
| 273 |
+
requires_grad: bool = False,
|
| 274 |
+
preserve_history: bool = False,
|
| 275 |
+
**kwargs,
|
| 276 |
+
):
|
| 277 |
+
# initialize logger
|
| 278 |
+
log = {
|
| 279 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 280 |
+
|
| 281 |
+
# choose the model
|
| 282 |
+
model = self.model_ema if use_ema else self.model
|
| 283 |
+
|
| 284 |
+
# check `sample_steps`
|
| 285 |
+
if sample_steps != self.diffusion_steps:
|
| 286 |
+
import warnings
|
| 287 |
+
warnings.warn(f"sample_steps != diffusion_steps, sample_steps will be set to diffusion_steps.")
|
| 288 |
+
sample_steps = self.diffusion_steps
|
| 289 |
+
|
| 290 |
+
# initialize the samples
|
| 291 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 292 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 293 |
+
if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 294 |
+
|
| 295 |
+
# preprocess the conditions
|
| 296 |
+
with torch.set_grad_enabled(requires_grad):
|
| 297 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 298 |
+
condition_vec_cg = condition_cg
|
| 299 |
+
|
| 300 |
+
# enter the sampling loop
|
| 301 |
+
for t in range(self.diffusion_steps - 1, -1, -1):
|
| 302 |
+
|
| 303 |
+
t_batch = torch.tensor(t, device=self.device, dtype=torch.long).repeat(n_samples)
|
| 304 |
+
bar_alpha = self.bar_alpha[t]
|
| 305 |
+
bar_alpha_prev = self.bar_alpha[t - 1] if t > 0 else torch.tensor(1.0, device=self.device)
|
| 306 |
+
alpha = self.alpha[t]
|
| 307 |
+
beta = self.beta[t]
|
| 308 |
+
|
| 309 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 310 |
+
pred_theta, log = self.predict_function(
|
| 311 |
+
xt, t_batch, bar_alpha,
|
| 312 |
+
use_ema=use_ema,
|
| 313 |
+
requires_grad=requires_grad,
|
| 314 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 315 |
+
condition_vec_cg=condition_vec_cg,
|
| 316 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 317 |
+
|
| 318 |
+
# one step denoise
|
| 319 |
+
if self.predict_noise:
|
| 320 |
+
|
| 321 |
+
xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
|
| 322 |
+
|
| 323 |
+
else:
|
| 324 |
+
|
| 325 |
+
xt = 1 / (1 - bar_alpha) * (
|
| 326 |
+
alpha.sqrt() * (1 - bar_alpha_prev) * xt +
|
| 327 |
+
beta * bar_alpha_prev.sqrt() * pred_theta)
|
| 328 |
+
|
| 329 |
+
if t != 0:
|
| 330 |
+
xt = xt + (beta * (1 - bar_alpha_prev) / (1 - bar_alpha)).sqrt() * torch.randn_like(xt)
|
| 331 |
+
|
| 332 |
+
# Fix the known portion
|
| 333 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 334 |
+
if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
|
| 335 |
+
|
| 336 |
+
if extra_sample_steps > 0:
|
| 337 |
+
|
| 338 |
+
t_batch = torch.tensor(0, device=self.device, dtype=torch.long).repeat(n_samples)
|
| 339 |
+
bar_alpha = self.bar_alpha[0]
|
| 340 |
+
bar_alpha_prev = torch.tensor(1.0, device=self.device)
|
| 341 |
+
alpha = self.alpha[0]
|
| 342 |
+
beta = self.beta[0]
|
| 343 |
+
|
| 344 |
+
for _ in range(extra_sample_steps):
|
| 345 |
+
|
| 346 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 347 |
+
pred_theta, log = self.predict_function(
|
| 348 |
+
xt, t_batch, bar_alpha,
|
| 349 |
+
use_ema=use_ema,
|
| 350 |
+
requires_grad=requires_grad,
|
| 351 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 352 |
+
condition_vec_cg=condition_vec_cg,
|
| 353 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 354 |
+
|
| 355 |
+
# one step denoise
|
| 356 |
+
if self.predict_noise:
|
| 357 |
+
|
| 358 |
+
xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
|
| 359 |
+
|
| 360 |
+
else:
|
| 361 |
+
|
| 362 |
+
xt = 1 / (1 - bar_alpha) * (
|
| 363 |
+
alpha.sqrt() * (1 - bar_alpha_prev) * xt +
|
| 364 |
+
beta * bar_alpha_prev.sqrt() * pred_theta)
|
| 365 |
+
|
| 366 |
+
# Fix the known portion
|
| 367 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 368 |
+
|
| 369 |
+
# calculate the final log_p
|
| 370 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 371 |
+
with torch.no_grad():
|
| 372 |
+
logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 373 |
+
log["log_p"] = logp
|
| 374 |
+
|
| 375 |
+
return xt, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/diffusionsde.py
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union, Callable, Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import (
|
| 11 |
+
at_least_ndim,
|
| 12 |
+
SUPPORTED_NOISE_SCHEDULES, SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
|
| 13 |
+
from .basic import DiffusionModel
|
| 14 |
+
|
| 15 |
+
SUPPORTED_SOLVERS = [
|
| 16 |
+
"ddpm", "ddim",
|
| 17 |
+
"ode_dpmsolver_1", "ode_dpmsolver++_1", "ode_dpmsolver++_2M",
|
| 18 |
+
"sde_dpmsolver_1", "sde_dpmsolver++_1", "sde_dpmsolver++_2M",]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
|
| 22 |
+
"""
|
| 23 |
+
x_theta = (x - sigma * eps_theta) / alpha
|
| 24 |
+
"""
|
| 25 |
+
return (x - sigma * eps_theta) / alpha
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def xtheta_to_epstheta(x, alpha, sigma, x_theta):
|
| 29 |
+
"""
|
| 30 |
+
eps_theta = (x - alpha * x_theta) / sigma
|
| 31 |
+
"""
|
| 32 |
+
return (x - alpha * x_theta) / sigma
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class BaseDiffusionSDE(DiffusionModel):
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
|
| 40 |
+
# ----------------- Neural Networks ----------------- #
|
| 41 |
+
nn_diffusion: BaseNNDiffusion,
|
| 42 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 43 |
+
|
| 44 |
+
# ----------------- Masks ----------------- #
|
| 45 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 46 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 47 |
+
# Add loss weight
|
| 48 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 49 |
+
|
| 50 |
+
# ------------------ Plugins ---------------- #
|
| 51 |
+
# Add a classifier to enable classifier-guidance
|
| 52 |
+
classifier: Optional[BaseClassifier] = None,
|
| 53 |
+
|
| 54 |
+
# ------------------ Training Params ---------------- #
|
| 55 |
+
grad_clip_norm: Optional[float] = None,
|
| 56 |
+
ema_rate: float = 0.995,
|
| 57 |
+
optim_params: Optional[dict] = None,
|
| 58 |
+
|
| 59 |
+
# ------------------- Diffusion Params ------------------- #
|
| 60 |
+
epsilon: float = 1e-3,
|
| 61 |
+
|
| 62 |
+
noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
|
| 63 |
+
noise_schedule_params: Optional[dict] = None,
|
| 64 |
+
|
| 65 |
+
x_max: Optional[torch.Tensor] = None,
|
| 66 |
+
x_min: Optional[torch.Tensor] = None,
|
| 67 |
+
|
| 68 |
+
predict_noise: bool = True,
|
| 69 |
+
|
| 70 |
+
device: Union[torch.device, str] = "cpu"
|
| 71 |
+
):
|
| 72 |
+
super().__init__(
|
| 73 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 74 |
+
0, ema_rate, optim_params, device)
|
| 75 |
+
|
| 76 |
+
self.predict_noise = predict_noise
|
| 77 |
+
self.epsilon = epsilon
|
| 78 |
+
self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
|
| 79 |
+
self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
|
| 80 |
+
|
| 81 |
+
@property
|
| 82 |
+
def supported_solvers(self):
|
| 83 |
+
return SUPPORTED_SOLVERS
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def clip_pred(self):
|
| 87 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 88 |
+
|
| 89 |
+
# ==================== Training: Score Matching ======================
|
| 90 |
+
|
| 91 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 92 |
+
raise NotImplementedError
|
| 93 |
+
|
| 94 |
+
def loss(self, x0, condition=None, **kwargs):
|
| 95 |
+
|
| 96 |
+
xt, t, eps = self.add_noise(x0)
|
| 97 |
+
|
| 98 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 99 |
+
|
| 100 |
+
if self.predict_noise:
|
| 101 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 102 |
+
else:
|
| 103 |
+
loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
|
| 104 |
+
|
| 105 |
+
loss = loss * self.loss_weight * (1 - self.fix_mask)
|
| 106 |
+
|
| 107 |
+
# find weighted_regression_tensor in kwargs
|
| 108 |
+
weighted_regression_tensor = kwargs.get("weighted_regression_tensor", None)
|
| 109 |
+
if weighted_regression_tensor is not None:
|
| 110 |
+
loss *= weighted_regression_tensor.unsqueeze(-1)
|
| 111 |
+
|
| 112 |
+
return loss.mean()
|
| 113 |
+
|
| 114 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 115 |
+
"""One-step gradient update.
|
| 116 |
+
Inputs:
|
| 117 |
+
- x0: torch.Tensor
|
| 118 |
+
Samples from the target distribution.
|
| 119 |
+
- condition: Optional
|
| 120 |
+
Condition of x0. `None` indicates no condition.
|
| 121 |
+
- update_ema: bool
|
| 122 |
+
Whether to update the exponential moving average model.
|
| 123 |
+
|
| 124 |
+
Outputs:
|
| 125 |
+
- log: dict
|
| 126 |
+
The log dictionary.
|
| 127 |
+
"""
|
| 128 |
+
loss = self.loss(x0, condition, **kwargs)
|
| 129 |
+
|
| 130 |
+
loss.backward()
|
| 131 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 132 |
+
if self.grad_clip_norm else None
|
| 133 |
+
self.optimizer.step()
|
| 134 |
+
self.optimizer.zero_grad()
|
| 135 |
+
|
| 136 |
+
if update_ema:
|
| 137 |
+
self.ema_update()
|
| 138 |
+
|
| 139 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 140 |
+
|
| 141 |
+
return log
|
| 142 |
+
|
| 143 |
+
def update_classifier(self, x0, condition):
|
| 144 |
+
|
| 145 |
+
xt, t, eps = self.add_noise(x0)
|
| 146 |
+
|
| 147 |
+
log = self.classifier.update(xt, t, condition)
|
| 148 |
+
|
| 149 |
+
return log
|
| 150 |
+
|
| 151 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 152 |
+
|
| 153 |
+
def classifier_guidance(
|
| 154 |
+
self, xt, t, alpha, sigma,
|
| 155 |
+
model, condition=None, w: float = 1.0,
|
| 156 |
+
pred=None):
|
| 157 |
+
"""
|
| 158 |
+
Guided Sampling CG:
|
| 159 |
+
bar_eps = eps - w * sigma * grad
|
| 160 |
+
bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
|
| 161 |
+
"""
|
| 162 |
+
if pred is None:
|
| 163 |
+
pred = model["diffusion"](xt, t, None)
|
| 164 |
+
if self.classifier is None or w == 0.0:
|
| 165 |
+
return pred, None
|
| 166 |
+
else:
|
| 167 |
+
log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
|
| 168 |
+
if self.predict_noise:
|
| 169 |
+
pred = pred - w * sigma * grad
|
| 170 |
+
else:
|
| 171 |
+
pred = pred + w * ((sigma ** 2) / alpha) * grad
|
| 172 |
+
|
| 173 |
+
return pred, log_p
|
| 174 |
+
|
| 175 |
+
def classifier_free_guidance(
|
| 176 |
+
self, xt, t,
|
| 177 |
+
model, condition=None, w: float = 1.0,
|
| 178 |
+
pred=None, pred_uncond=None,
|
| 179 |
+
requires_grad: bool = False):
|
| 180 |
+
"""
|
| 181 |
+
Guided Sampling CFG:
|
| 182 |
+
bar_eps = w * pred + (1 - w) * pred_uncond
|
| 183 |
+
bar_x0 = w * pred + (1 - w) * pred_uncond
|
| 184 |
+
"""
|
| 185 |
+
with torch.set_grad_enabled(requires_grad):
|
| 186 |
+
if w != 0.0 and w != 1.0:
|
| 187 |
+
if pred is None or pred_uncond is None:
|
| 188 |
+
b = xt.shape[0]
|
| 189 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 190 |
+
condition = torch.cat([condition, torch.zeros_like(condition)], 0)
|
| 191 |
+
pred_all = model["diffusion"](
|
| 192 |
+
xt.repeat(*repeat_dim), t.repeat(2), condition)
|
| 193 |
+
pred, pred_uncond = pred_all[:b], pred_all[b:]
|
| 194 |
+
elif w == 0.0:
|
| 195 |
+
pred = 0.
|
| 196 |
+
pred_uncond = model["diffusion"](xt, t, None)
|
| 197 |
+
else:
|
| 198 |
+
pred = model["diffusion"](xt, t, condition)
|
| 199 |
+
pred_uncond = 0.
|
| 200 |
+
|
| 201 |
+
if self.predict_noise or not self.predict_noise:
|
| 202 |
+
bar_pred = w * pred + (1 - w) * pred_uncond
|
| 203 |
+
else:
|
| 204 |
+
bar_pred = pred
|
| 205 |
+
|
| 206 |
+
return bar_pred
|
| 207 |
+
|
| 208 |
+
def clip_prediction(self, pred, xt, alpha, sigma):
|
| 209 |
+
"""
|
| 210 |
+
Clip the prediction at each sampling step to stablize the generation.
|
| 211 |
+
(xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
|
| 212 |
+
x_min <= x0 <= x_max
|
| 213 |
+
"""
|
| 214 |
+
if self.predict_noise:
|
| 215 |
+
if self.clip_pred:
|
| 216 |
+
upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
|
| 217 |
+
lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
|
| 218 |
+
pred = pred.clip(lower_bound, upper_bound)
|
| 219 |
+
else:
|
| 220 |
+
if self.clip_pred:
|
| 221 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 222 |
+
|
| 223 |
+
return pred
|
| 224 |
+
|
| 225 |
+
def guided_sampling(
|
| 226 |
+
self, xt, t, alpha, sigma,
|
| 227 |
+
model,
|
| 228 |
+
condition_cfg=None, w_cfg: float = 0.0,
|
| 229 |
+
condition_cg=None, w_cg: float = 0.0,
|
| 230 |
+
requires_grad: bool = False):
|
| 231 |
+
"""
|
| 232 |
+
One-step epsilon/x0 prediction with guidance.
|
| 233 |
+
"""
|
| 234 |
+
|
| 235 |
+
pred = self.classifier_free_guidance(
|
| 236 |
+
xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
|
| 237 |
+
|
| 238 |
+
pred, logp = self.classifier_guidance(
|
| 239 |
+
xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
|
| 240 |
+
|
| 241 |
+
return pred, logp
|
| 242 |
+
|
| 243 |
+
def sample(self, *args, **kwargs):
|
| 244 |
+
raise NotImplementedError
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
class DiscreteDiffusionSDE(BaseDiffusionSDE):
|
| 248 |
+
"""Discrete-time Diffusion SDE (VP-SDE)
|
| 249 |
+
|
| 250 |
+
The Diffusion SDE is currently one of the most commonly used formulations of diffusion processes.
|
| 251 |
+
Its training process involves utilizing neural networks to estimate its scaled score function,
|
| 252 |
+
which is used to compute the reverse process. The Diffusion SDE has reverse processes
|
| 253 |
+
in both SDE and ODE forms, sharing the same marginal distribution.
|
| 254 |
+
The first-order discretized forms of both are equivalent to well-known models such as DDPM and DDIM.
|
| 255 |
+
DPM-Solvers have observed the semi-linearity of the reverse process and have computed its exact solution.
|
| 256 |
+
|
| 257 |
+
The DiscreteDiffusionSDE is the discrete-time version of the Diffusion SDE.
|
| 258 |
+
It discretizes the continuous time interval into a finite number of diffusion steps
|
| 259 |
+
and only estimates the score function on these steps.
|
| 260 |
+
Therefore, in sampling, solvers can only work on these learned steps.
|
| 261 |
+
The sampling steps are required to be greater than 1 and less than or equal to the number of diffusion steps.
|
| 262 |
+
|
| 263 |
+
Args:
|
| 264 |
+
- nn_diffusion: BaseNNDiffusion
|
| 265 |
+
The neural network backbone for the Diffusion model.
|
| 266 |
+
- nn_condition: Optional[BaseNNCondition]
|
| 267 |
+
The neural network backbone for the condition embedding.
|
| 268 |
+
|
| 269 |
+
- fix_mask: Union[list, np.ndarray, torch.Tensor]
|
| 270 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 271 |
+
The mask should be in the shape of `x_shape`.
|
| 272 |
+
- loss_weight: Union[list, np.ndarray, torch.Tensor]
|
| 273 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 274 |
+
|
| 275 |
+
- classifier: Optional[BaseClassifier]
|
| 276 |
+
Add a classifier to enable classifier-guidance.
|
| 277 |
+
|
| 278 |
+
- grad_clip_norm: Optional[float]
|
| 279 |
+
Gradient clipping norm.
|
| 280 |
+
- ema_rate: float
|
| 281 |
+
Exponential moving average rate.
|
| 282 |
+
- optim_params: Optional[dict]
|
| 283 |
+
Optimizer parameters.
|
| 284 |
+
|
| 285 |
+
- epsilon: float
|
| 286 |
+
The minimum time step for the diffusion reverse process.
|
| 287 |
+
In practice, using a very small value instead of `0` can avoid numerical instability.
|
| 288 |
+
|
| 289 |
+
- diffusion_steps: int
|
| 290 |
+
The discretization steps for discrete-time diffusion models.
|
| 291 |
+
- discretization: Union[str, Callable]
|
| 292 |
+
The discretization method for the diffusion steps.
|
| 293 |
+
|
| 294 |
+
- noise_schedule: Union[str, Dict[str, Callable]]
|
| 295 |
+
The noise schedule for the diffusion process. Can be "linear" or "cosine".
|
| 296 |
+
- noise_schedule_params: Optional[dict]
|
| 297 |
+
The parameters for the noise schedule.
|
| 298 |
+
|
| 299 |
+
- x_max: Optional[torch.Tensor]
|
| 300 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 301 |
+
- x_min: Optional[torch.Tensor]
|
| 302 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 303 |
+
|
| 304 |
+
- predict_noise: bool
|
| 305 |
+
Whether to predict the noise or the data.
|
| 306 |
+
|
| 307 |
+
- device: Union[torch.device, str]
|
| 308 |
+
The device to run the model.
|
| 309 |
+
"""
|
| 310 |
+
def __init__(
|
| 311 |
+
self,
|
| 312 |
+
|
| 313 |
+
# ----------------- Neural Networks ----------------- #
|
| 314 |
+
nn_diffusion: BaseNNDiffusion,
|
| 315 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 316 |
+
|
| 317 |
+
# ----------------- Masks ----------------- #
|
| 318 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 319 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 320 |
+
# Add loss weight
|
| 321 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 322 |
+
|
| 323 |
+
# ------------------ Plugins ---------------- #
|
| 324 |
+
# Add a classifier to enable classifier-guidance
|
| 325 |
+
classifier: Optional[BaseClassifier] = None,
|
| 326 |
+
|
| 327 |
+
# ------------------ Training Params ---------------- #
|
| 328 |
+
grad_clip_norm: Optional[float] = None,
|
| 329 |
+
ema_rate: float = 0.995,
|
| 330 |
+
optim_params: Optional[dict] = None,
|
| 331 |
+
|
| 332 |
+
# ------------------- Diffusion Params ------------------- #
|
| 333 |
+
epsilon: float = 1e-3,
|
| 334 |
+
|
| 335 |
+
diffusion_steps: int = 1000,
|
| 336 |
+
discretization: Union[str, Callable] = "uniform",
|
| 337 |
+
|
| 338 |
+
noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
|
| 339 |
+
noise_schedule_params: Optional[dict] = None,
|
| 340 |
+
|
| 341 |
+
x_max: Optional[torch.Tensor] = None,
|
| 342 |
+
x_min: Optional[torch.Tensor] = None,
|
| 343 |
+
|
| 344 |
+
predict_noise: bool = True,
|
| 345 |
+
|
| 346 |
+
device: Union[torch.device, str] = "cpu"
|
| 347 |
+
):
|
| 348 |
+
super().__init__(
|
| 349 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm, ema_rate, optim_params,
|
| 350 |
+
epsilon, noise_schedule, noise_schedule_params, x_max, x_min, predict_noise, device)
|
| 351 |
+
|
| 352 |
+
self.diffusion_steps = diffusion_steps
|
| 353 |
+
|
| 354 |
+
if 1. / diffusion_steps < epsilon:
|
| 355 |
+
raise ValueError("epsilon is too large for the number of diffusion steps")
|
| 356 |
+
|
| 357 |
+
# ================= Discretization =================
|
| 358 |
+
# - Map the continuous range [epsilon, 1.] to the discrete range [0, T-1]
|
| 359 |
+
if isinstance(discretization, str):
|
| 360 |
+
if discretization in SUPPORTED_DISCRETIZATIONS.keys():
|
| 361 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, epsilon).to(device)
|
| 362 |
+
else:
|
| 363 |
+
Warning(f"Discretization method {discretization} is not supported. "
|
| 364 |
+
f"Using uniform discretization instead.")
|
| 365 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, epsilon).to(device)
|
| 366 |
+
elif callable(discretization):
|
| 367 |
+
self.t_diffusion = discretization(diffusion_steps, epsilon).to(device)
|
| 368 |
+
else:
|
| 369 |
+
raise ValueError("discretization must be a callable or a string")
|
| 370 |
+
|
| 371 |
+
# ================= Noise Schedule =================
|
| 372 |
+
if isinstance(noise_schedule, str):
|
| 373 |
+
if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
|
| 374 |
+
self.alpha, self.sigma = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["forward"](
|
| 375 |
+
self.t_diffusion, **(noise_schedule_params or {}))
|
| 376 |
+
else:
|
| 377 |
+
raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
|
| 378 |
+
elif isinstance(noise_schedule, dict):
|
| 379 |
+
self.alpha, self.sigma = noise_schedule["forward"](self.t_diffusion, **(noise_schedule_params or {}))
|
| 380 |
+
else:
|
| 381 |
+
raise ValueError("noise_schedule must be a callable or a string")
|
| 382 |
+
|
| 383 |
+
self.logSNR = torch.log(self.alpha / self.sigma)
|
| 384 |
+
|
| 385 |
+
# ==================== Training: Score Matching ======================
|
| 386 |
+
|
| 387 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 388 |
+
|
| 389 |
+
t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
|
| 390 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 391 |
+
|
| 392 |
+
alpha, sigma = at_least_ndim(self.alpha[t], x0.dim()), at_least_ndim(self.sigma[t], x0.dim())
|
| 393 |
+
|
| 394 |
+
xt = alpha * x0 + sigma * eps
|
| 395 |
+
xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
|
| 396 |
+
|
| 397 |
+
return xt, t, eps
|
| 398 |
+
|
| 399 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 400 |
+
|
| 401 |
+
def sample(
|
| 402 |
+
self,
|
| 403 |
+
# ---------- the known fixed portion ---------- #
|
| 404 |
+
prior: torch.Tensor,
|
| 405 |
+
# ----------------- sampling ----------------- #
|
| 406 |
+
solver: str = "ddpm",
|
| 407 |
+
n_samples: int = 1,
|
| 408 |
+
sample_steps: int = 5,
|
| 409 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 410 |
+
use_ema: bool = True,
|
| 411 |
+
temperature: float = 1.0,
|
| 412 |
+
# ------------------ guidance ------------------ #
|
| 413 |
+
condition_cfg=None,
|
| 414 |
+
mask_cfg=None,
|
| 415 |
+
w_cfg: float = 0.0,
|
| 416 |
+
condition_cg=None,
|
| 417 |
+
w_cg: float = 0.0,
|
| 418 |
+
# ----------- Diffusion-X sampling ----------
|
| 419 |
+
diffusion_x_sampling_steps: int = 0,
|
| 420 |
+
# ----------- Warm-Starting -----------
|
| 421 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 422 |
+
warm_start_forward_level: float = 0.3,
|
| 423 |
+
# ------------------ others ------------------ #
|
| 424 |
+
requires_grad: bool = False,
|
| 425 |
+
preserve_history: bool = False,
|
| 426 |
+
# ------------------ Data collection ------------------ #
|
| 427 |
+
collector=None,
|
| 428 |
+
collect_trajectory: bool = False,
|
| 429 |
+
obs_dim: int = None,
|
| 430 |
+
initial_noise: Optional[torch.Tensor] = None,
|
| 431 |
+
return_initial_noise: bool = False,
|
| 432 |
+
**kwargs,
|
| 433 |
+
):
|
| 434 |
+
"""Sampling.
|
| 435 |
+
|
| 436 |
+
Inputs:
|
| 437 |
+
- prior: torch.Tensor
|
| 438 |
+
The known fixed portion of the input data. Should be in the shape of generated data.
|
| 439 |
+
Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
|
| 440 |
+
|
| 441 |
+
- solver: str
|
| 442 |
+
The solver for the reverse process. Check `supported_solvers` property for available solvers.
|
| 443 |
+
- n_samples: int
|
| 444 |
+
The number of samples to generate.
|
| 445 |
+
- sample_steps: int
|
| 446 |
+
The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
|
| 447 |
+
- sample_step_schedule: Union[str, Callable]
|
| 448 |
+
The schedule for the sampling steps.
|
| 449 |
+
- use_ema: bool
|
| 450 |
+
Whether to use the exponential moving average model.
|
| 451 |
+
- temperature: float
|
| 452 |
+
The temperature for sampling.
|
| 453 |
+
- initial_noise: Optional[torch.Tensor]
|
| 454 |
+
Optional standard-normal noise used to initialize the reverse process.
|
| 455 |
+
It is multiplied by ``temperature`` before fixed conditioning is applied.
|
| 456 |
+
Supplying this makes noise/trajectory distillation pairs exactly reproducible.
|
| 457 |
+
- return_initial_noise: bool
|
| 458 |
+
If true, return the exact conditioned initial state as ``log["initial_xt"]``.
|
| 459 |
+
|
| 460 |
+
- condition_cfg: Optional
|
| 461 |
+
Condition for Classifier-free-guidance.
|
| 462 |
+
- mask_cfg: Optional
|
| 463 |
+
Mask for Classifier-guidance.
|
| 464 |
+
- w_cfg: float
|
| 465 |
+
Weight for Classifier-free-guidance.
|
| 466 |
+
- condition_cg: Optional
|
| 467 |
+
Condition for Classifier-guidance.
|
| 468 |
+
- w_cg: float
|
| 469 |
+
Weight for Classifier-guidance.
|
| 470 |
+
|
| 471 |
+
- diffusion_x_sampling_steps: int
|
| 472 |
+
The number of diffusion steps for diffusion-x sampling.
|
| 473 |
+
|
| 474 |
+
- warm_start_reference: Optional[torch.Tensor]
|
| 475 |
+
Reference data for warm-starting sampling. `None` indicates no warm-starting.
|
| 476 |
+
- warm_start_forward_level: float
|
| 477 |
+
The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
|
| 478 |
+
|
| 479 |
+
- requires_grad: bool
|
| 480 |
+
Whether to preserve gradients.
|
| 481 |
+
- preserve_history: bool
|
| 482 |
+
Whether to preserve the sampling history.
|
| 483 |
+
|
| 484 |
+
Outputs:
|
| 485 |
+
- x0: torch.Tensor
|
| 486 |
+
Generated samples. Be in the shape of `(n_samples, *x_shape)`.
|
| 487 |
+
- log: dict
|
| 488 |
+
The log dictionary.
|
| 489 |
+
"""
|
| 490 |
+
assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
|
| 491 |
+
|
| 492 |
+
# ===================== Initialization =====================
|
| 493 |
+
log = {
|
| 494 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 495 |
+
|
| 496 |
+
model = self.model if not use_ema else self.model_ema
|
| 497 |
+
|
| 498 |
+
prior = prior.to(self.device)
|
| 499 |
+
if isinstance(warm_start_reference, torch.Tensor) and isinstance(initial_noise, torch.Tensor):
|
| 500 |
+
raise ValueError("warm_start_reference and initial_noise are mutually exclusive")
|
| 501 |
+
if isinstance(warm_start_reference, torch.Tensor):
|
| 502 |
+
diffusion_steps = int(warm_start_forward_level * self.diffusion_steps)
|
| 503 |
+
fwd_alpha, fwd_sigma = self.alpha[diffusion_steps], self.sigma[diffusion_steps]
|
| 504 |
+
xt = warm_start_reference * fwd_alpha + fwd_sigma * torch.randn_like(warm_start_reference)
|
| 505 |
+
else:
|
| 506 |
+
diffusion_steps = self.diffusion_steps
|
| 507 |
+
if isinstance(initial_noise, torch.Tensor):
|
| 508 |
+
if initial_noise.shape != prior.shape:
|
| 509 |
+
raise ValueError(
|
| 510 |
+
f"initial_noise shape {tuple(initial_noise.shape)} does not match "
|
| 511 |
+
f"prior shape {tuple(prior.shape)}")
|
| 512 |
+
xt = initial_noise.to(device=prior.device, dtype=prior.dtype) * temperature
|
| 513 |
+
else:
|
| 514 |
+
xt = torch.randn_like(prior) * temperature
|
| 515 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 516 |
+
if return_initial_noise:
|
| 517 |
+
# Clone before denoising: later solver updates must not mutate the saved pair input.
|
| 518 |
+
log["initial_xt"] = xt.detach().clone()
|
| 519 |
+
if preserve_history:
|
| 520 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 521 |
+
|
| 522 |
+
with torch.set_grad_enabled(requires_grad):
|
| 523 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 524 |
+
condition_vec_cg = condition_cg
|
| 525 |
+
|
| 526 |
+
# ===================== Sampling Schedule ====================
|
| 527 |
+
if isinstance(sample_step_schedule, str):
|
| 528 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 529 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 530 |
+
diffusion_steps, sample_steps)
|
| 531 |
+
else:
|
| 532 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 533 |
+
elif callable(sample_step_schedule):
|
| 534 |
+
sample_step_schedule = sample_step_schedule(diffusion_steps, sample_steps)
|
| 535 |
+
else:
|
| 536 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 537 |
+
|
| 538 |
+
alphas = self.alpha[sample_step_schedule]
|
| 539 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 540 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 541 |
+
hs = torch.zeros_like(logSNRs)
|
| 542 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 543 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 544 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 545 |
+
|
| 546 |
+
buffer = []
|
| 547 |
+
|
| 548 |
+
# ===================== Denoising Loop ========================
|
| 549 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
|
| 550 |
+
for i in reversed(loop_steps):
|
| 551 |
+
|
| 552 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 553 |
+
|
| 554 |
+
xt_before = xt
|
| 555 |
+
|
| 556 |
+
# guided sampling
|
| 557 |
+
pred, logp = self.guided_sampling(
|
| 558 |
+
xt, t, alphas[i], sigmas[i],
|
| 559 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 560 |
+
|
| 561 |
+
# clip the prediction
|
| 562 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 563 |
+
|
| 564 |
+
# noise & data prediction
|
| 565 |
+
eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
|
| 566 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 567 |
+
|
| 568 |
+
# one-step update
|
| 569 |
+
if solver == "ddpm":
|
| 570 |
+
xt = (
|
| 571 |
+
(alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
|
| 572 |
+
(sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
|
| 573 |
+
if i > 1:
|
| 574 |
+
xt += (stds[i] * torch.randn_like(xt))
|
| 575 |
+
|
| 576 |
+
elif solver == "ddim":
|
| 577 |
+
xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
|
| 578 |
+
|
| 579 |
+
elif solver == "ode_dpmsolver_1":
|
| 580 |
+
xt = (alphas[i - 1] / alphas[i]) * xt - sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta
|
| 581 |
+
|
| 582 |
+
elif solver == "ode_dpmsolver++_1":
|
| 583 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 584 |
+
|
| 585 |
+
elif solver == "ode_dpmsolver++_2M":
|
| 586 |
+
buffer.append(x_theta)
|
| 587 |
+
if i < sample_steps:
|
| 588 |
+
r = hs[i + 1] / hs[i]
|
| 589 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 590 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
|
| 591 |
+
else:
|
| 592 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 593 |
+
|
| 594 |
+
elif solver == "sde_dpmsolver_1":
|
| 595 |
+
xt = ((alphas[i - 1] / alphas[i]) * xt -
|
| 596 |
+
2 * sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta +
|
| 597 |
+
sigmas[i - 1] * torch.expm1(2 * hs[i]).sqrt() * torch.randn_like(xt))
|
| 598 |
+
|
| 599 |
+
elif solver == "sde_dpmsolver++_1":
|
| 600 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 601 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 602 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 603 |
+
|
| 604 |
+
elif solver == "sde_dpmsolver++_2M":
|
| 605 |
+
buffer.append(x_theta)
|
| 606 |
+
if i < sample_steps:
|
| 607 |
+
r = hs[i + 1] / hs[i]
|
| 608 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 609 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 610 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * D +
|
| 611 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 612 |
+
else:
|
| 613 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 614 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 615 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 616 |
+
|
| 617 |
+
# fix the known portion, and preserve the sampling history
|
| 618 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 619 |
+
#
|
| 620 |
+
xt_after = xt
|
| 621 |
+
|
| 622 |
+
# === 轨迹采集(关键)===
|
| 623 |
+
if collect_trajectory and collector is not None:
|
| 624 |
+
assert obs_dim is not None, "collect_trajectory 需要传入 obs_dim"
|
| 625 |
+
u = prior[:, 0, :obs_dim] # u = obs0(归一化之后)
|
| 626 |
+
collector.add(xt_before, xt_after, t, u, i_step=i)
|
| 627 |
+
|
| 628 |
+
if preserve_history:
|
| 629 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 630 |
+
|
| 631 |
+
# ================= Post-processing =================
|
| 632 |
+
if self.classifier is not None:
|
| 633 |
+
with torch.no_grad():
|
| 634 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 635 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 636 |
+
log["log_p"] = logp
|
| 637 |
+
|
| 638 |
+
if self.clip_pred:
|
| 639 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 640 |
+
|
| 641 |
+
return xt, log
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
class ContinuousDiffusionSDE(BaseDiffusionSDE):
|
| 645 |
+
"""Continuous-time Diffusion SDE (VP-SDE)
|
| 646 |
+
|
| 647 |
+
The Diffusion SDE is currently one of the most commonly used formulations of diffusion processes.
|
| 648 |
+
Its training process involves utilizing neural networks to estimate its scaled score function,
|
| 649 |
+
which is used to compute the reverse process. The Diffusion SDE has reverse processes
|
| 650 |
+
in both SDE and ODE forms, sharing the same marginal distribution.
|
| 651 |
+
The first-order discretized forms of both are equivalent to well-known models such as DDPM and DDIM.
|
| 652 |
+
DPM-Solvers have observed the semi-linearity of the reverse process and have computed its exact solution.
|
| 653 |
+
|
| 654 |
+
The ContinuousDiffusionSDE is the continuous-time version of the Diffusion SDE.
|
| 655 |
+
It estimates the score function at any $t\in[0,T]$ and solves the reverse process in continuous time.
|
| 656 |
+
The sampling steps are required to be greater than 1.
|
| 657 |
+
|
| 658 |
+
Args:
|
| 659 |
+
- nn_diffusion: BaseNNDiffusion
|
| 660 |
+
The neural network backbone for the Diffusion model.
|
| 661 |
+
- nn_condition: Optional[BaseNNCondition]
|
| 662 |
+
The neural network backbone for the condition embedding.
|
| 663 |
+
|
| 664 |
+
- fix_mask: Union[list, np.ndarray, torch.Tensor]
|
| 665 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 666 |
+
The mask should be in the shape of `x_shape`.
|
| 667 |
+
- loss_weight: Union[list, np.ndarray, torch.Tensor]
|
| 668 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 669 |
+
|
| 670 |
+
- classifier: Optional[BaseClassifier]
|
| 671 |
+
Add a classifier to enable classifier-guidance.
|
| 672 |
+
|
| 673 |
+
- grad_clip_norm: Optional[float]
|
| 674 |
+
Gradient clipping norm.
|
| 675 |
+
- ema_rate: float
|
| 676 |
+
Exponential moving average rate.
|
| 677 |
+
- optim_params: Optional[dict]
|
| 678 |
+
Optimizer parameters.
|
| 679 |
+
|
| 680 |
+
- epsilon: float
|
| 681 |
+
The minimum time step for the diffusion reverse process.
|
| 682 |
+
In practice, using a very small value instead of `0` can avoid numerical instability.
|
| 683 |
+
|
| 684 |
+
- noise_schedule: Union[str, Dict[str, Callable]]
|
| 685 |
+
The noise schedule for the diffusion process. Can be "linear" or "cosine".
|
| 686 |
+
- noise_schedule_params: Optional[dict]
|
| 687 |
+
The parameters for the noise schedule.
|
| 688 |
+
|
| 689 |
+
- x_max: Optional[torch.Tensor]
|
| 690 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 691 |
+
- x_min: Optional[torch.Tensor]
|
| 692 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 693 |
+
|
| 694 |
+
- predict_noise: bool
|
| 695 |
+
Whether to predict the noise or the data.
|
| 696 |
+
|
| 697 |
+
- device: Union[torch.device, str]
|
| 698 |
+
The device to run the model.
|
| 699 |
+
"""
|
| 700 |
+
def __init__(
|
| 701 |
+
self,
|
| 702 |
+
|
| 703 |
+
# ----------------- Neural Networks ----------------- #
|
| 704 |
+
nn_diffusion: BaseNNDiffusion,
|
| 705 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 706 |
+
|
| 707 |
+
# ----------------- Masks ----------------- #
|
| 708 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 709 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 710 |
+
# Add loss weight
|
| 711 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 712 |
+
|
| 713 |
+
# ------------------ Plugins ---------------- #
|
| 714 |
+
# Add a classifier to enable classifier-guidance
|
| 715 |
+
classifier: Optional[BaseClassifier] = None,
|
| 716 |
+
|
| 717 |
+
# ------------------ Training Params ---------------- #
|
| 718 |
+
grad_clip_norm: Optional[float] = None,
|
| 719 |
+
ema_rate: float = 0.995,
|
| 720 |
+
optim_params: Optional[dict] = None,
|
| 721 |
+
|
| 722 |
+
# ------------------- Diffusion Params ------------------- #
|
| 723 |
+
epsilon: float = 1e-3,
|
| 724 |
+
|
| 725 |
+
noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
|
| 726 |
+
noise_schedule_params: Optional[dict] = None,
|
| 727 |
+
|
| 728 |
+
x_max: Optional[torch.Tensor] = None,
|
| 729 |
+
x_min: Optional[torch.Tensor] = None,
|
| 730 |
+
|
| 731 |
+
predict_noise: bool = True,
|
| 732 |
+
|
| 733 |
+
device: Union[torch.device, str] = "cpu"
|
| 734 |
+
):
|
| 735 |
+
super().__init__(
|
| 736 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm, ema_rate, optim_params,
|
| 737 |
+
epsilon, noise_schedule, noise_schedule_params, x_max, x_min, predict_noise, device)
|
| 738 |
+
|
| 739 |
+
# ==================== Continuous Time-step Range ====================
|
| 740 |
+
if noise_schedule == "cosine":
|
| 741 |
+
self.t_diffusion = [epsilon, 0.9946]
|
| 742 |
+
else:
|
| 743 |
+
self.t_diffusion = [epsilon, 1.]
|
| 744 |
+
|
| 745 |
+
# ===================== Noise Schedule ======================
|
| 746 |
+
if isinstance(noise_schedule, str):
|
| 747 |
+
if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
|
| 748 |
+
self.noise_schedule_funcs = SUPPORTED_NOISE_SCHEDULES[noise_schedule]
|
| 749 |
+
self.noise_schedule_params = noise_schedule_params
|
| 750 |
+
else:
|
| 751 |
+
raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
|
| 752 |
+
elif isinstance(noise_schedule, dict):
|
| 753 |
+
self.noise_schedule_funcs = noise_schedule
|
| 754 |
+
self.noise_schedule_params = noise_schedule_params
|
| 755 |
+
else:
|
| 756 |
+
raise ValueError("noise_schedule must be a callable or a string")
|
| 757 |
+
|
| 758 |
+
# ==================== Training: Score Matching ======================
|
| 759 |
+
|
| 760 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 761 |
+
|
| 762 |
+
t = (torch.rand((x0.shape[0],), device=self.device) *
|
| 763 |
+
(self.t_diffusion[1] - self.t_diffusion[0]) + self.t_diffusion[0]) if t is None else t
|
| 764 |
+
|
| 765 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 766 |
+
|
| 767 |
+
alpha, sigma = self.noise_schedule_funcs["forward"](t, **(self.noise_schedule_params or {}))
|
| 768 |
+
alpha = at_least_ndim(alpha, x0.dim())
|
| 769 |
+
sigma = at_least_ndim(sigma, x0.dim())
|
| 770 |
+
|
| 771 |
+
xt = alpha * x0 + sigma * eps
|
| 772 |
+
xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
|
| 773 |
+
|
| 774 |
+
return xt, t, eps
|
| 775 |
+
|
| 776 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 777 |
+
|
| 778 |
+
def sample(
|
| 779 |
+
self,
|
| 780 |
+
# ---------- the known fixed portion ---------- #
|
| 781 |
+
prior: torch.Tensor,
|
| 782 |
+
# ----------------- sampling ----------------- #
|
| 783 |
+
solver: str = "ddpm",
|
| 784 |
+
n_samples: int = 1,
|
| 785 |
+
sample_steps: int = 5,
|
| 786 |
+
sample_step_schedule: Union[str, Callable] = "uniform_continuous",
|
| 787 |
+
use_ema: bool = True,
|
| 788 |
+
temperature: float = 1.0,
|
| 789 |
+
# ------------------ guidance ------------------ #
|
| 790 |
+
condition_cfg=None,
|
| 791 |
+
mask_cfg=None,
|
| 792 |
+
w_cfg: float = 0.0,
|
| 793 |
+
condition_cg=None,
|
| 794 |
+
w_cg: float = 0.0,
|
| 795 |
+
# ----------- Diffusion-X sampling ----------
|
| 796 |
+
diffusion_x_sampling_steps: int = 0,
|
| 797 |
+
# ----------- Warm-Starting -----------
|
| 798 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 799 |
+
warm_start_forward_level: float = 0.3,
|
| 800 |
+
# ------------------ others ------------------ #
|
| 801 |
+
requires_grad: bool = False,
|
| 802 |
+
preserve_history: bool = False,
|
| 803 |
+
**kwargs,
|
| 804 |
+
):
|
| 805 |
+
"""Sampling.
|
| 806 |
+
|
| 807 |
+
Inputs:
|
| 808 |
+
- prior: torch.Tensor
|
| 809 |
+
The known fixed portion of the input data. Should be in the shape of generated data.
|
| 810 |
+
Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
|
| 811 |
+
|
| 812 |
+
- solver: str
|
| 813 |
+
The solver for the reverse process. Check `supported_solvers` property for available solvers.
|
| 814 |
+
- n_samples: int
|
| 815 |
+
The number of samples to generate.
|
| 816 |
+
- sample_steps: int
|
| 817 |
+
The number of sampling steps. Should be greater than 1.
|
| 818 |
+
- sample_step_schedule: Union[str, Callable]
|
| 819 |
+
The schedule for the sampling steps.
|
| 820 |
+
- use_ema: bool
|
| 821 |
+
Whether to use the exponential moving average model.
|
| 822 |
+
- temperature: float
|
| 823 |
+
The temperature for sampling.
|
| 824 |
+
|
| 825 |
+
- condition_cfg: Optional
|
| 826 |
+
Condition for Classifier-free-guidance.
|
| 827 |
+
- mask_cfg: Optional
|
| 828 |
+
Mask for Classifier-guidance.
|
| 829 |
+
- w_cfg: float
|
| 830 |
+
Weight for Classifier-free-guidance.
|
| 831 |
+
- condition_cg: Optional
|
| 832 |
+
Condition for Classifier-guidance.
|
| 833 |
+
- w_cg: float
|
| 834 |
+
Weight for Classifier-guidance.
|
| 835 |
+
|
| 836 |
+
- diffusion_x_sampling_steps: int
|
| 837 |
+
The number of diffusion steps for diffusion-x sampling.
|
| 838 |
+
|
| 839 |
+
- warm_start_reference: Optional[torch.Tensor]
|
| 840 |
+
Reference data for warm-starting sampling. `None` indicates no warm-starting.
|
| 841 |
+
- warm_start_forward_level: float
|
| 842 |
+
The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
|
| 843 |
+
|
| 844 |
+
- requires_grad: bool
|
| 845 |
+
Whether to preserve gradients.
|
| 846 |
+
- preserve_history: bool
|
| 847 |
+
Whether to preserve the sampling history.
|
| 848 |
+
|
| 849 |
+
Outputs:
|
| 850 |
+
- x0: torch.Tensor
|
| 851 |
+
Generated samples. Be in the shape of `(n_samples, *x_shape)`.
|
| 852 |
+
- log: dict
|
| 853 |
+
The log dictionary.
|
| 854 |
+
"""
|
| 855 |
+
assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
|
| 856 |
+
|
| 857 |
+
# ===================== Initialization =====================
|
| 858 |
+
log = {
|
| 859 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 860 |
+
|
| 861 |
+
model = self.model if not use_ema else self.model_ema
|
| 862 |
+
|
| 863 |
+
prior = prior.to(self.device)
|
| 864 |
+
if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
|
| 865 |
+
warm_start_forward_level = self.epsilon + warm_start_forward_level * (1. - self.epsilon)
|
| 866 |
+
fwd_alpha, fwd_sigma = self.noise_schedule_funcs["forward"](
|
| 867 |
+
torch.ones((1,), device=self.device) * warm_start_forward_level, **(self.noise_schedule_params or {}))
|
| 868 |
+
xt = warm_start_reference * fwd_alpha + fwd_sigma * torch.randn_like(warm_start_reference)
|
| 869 |
+
else:
|
| 870 |
+
xt = torch.randn_like(prior) * temperature
|
| 871 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 872 |
+
if preserve_history:
|
| 873 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 874 |
+
|
| 875 |
+
with torch.set_grad_enabled(requires_grad):
|
| 876 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 877 |
+
condition_vec_cg = condition_cg
|
| 878 |
+
|
| 879 |
+
# ===================== Sampling Schedule ====================
|
| 880 |
+
if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
|
| 881 |
+
t_diffusion = [self.t_diffusion[0], warm_start_forward_level]
|
| 882 |
+
else:
|
| 883 |
+
t_diffusion = self.t_diffusion
|
| 884 |
+
if isinstance(sample_step_schedule, str):
|
| 885 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 886 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 887 |
+
t_diffusion, sample_steps)
|
| 888 |
+
else:
|
| 889 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 890 |
+
elif callable(sample_step_schedule):
|
| 891 |
+
sample_step_schedule = sample_step_schedule(t_diffusion, sample_steps)
|
| 892 |
+
else:
|
| 893 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 894 |
+
|
| 895 |
+
alphas, sigmas = self.noise_schedule_funcs["forward"](
|
| 896 |
+
sample_step_schedule, **(self.noise_schedule_params or {}))
|
| 897 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 898 |
+
hs = torch.zeros_like(logSNRs)
|
| 899 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 900 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 901 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 902 |
+
|
| 903 |
+
buffer = []
|
| 904 |
+
|
| 905 |
+
# ===================== Denoising Loop ========================
|
| 906 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
|
| 907 |
+
for i in reversed(loop_steps):
|
| 908 |
+
|
| 909 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
|
| 910 |
+
|
| 911 |
+
# guided sampling
|
| 912 |
+
pred, logp = self.guided_sampling(
|
| 913 |
+
xt, t, alphas[i], sigmas[i],
|
| 914 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 915 |
+
|
| 916 |
+
# clip the prediction
|
| 917 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 918 |
+
|
| 919 |
+
# transform to eps_theta
|
| 920 |
+
eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
|
| 921 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 922 |
+
|
| 923 |
+
# one-step update
|
| 924 |
+
if solver == "ddpm":
|
| 925 |
+
xt = (
|
| 926 |
+
(alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
|
| 927 |
+
(sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
|
| 928 |
+
if i > 1:
|
| 929 |
+
xt += (stds[i] * torch.randn_like(xt))
|
| 930 |
+
|
| 931 |
+
elif solver == "ddim":
|
| 932 |
+
xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
|
| 933 |
+
|
| 934 |
+
elif solver == "ode_dpmsolver_1":
|
| 935 |
+
xt = (alphas[i - 1] / alphas[i]) * xt - sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta
|
| 936 |
+
|
| 937 |
+
elif solver == "ode_dpmsolver++_1":
|
| 938 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 939 |
+
|
| 940 |
+
elif solver == "ode_dpmsolver++_2M":
|
| 941 |
+
buffer.append(x_theta)
|
| 942 |
+
if i < sample_steps:
|
| 943 |
+
r = hs[i + 1] / hs[i]
|
| 944 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 945 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
|
| 946 |
+
else:
|
| 947 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 948 |
+
|
| 949 |
+
elif solver == "sde_dpmsolver_1":
|
| 950 |
+
xt = ((alphas[i - 1] / alphas[i]) * xt -
|
| 951 |
+
2 * sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta +
|
| 952 |
+
sigmas[i - 1] * torch.expm1(2 * hs[i]).sqrt() * torch.randn_like(xt))
|
| 953 |
+
|
| 954 |
+
elif solver == "sde_dpmsolver++_1":
|
| 955 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 956 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 957 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 958 |
+
|
| 959 |
+
elif solver == "sde_dpmsolver++_2M":
|
| 960 |
+
buffer.append(x_theta)
|
| 961 |
+
if i < sample_steps:
|
| 962 |
+
r = hs[i + 1] / hs[i]
|
| 963 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 964 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 965 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * D +
|
| 966 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 967 |
+
else:
|
| 968 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 969 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 970 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 971 |
+
|
| 972 |
+
# fix the known portion, and preserve the sampling history
|
| 973 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 974 |
+
if preserve_history:
|
| 975 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 976 |
+
|
| 977 |
+
# ================= Post-processing =================
|
| 978 |
+
if self.classifier is not None and w_cg != 0.:
|
| 979 |
+
with torch.no_grad():
|
| 980 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 981 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 982 |
+
log["log_p"] = logp
|
| 983 |
+
|
| 984 |
+
if self.clip_pred:
|
| 985 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 986 |
+
|
| 987 |
+
return xt, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver.py
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import at_least_ndim
|
| 11 |
+
from .basic import DiffusionModel
|
| 12 |
+
|
| 13 |
+
SAMPLER_CONFIG = {
|
| 14 |
+
|
| 15 |
+
"ode_dpm_1": {
|
| 16 |
+
"predict_noise": True,
|
| 17 |
+
"order": 1, },
|
| 18 |
+
|
| 19 |
+
"ddim": {
|
| 20 |
+
"predict_noise": True,
|
| 21 |
+
"order": 1, },
|
| 22 |
+
|
| 23 |
+
"sde_dpm_1": {
|
| 24 |
+
"predict_noise": True,
|
| 25 |
+
"order": 1, },
|
| 26 |
+
|
| 27 |
+
"ode_dpmpp_1": {
|
| 28 |
+
"predict_noise": False,
|
| 29 |
+
"order": 1, },
|
| 30 |
+
|
| 31 |
+
"sde_dpmpp_1": {
|
| 32 |
+
"predict_noise": False,
|
| 33 |
+
"order": 1, },
|
| 34 |
+
|
| 35 |
+
"ode_dpm_2": {
|
| 36 |
+
"predict_noise": True,
|
| 37 |
+
"order": 2, },
|
| 38 |
+
|
| 39 |
+
"sde_dpm_2": {
|
| 40 |
+
"predict_noise": True,
|
| 41 |
+
"order": 2, },
|
| 42 |
+
|
| 43 |
+
"ode_dpmpp_2": {
|
| 44 |
+
"predict_noise": False,
|
| 45 |
+
"order": 2, },
|
| 46 |
+
|
| 47 |
+
"sde_dpmpp_2": {
|
| 48 |
+
"predict_noise": False,
|
| 49 |
+
"order": 2, }, }
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
|
| 53 |
+
"""
|
| 54 |
+
x_theta = (x - sigma * eps_theta) / alpha
|
| 55 |
+
"""
|
| 56 |
+
return (x - sigma * eps_theta) / alpha
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def xtheta_to_epstheta(x, alpha, sigma, x_theta):
|
| 60 |
+
"""
|
| 61 |
+
eps_theta = (x - alpha * x_theta) / sigma
|
| 62 |
+
"""
|
| 63 |
+
return (x - alpha * x_theta) / sigma
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler):
|
| 67 |
+
|
| 68 |
+
if sampler[:-2] == "ode_dpm" or sampler == "ddim":
|
| 69 |
+
xt_next = (
|
| 70 |
+
alphas[i] / alphas[i - 1] * xt -
|
| 71 |
+
sigmas[i] * torch.expm1(h[i]) * pred_theta)
|
| 72 |
+
elif sampler[:-2] == "sde_dpm":
|
| 73 |
+
xt_next = (
|
| 74 |
+
alphas[i] / alphas[i - 1] * xt -
|
| 75 |
+
2. * sigmas[i] * torch.expm1(h[i]) * pred_theta +
|
| 76 |
+
sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
|
| 77 |
+
elif sampler[:-2] == "ode_dpmpp":
|
| 78 |
+
xt_next = (
|
| 79 |
+
sigmas[i] / sigmas[i - 1] * xt -
|
| 80 |
+
alphas[i] * torch.expm1(-h[i]) * pred_theta)
|
| 81 |
+
elif sampler[:-2] == "sde_dpmpp":
|
| 82 |
+
xt_next = (
|
| 83 |
+
sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt -
|
| 84 |
+
alphas[i] * torch.expm1(-2. * h[i]) * pred_theta +
|
| 85 |
+
sigmas[i] * (-1. * torch.expm1(-2. * h[i])).sqrt() * torch.randn_like(xt))
|
| 86 |
+
else:
|
| 87 |
+
raise ValueError(f'Unknown sampler: {sampler}.')
|
| 88 |
+
|
| 89 |
+
return xt_next
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class DPMSolver(DiffusionModel):
|
| 93 |
+
|
| 94 |
+
def __init__(
|
| 95 |
+
self,
|
| 96 |
+
|
| 97 |
+
# ----------------- Neural Networks ----------------- #
|
| 98 |
+
nn_diffusion: BaseNNDiffusion,
|
| 99 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 100 |
+
|
| 101 |
+
# ----------------- Masks ----------------- #
|
| 102 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 103 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 104 |
+
# Add loss weight
|
| 105 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 106 |
+
|
| 107 |
+
# ------------------ Plugs ---------------- #
|
| 108 |
+
# Add a classifier to enable classifier-guidance
|
| 109 |
+
classifier: Optional[BaseClassifier] = None,
|
| 110 |
+
|
| 111 |
+
# ------------------ Params ---------------- #
|
| 112 |
+
grad_clip_norm: Optional[float] = None,
|
| 113 |
+
diffusion_steps: int = 1000,
|
| 114 |
+
ema_rate: float = 0.995,
|
| 115 |
+
optim_params: Optional[dict] = None,
|
| 116 |
+
x_max: Optional[torch.Tensor] = None,
|
| 117 |
+
x_min: Optional[torch.Tensor] = None,
|
| 118 |
+
|
| 119 |
+
# ------------------- DPM Params ------------------- #
|
| 120 |
+
predict_noise: bool = False,
|
| 121 |
+
noise_schedule: str = "linear", # or cosine
|
| 122 |
+
t_eps: float = 1e-3,
|
| 123 |
+
|
| 124 |
+
device: Union[torch.device, str] = "cpu"
|
| 125 |
+
):
|
| 126 |
+
super().__init__(
|
| 127 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 128 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 129 |
+
|
| 130 |
+
self.predict_noise = predict_noise
|
| 131 |
+
self.noise_schedule = noise_schedule
|
| 132 |
+
self.t_eps = t_eps
|
| 133 |
+
self.x_max, self.x_min = x_max, x_min
|
| 134 |
+
|
| 135 |
+
@property
|
| 136 |
+
def clip_pred(self):
|
| 137 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 138 |
+
|
| 139 |
+
@property
|
| 140 |
+
def supported_samplers(self):
|
| 141 |
+
return list(SAMPLER_CONFIG.keys())
|
| 142 |
+
|
| 143 |
+
@property
|
| 144 |
+
def t_range(self):
|
| 145 |
+
if self.noise_schedule == "linear":
|
| 146 |
+
return self.t_eps, 1.
|
| 147 |
+
elif self.noise_schedule == "cosine":
|
| 148 |
+
return self.t_eps, 0.9946
|
| 149 |
+
else:
|
| 150 |
+
raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
|
| 151 |
+
|
| 152 |
+
def alpha_schedule(self, t):
|
| 153 |
+
if self.noise_schedule == "linear":
|
| 154 |
+
beta0, beta1 = 0.1, 20
|
| 155 |
+
return (-(beta1 - beta0) / 4 * (t ** 2) - beta0 / 2 * t).exp()
|
| 156 |
+
elif self.noise_schedule == "cosine":
|
| 157 |
+
s = 0.008
|
| 158 |
+
return ((torch.cos(np.pi / 2 * (t + s) / (1 + s))).log() - np.log(np.cos(np.pi / 2 * s / (1 + s)))).exp()
|
| 159 |
+
else:
|
| 160 |
+
raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
|
| 161 |
+
|
| 162 |
+
# ---------------------------------------------------------------------------
|
| 163 |
+
# Training
|
| 164 |
+
|
| 165 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 166 |
+
if t is None:
|
| 167 |
+
t = torch.rand((x0.shape[0],), device=self.device)
|
| 168 |
+
t = self.t_range[0] + t * (self.t_range[1] - self.t_range[0])
|
| 169 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 170 |
+
alpha = self.alpha_schedule(at_least_ndim(t, x0.dim()))
|
| 171 |
+
sigma = (1 - alpha ** 2).sqrt()
|
| 172 |
+
xt = x0 * alpha + sigma * eps
|
| 173 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 174 |
+
return xt, t, eps
|
| 175 |
+
|
| 176 |
+
def loss(self, x0, condition=None):
|
| 177 |
+
xt, t, eps = self.add_noise(x0)
|
| 178 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 179 |
+
|
| 180 |
+
if self.predict_noise:
|
| 181 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 182 |
+
else:
|
| 183 |
+
loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
|
| 184 |
+
|
| 185 |
+
return (loss * self.loss_weight * (1. - self.fix_mask)).mean()
|
| 186 |
+
|
| 187 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 188 |
+
self.optimizer.zero_grad()
|
| 189 |
+
loss = self.loss(x0, condition)
|
| 190 |
+
loss.backward()
|
| 191 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 192 |
+
if self.grad_clip_norm else None
|
| 193 |
+
self.optimizer.step()
|
| 194 |
+
if update_ema: self.ema_update()
|
| 195 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 196 |
+
return log
|
| 197 |
+
|
| 198 |
+
def update_classifier(self, x0, condition):
|
| 199 |
+
xt, t, eps = self.add_noise(x0)
|
| 200 |
+
log = self.classifier.update(xt, t, condition)
|
| 201 |
+
return log
|
| 202 |
+
|
| 203 |
+
# ---------------------------------------------------------------------------
|
| 204 |
+
# Inference
|
| 205 |
+
|
| 206 |
+
def predict_function(
|
| 207 |
+
self, x, t, alpha, sigma,
|
| 208 |
+
use_ema=False, requires_grad=False, predict_noise=False,
|
| 209 |
+
# ----------------- CFG ----------------- #
|
| 210 |
+
condition_vec_cfg=None,
|
| 211 |
+
w_cfg: float = 0.0,
|
| 212 |
+
# ----------------- CG ----------------- #
|
| 213 |
+
condition_vec_cg=None,
|
| 214 |
+
w_cg: float = 1.0,
|
| 215 |
+
):
|
| 216 |
+
b = x.shape[0]
|
| 217 |
+
model = self.model_ema if use_ema else self.model
|
| 218 |
+
|
| 219 |
+
# ----------------- CFG ----------------- #
|
| 220 |
+
with torch.set_grad_enabled(requires_grad):
|
| 221 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 222 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
|
| 223 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 224 |
+
pred = model["diffusion"](
|
| 225 |
+
x.repeat(*repeat_dim), t.repeat(2), condition_vec_cfg)
|
| 226 |
+
pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
|
| 227 |
+
elif w_cfg == 0.0:
|
| 228 |
+
pred = model["diffusion"](x, t, None)
|
| 229 |
+
else:
|
| 230 |
+
pred = model["diffusion"](x, t, condition_vec_cfg)
|
| 231 |
+
|
| 232 |
+
if self.predict_noise and predict_noise:
|
| 233 |
+
# The network predicts noise, and we need to predict noise here
|
| 234 |
+
pass
|
| 235 |
+
elif self.predict_noise and not predict_noise:
|
| 236 |
+
# The network predicts noise, and we need to predict x0 here
|
| 237 |
+
pred = epstheta_to_xtheta(x, alpha, sigma, pred)
|
| 238 |
+
elif not self.predict_noise and predict_noise:
|
| 239 |
+
# The network predicts x0, and we need to predict noise here
|
| 240 |
+
pred = xtheta_to_epstheta(x, alpha, sigma, pred)
|
| 241 |
+
else:
|
| 242 |
+
# The network predicts x0, and we need to predict x0 here
|
| 243 |
+
pass
|
| 244 |
+
|
| 245 |
+
# ----------------- CG ----------------- #
|
| 246 |
+
if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
|
| 247 |
+
log_p, grad = self.classifier.gradients(x.clone(), t, condition_vec_cg)
|
| 248 |
+
if predict_noise:
|
| 249 |
+
pred = pred - w_cg * sigma * grad
|
| 250 |
+
else:
|
| 251 |
+
pred = pred + w_cg * ((sigma ** 2) / alpha) * grad
|
| 252 |
+
else:
|
| 253 |
+
log_p = None
|
| 254 |
+
|
| 255 |
+
# If the bounds are not None, clip `pred`
|
| 256 |
+
if predict_noise:
|
| 257 |
+
if self.clip_pred:
|
| 258 |
+
upper_bound = (x - alpha * self.x_min) / sigma if self.x_min is not None else None
|
| 259 |
+
lower_bound = (x - alpha * self.x_max) / sigma if self.x_max is not None else None
|
| 260 |
+
pred = pred.clip(lower_bound, upper_bound)
|
| 261 |
+
pred = pred * (1 - self.fix_mask)
|
| 262 |
+
else:
|
| 263 |
+
if self.clip_pred:
|
| 264 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 265 |
+
pred = pred * (1 - self.fix_mask) + x * self.fix_mask
|
| 266 |
+
|
| 267 |
+
return pred, {"log_p": log_p}
|
| 268 |
+
|
| 269 |
+
# def first_order_sample(
|
| 270 |
+
# self,
|
| 271 |
+
# sampler: str = "ode_dpm_1",
|
| 272 |
+
# # ---------- the known fixed portion ---------- #
|
| 273 |
+
# prior: Optional[torch.Tensor] = None,
|
| 274 |
+
# # ----------------- sampling ----------------- #
|
| 275 |
+
# n_samples: int = 1,
|
| 276 |
+
# sample_steps: int = None,
|
| 277 |
+
# use_ema: bool = True,
|
| 278 |
+
# kappa: float = 1.0,
|
| 279 |
+
# temperature: float = 1.0,
|
| 280 |
+
# # ------------------ guidance ------------------ #
|
| 281 |
+
# condition_cfg=None,
|
| 282 |
+
# mask_cfg=None,
|
| 283 |
+
# w_cfg: float = 0.0,
|
| 284 |
+
# condition_cg=None,
|
| 285 |
+
# w_cg: float = 0.0,
|
| 286 |
+
#
|
| 287 |
+
# requires_grad: bool = False,
|
| 288 |
+
# preserve_history: bool = False,
|
| 289 |
+
# ):
|
| 290 |
+
# log = {
|
| 291 |
+
# "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 292 |
+
#
|
| 293 |
+
# model = self.model_ema if use_ema else self.model
|
| 294 |
+
#
|
| 295 |
+
# idx = torch.arange(sample_steps + 1)
|
| 296 |
+
# t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 297 |
+
# idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 298 |
+
#
|
| 299 |
+
# alphas = self.alpha_schedule(t)
|
| 300 |
+
# sigmas = (1 - alphas ** 2).sqrt()
|
| 301 |
+
# logSNRs = (alphas / sigmas).log()
|
| 302 |
+
# h = torch.zeros_like(logSNRs)
|
| 303 |
+
# h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 304 |
+
#
|
| 305 |
+
# xt = torch.randn_like(prior, device=self.device) * temperature
|
| 306 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 307 |
+
# if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 308 |
+
#
|
| 309 |
+
# with torch.set_grad_enabled(requires_grad):
|
| 310 |
+
# condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 311 |
+
# condition_vec_cg = condition_cg
|
| 312 |
+
#
|
| 313 |
+
# for i in range(1, sample_steps + 1):
|
| 314 |
+
#
|
| 315 |
+
# pred_theta, log = self.predict_function(
|
| 316 |
+
# xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 317 |
+
# use_ema=use_ema,
|
| 318 |
+
# requires_grad=requires_grad,
|
| 319 |
+
# predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 320 |
+
# condition_vec_cfg=condition_vec_cfg,
|
| 321 |
+
# condition_vec_cg=condition_vec_cg,
|
| 322 |
+
# w_cfg=w_cfg, w_cg=w_cg)
|
| 323 |
+
#
|
| 324 |
+
# xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
|
| 325 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 326 |
+
# if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 327 |
+
#
|
| 328 |
+
# if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 329 |
+
# with torch.no_grad():
|
| 330 |
+
# logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 331 |
+
# log["log_p"] = logp
|
| 332 |
+
#
|
| 333 |
+
# return xt, log
|
| 334 |
+
#
|
| 335 |
+
# def second_order_sample_multistep(
|
| 336 |
+
# self,
|
| 337 |
+
# sampler: str = "ode_dpmpp_2",
|
| 338 |
+
# # ---------- the known fixed portion ---------- #
|
| 339 |
+
# prior: Optional[torch.Tensor] = None,
|
| 340 |
+
# # ----------------- sampling ----------------- #
|
| 341 |
+
# n_samples: int = 1,
|
| 342 |
+
# sample_steps: int = None,
|
| 343 |
+
# use_ema: bool = True,
|
| 344 |
+
# kappa: float = 1.0,
|
| 345 |
+
# temperature: float = 1.0,
|
| 346 |
+
# # ------------------ guidance ------------------ #
|
| 347 |
+
# condition_cfg=None,
|
| 348 |
+
# mask_cfg=None,
|
| 349 |
+
# w_cfg: float = 0.0,
|
| 350 |
+
# condition_cg=None,
|
| 351 |
+
# w_cg: float = 0.0,
|
| 352 |
+
#
|
| 353 |
+
# requires_grad: bool = False,
|
| 354 |
+
# preserve_history: bool = False,
|
| 355 |
+
# ):
|
| 356 |
+
# log = {
|
| 357 |
+
# "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 358 |
+
#
|
| 359 |
+
# model = self.model_ema if use_ema else self.model
|
| 360 |
+
#
|
| 361 |
+
# idx = torch.arange(sample_steps + 1)
|
| 362 |
+
# t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 363 |
+
# idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 364 |
+
#
|
| 365 |
+
# alphas = self.alpha_schedule(t)
|
| 366 |
+
# sigmas = (1 - alphas ** 2).sqrt()
|
| 367 |
+
# logSNRs = (alphas / sigmas).log()
|
| 368 |
+
# h = torch.zeros_like(logSNRs)
|
| 369 |
+
# h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 370 |
+
#
|
| 371 |
+
# buffer = []
|
| 372 |
+
#
|
| 373 |
+
# xt = torch.randn_like(prior, device=self.device) * temperature
|
| 374 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 375 |
+
# if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 376 |
+
#
|
| 377 |
+
# with torch.set_grad_enabled(requires_grad):
|
| 378 |
+
# condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 379 |
+
# condition_vec_cg = condition_cg
|
| 380 |
+
#
|
| 381 |
+
# # pre-update
|
| 382 |
+
#
|
| 383 |
+
# pred_theta, log = self.predict_function(
|
| 384 |
+
# xt, t[0].repeat(n_samples), alphas[0], sigmas[0],
|
| 385 |
+
# use_ema=use_ema,
|
| 386 |
+
# requires_grad=requires_grad,
|
| 387 |
+
# predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 388 |
+
# condition_vec_cfg=condition_vec_cfg,
|
| 389 |
+
# condition_vec_cg=condition_vec_cg,
|
| 390 |
+
# w_cfg=w_cfg, w_cg=w_cg)
|
| 391 |
+
# buffer.append(pred_theta.clone())
|
| 392 |
+
#
|
| 393 |
+
# xt = onestep_estimate(xt, pred_theta, 1, alphas, sigmas, h, sampler)
|
| 394 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 395 |
+
# if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 396 |
+
#
|
| 397 |
+
# pred_theta, log = self.predict_function(
|
| 398 |
+
# xt, t[1].repeat(n_samples), alphas[1], sigmas[1],
|
| 399 |
+
# use_ema=use_ema,
|
| 400 |
+
# requires_grad=requires_grad,
|
| 401 |
+
# predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 402 |
+
# condition_vec_cfg=condition_vec_cfg,
|
| 403 |
+
# condition_vec_cg=condition_vec_cg,
|
| 404 |
+
# w_cfg=w_cfg, w_cg=w_cg)
|
| 405 |
+
# buffer.append(pred_theta.clone())
|
| 406 |
+
#
|
| 407 |
+
# for i in range(2, sample_steps + 1):
|
| 408 |
+
#
|
| 409 |
+
# r = h[i - 1] / h[i]
|
| 410 |
+
#
|
| 411 |
+
# D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 412 |
+
#
|
| 413 |
+
# xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
|
| 414 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 415 |
+
# if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
|
| 416 |
+
#
|
| 417 |
+
# if i < sample_steps + 1:
|
| 418 |
+
# pred_theta, log = self.predict_function(
|
| 419 |
+
# xt, t[i].repeat(n_samples), alphas[i], sigmas[i],
|
| 420 |
+
# use_ema=use_ema,
|
| 421 |
+
# requires_grad=requires_grad,
|
| 422 |
+
# predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 423 |
+
# condition_vec_cfg=condition_vec_cfg,
|
| 424 |
+
# condition_vec_cg=condition_vec_cg,
|
| 425 |
+
# w_cfg=w_cfg, w_cg=w_cg)
|
| 426 |
+
# buffer.append(pred_theta.clone())
|
| 427 |
+
#
|
| 428 |
+
# if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 429 |
+
# with torch.no_grad():
|
| 430 |
+
# logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 431 |
+
# log["log_p"] = logp
|
| 432 |
+
#
|
| 433 |
+
# return xt, log
|
| 434 |
+
|
| 435 |
+
def sample(
|
| 436 |
+
self,
|
| 437 |
+
# ---------- the known fixed portion ---------- #
|
| 438 |
+
prior: Optional[torch.Tensor] = None,
|
| 439 |
+
# ----------------- sampling ----------------- #
|
| 440 |
+
n_samples: int = 1,
|
| 441 |
+
sample_steps: int = 5,
|
| 442 |
+
use_ema: bool = True,
|
| 443 |
+
temperature: float = 1.0,
|
| 444 |
+
kappa: float = 1.0,
|
| 445 |
+
sampler: str = "ddim",
|
| 446 |
+
# ------------------ guidance ------------------ #
|
| 447 |
+
condition_cfg=None,
|
| 448 |
+
mask_cfg=None,
|
| 449 |
+
w_cfg: float = 0.0,
|
| 450 |
+
condition_cg=None,
|
| 451 |
+
w_cg: float = 0.0,
|
| 452 |
+
# ------------------ others ------------------ #
|
| 453 |
+
requires_grad: bool = False,
|
| 454 |
+
preserve_history: bool = False,
|
| 455 |
+
**kwargs,
|
| 456 |
+
):
|
| 457 |
+
assert sampler in self.supported_samplers, f"Sampler '{sampler}' is not supported."
|
| 458 |
+
|
| 459 |
+
# initialize logger
|
| 460 |
+
log = {
|
| 461 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 462 |
+
|
| 463 |
+
# choose the model
|
| 464 |
+
model = self.model_ema if use_ema else self.model
|
| 465 |
+
|
| 466 |
+
# schedule the sampling steps
|
| 467 |
+
idx = torch.arange(sample_steps + 1)
|
| 468 |
+
t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 469 |
+
idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 470 |
+
|
| 471 |
+
alphas = self.alpha_schedule(t)
|
| 472 |
+
sigmas = (1 - alphas ** 2).sqrt()
|
| 473 |
+
logSNRs = (alphas / sigmas).log()
|
| 474 |
+
h = torch.zeros_like(logSNRs)
|
| 475 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 476 |
+
|
| 477 |
+
buffer = [] # only for 2nd order samplers
|
| 478 |
+
|
| 479 |
+
# initialize the samples
|
| 480 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 481 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 482 |
+
if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 483 |
+
|
| 484 |
+
# preprocess the conditions
|
| 485 |
+
with torch.set_grad_enabled(requires_grad):
|
| 486 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 487 |
+
condition_vec_cg = condition_cg
|
| 488 |
+
|
| 489 |
+
# enter the sampling loop
|
| 490 |
+
for i in range(1, sample_steps + 1):
|
| 491 |
+
|
| 492 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 493 |
+
pred_theta, log = self.predict_function(
|
| 494 |
+
xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 495 |
+
use_ema=use_ema,
|
| 496 |
+
requires_grad=requires_grad,
|
| 497 |
+
predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 498 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 499 |
+
condition_vec_cg=condition_vec_cg,
|
| 500 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 501 |
+
if SAMPLER_CONFIG[sampler]["order"] == 2: buffer.append(pred_theta.clone())
|
| 502 |
+
|
| 503 |
+
# one step denoise
|
| 504 |
+
if SAMPLER_CONFIG[sampler]["order"] == 2 and i > 1:
|
| 505 |
+
|
| 506 |
+
r = h[i - 1] / h[i]
|
| 507 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 508 |
+
|
| 509 |
+
xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
|
| 510 |
+
|
| 511 |
+
else:
|
| 512 |
+
|
| 513 |
+
xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
|
| 514 |
+
|
| 515 |
+
# fix the known portion, and preserve the sampling history
|
| 516 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 517 |
+
if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
|
| 518 |
+
|
| 519 |
+
# calculate the final log_p
|
| 520 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 521 |
+
with torch.no_grad():
|
| 522 |
+
logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 523 |
+
log["log_p"] = logp
|
| 524 |
+
|
| 525 |
+
# clip xt if bound is not None
|
| 526 |
+
if self.clip_pred:
|
| 527 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 528 |
+
|
| 529 |
+
return xt, log
|
| 530 |
+
|
| 531 |
+
def sample_x(
|
| 532 |
+
self,
|
| 533 |
+
# ---------- the known fixed portion ---------- #
|
| 534 |
+
prior: Optional[torch.Tensor] = None,
|
| 535 |
+
# ----------------- sampling ----------------- #
|
| 536 |
+
n_samples: int = 1,
|
| 537 |
+
sample_steps: int = 5,
|
| 538 |
+
extra_sample_steps: int = 8,
|
| 539 |
+
use_ema: bool = True,
|
| 540 |
+
temperature: float = 1.0,
|
| 541 |
+
kappa: float = 1.0,
|
| 542 |
+
sampler: str = "ddim",
|
| 543 |
+
# ------------------ guidance ------------------ #
|
| 544 |
+
condition_cfg=None,
|
| 545 |
+
mask_cfg=None,
|
| 546 |
+
w_cfg: float = 0.0,
|
| 547 |
+
condition_cg=None,
|
| 548 |
+
w_cg: float = 0.0,
|
| 549 |
+
# ------------------ others ------------------ #
|
| 550 |
+
requires_grad: bool = False,
|
| 551 |
+
preserve_history: bool = False,
|
| 552 |
+
**kwargs,
|
| 553 |
+
):
|
| 554 |
+
assert sampler in self.supported_samplers, f"Sampler '{sampler}' is not supported."
|
| 555 |
+
|
| 556 |
+
# initialize logger
|
| 557 |
+
log = {
|
| 558 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 559 |
+
|
| 560 |
+
# choose the model
|
| 561 |
+
model = self.model_ema if use_ema else self.model
|
| 562 |
+
|
| 563 |
+
# schedule the sampling steps
|
| 564 |
+
idx = torch.arange(sample_steps + 1)
|
| 565 |
+
t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 566 |
+
idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 567 |
+
|
| 568 |
+
alphas = self.alpha_schedule(t)
|
| 569 |
+
sigmas = (1 - alphas ** 2).sqrt()
|
| 570 |
+
logSNRs = (alphas / sigmas).log()
|
| 571 |
+
h = torch.zeros_like(logSNRs)
|
| 572 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 573 |
+
|
| 574 |
+
buffer = [] # only for 2nd order samplers
|
| 575 |
+
|
| 576 |
+
# initialize the samples
|
| 577 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 578 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 579 |
+
if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 580 |
+
|
| 581 |
+
# preprocess the conditions
|
| 582 |
+
with torch.set_grad_enabled(requires_grad):
|
| 583 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 584 |
+
condition_vec_cg = condition_cg
|
| 585 |
+
|
| 586 |
+
# enter the sampling loop
|
| 587 |
+
for i in range(1, sample_steps + 1):
|
| 588 |
+
|
| 589 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 590 |
+
pred_theta, log = self.predict_function(
|
| 591 |
+
xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 592 |
+
use_ema=use_ema,
|
| 593 |
+
requires_grad=requires_grad,
|
| 594 |
+
predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 595 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 596 |
+
condition_vec_cg=condition_vec_cg,
|
| 597 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 598 |
+
if SAMPLER_CONFIG[sampler]["order"] == 2: buffer.append(pred_theta.clone())
|
| 599 |
+
|
| 600 |
+
# one step denoise
|
| 601 |
+
if SAMPLER_CONFIG[sampler]["order"] == 2 and i > 1:
|
| 602 |
+
|
| 603 |
+
r = h[i - 1] / h[i]
|
| 604 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 605 |
+
|
| 606 |
+
xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
|
| 607 |
+
|
| 608 |
+
else:
|
| 609 |
+
|
| 610 |
+
xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
|
| 611 |
+
|
| 612 |
+
# fix the known portion, and preserve the sampling history
|
| 613 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 614 |
+
if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy() # TODO: BUG logging
|
| 615 |
+
|
| 616 |
+
# diffusion-X extra sampling steps, only for 1st order solvers
|
| 617 |
+
if SAMPLER_CONFIG[sampler]["order"] == 1 and extra_sample_steps > 0:
|
| 618 |
+
for i in range(extra_sample_steps):
|
| 619 |
+
|
| 620 |
+
# predict eps_theta or x_theta with CG/CFG
|
| 621 |
+
pred_theta, log = self.predict_function(
|
| 622 |
+
xt, t[sample_steps - 1].repeat(n_samples), alphas[sample_steps - 1], sigmas[sample_steps - 1],
|
| 623 |
+
use_ema=use_ema,
|
| 624 |
+
requires_grad=requires_grad,
|
| 625 |
+
predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
|
| 626 |
+
condition_vec_cfg=condition_vec_cfg,
|
| 627 |
+
condition_vec_cg=condition_vec_cg,
|
| 628 |
+
w_cfg=w_cfg, w_cg=w_cg)
|
| 629 |
+
|
| 630 |
+
xt = onestep_estimate(xt, pred_theta, sample_steps, alphas, sigmas, h, sampler)
|
| 631 |
+
|
| 632 |
+
# fix the known portion, and preserve the sampling history
|
| 633 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 634 |
+
|
| 635 |
+
# calculate the final log_p
|
| 636 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 637 |
+
with torch.no_grad():
|
| 638 |
+
logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 639 |
+
log["log_p"] = logp
|
| 640 |
+
|
| 641 |
+
# clip xt if bound is not None
|
| 642 |
+
if self.clip_pred:
|
| 643 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 644 |
+
|
| 645 |
+
return xt, log
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
# def sample_with_ode_dpm_1(
|
| 650 |
+
# self,
|
| 651 |
+
# # ---------- the known fixed portion ---------- #
|
| 652 |
+
# prior: Optional[torch.Tensor] = None,
|
| 653 |
+
# # ----------------- sampling ----------------- #
|
| 654 |
+
# n_samples: int = 1,
|
| 655 |
+
# sample_steps: int = None,
|
| 656 |
+
# use_ema: bool = True,
|
| 657 |
+
# kappa: float = 1.0,
|
| 658 |
+
# temperature: float = 1.0,
|
| 659 |
+
# # ------------------ guidance ------------------ #
|
| 660 |
+
# condition_cfg=None,
|
| 661 |
+
# mask_cfg=None,
|
| 662 |
+
# w_cfg: float = 0.0,
|
| 663 |
+
# condition_cg=None,
|
| 664 |
+
# w_cg: float = 0.0,
|
| 665 |
+
#
|
| 666 |
+
# preserve_history: bool = False,
|
| 667 |
+
# ):
|
| 668 |
+
# log = {
|
| 669 |
+
# "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 670 |
+
#
|
| 671 |
+
# model = self.model_ema if use_ema else self.model
|
| 672 |
+
#
|
| 673 |
+
# idx = torch.arange(sample_steps + 1)
|
| 674 |
+
# t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 675 |
+
# idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 676 |
+
#
|
| 677 |
+
# alphas = self.alpha_schedule(t)
|
| 678 |
+
# sigmas = (1 - alphas ** 2).sqrt()
|
| 679 |
+
# logSNRs = (alphas / sigmas).log()
|
| 680 |
+
# h = torch.zeros_like(logSNRs)
|
| 681 |
+
# h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 682 |
+
#
|
| 683 |
+
# xt = torch.randn_like(prior, device=self.device) * temperature
|
| 684 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 685 |
+
# if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 686 |
+
#
|
| 687 |
+
# with torch.no_grad():
|
| 688 |
+
# condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 689 |
+
# condition_vec_cg = condition_cg
|
| 690 |
+
#
|
| 691 |
+
# for i in range(1, sample_steps + 1):
|
| 692 |
+
#
|
| 693 |
+
# eps_theta, log = self.predict_eps_theta(
|
| 694 |
+
# xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 695 |
+
# use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 696 |
+
#
|
| 697 |
+
# xt = alphas[i] / alphas[i - 1] * xt - sigmas[i] * torch.expm1(h[i]) * eps_theta
|
| 698 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 699 |
+
#
|
| 700 |
+
# if preserve_history:
|
| 701 |
+
# x_history[:, i] = xt.cpu().numpy()
|
| 702 |
+
#
|
| 703 |
+
# log["sample_history"] = x_history
|
| 704 |
+
# if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 705 |
+
# with torch.no_grad():
|
| 706 |
+
# logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 707 |
+
# log["log_p"] = logp
|
| 708 |
+
#
|
| 709 |
+
# return xt, log
|
| 710 |
+
#
|
| 711 |
+
# def sample_with_sde_dpm_1(
|
| 712 |
+
# self,
|
| 713 |
+
# # ---------- the known fixed portion ---------- #
|
| 714 |
+
# prior: Optional[torch.Tensor] = None,
|
| 715 |
+
# # ----------------- sampling ----------------- #
|
| 716 |
+
# n_samples: int = 1,
|
| 717 |
+
# sample_steps: int = None,
|
| 718 |
+
# use_ema: bool = True,
|
| 719 |
+
# kappa: float = 1.0,
|
| 720 |
+
# temperature: float = 1.0,
|
| 721 |
+
# # ------------------ guidance ------------------ #
|
| 722 |
+
# condition_cfg=None,
|
| 723 |
+
# mask_cfg=None,
|
| 724 |
+
# w_cfg: float = 0.0,
|
| 725 |
+
# condition_cg=None,
|
| 726 |
+
# w_cg: float = 0.0,
|
| 727 |
+
#
|
| 728 |
+
# preserve_history: bool = False,
|
| 729 |
+
# ):
|
| 730 |
+
# log, x_history = {}, None
|
| 731 |
+
# model = self.model_ema if use_ema else self.model
|
| 732 |
+
#
|
| 733 |
+
# idx = torch.arange(sample_steps + 1)
|
| 734 |
+
# t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 735 |
+
# idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 736 |
+
#
|
| 737 |
+
# alphas = self.alpha_schedule(t)
|
| 738 |
+
# sigmas = (1 - alphas ** 2).sqrt()
|
| 739 |
+
# logSNRs = (alphas / sigmas).log()
|
| 740 |
+
# h = torch.zeros_like(logSNRs)
|
| 741 |
+
# h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 742 |
+
#
|
| 743 |
+
# if prior is None:
|
| 744 |
+
# xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 745 |
+
# else:
|
| 746 |
+
# xt = torch.randn_like(prior, device=self.device) * temperature
|
| 747 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 748 |
+
#
|
| 749 |
+
# if preserve_history:
|
| 750 |
+
# x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 751 |
+
# x_history[:, 0] = xt.cpu().numpy()
|
| 752 |
+
#
|
| 753 |
+
# with torch.no_grad():
|
| 754 |
+
# condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 755 |
+
# condition_vec_cg = condition_cg
|
| 756 |
+
#
|
| 757 |
+
# for i in range(1, sample_steps + 1):
|
| 758 |
+
#
|
| 759 |
+
# eps_theta, log = self.predict_eps_theta(
|
| 760 |
+
# xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 761 |
+
# use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 762 |
+
#
|
| 763 |
+
# xt = (
|
| 764 |
+
# alphas[i] / alphas[i - 1] * xt -
|
| 765 |
+
# 2. * sigmas[i] * torch.expm1(h[i]) * eps_theta +
|
| 766 |
+
# sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
|
| 767 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 768 |
+
#
|
| 769 |
+
# if preserve_history:
|
| 770 |
+
# x_history[:, i] = xt.cpu().numpy()
|
| 771 |
+
#
|
| 772 |
+
# log["sample_history"] = x_history
|
| 773 |
+
# if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 774 |
+
# with torch.no_grad():
|
| 775 |
+
# logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 776 |
+
# log["log_p"] = logp
|
| 777 |
+
#
|
| 778 |
+
# return xt, log
|
| 779 |
+
#
|
| 780 |
+
# def sample_with_sde_dpmpp_1(
|
| 781 |
+
# self,
|
| 782 |
+
# # ---------- the known fixed portion ---------- #
|
| 783 |
+
# prior: Optional[torch.Tensor] = None,
|
| 784 |
+
# # ----------------- sampling ----------------- #
|
| 785 |
+
# n_samples: int = 1,
|
| 786 |
+
# sample_steps: int = None,
|
| 787 |
+
# use_ema: bool = True,
|
| 788 |
+
# kappa: float = 1.0,
|
| 789 |
+
# temperature: float = 1.0,
|
| 790 |
+
# # ------------------ guidance ------------------ #
|
| 791 |
+
# condition_cfg=None,
|
| 792 |
+
# mask_cfg=None,
|
| 793 |
+
# w_cfg: float = 0.0,
|
| 794 |
+
# condition_cg=None,
|
| 795 |
+
# w_cg: float = 0.0,
|
| 796 |
+
#
|
| 797 |
+
# preserve_history: bool = False,
|
| 798 |
+
# ):
|
| 799 |
+
# log, x_history = {}, None
|
| 800 |
+
# model = self.model_ema if use_ema else self.model
|
| 801 |
+
#
|
| 802 |
+
# idx = torch.arange(sample_steps + 1)
|
| 803 |
+
# t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
|
| 804 |
+
# idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
|
| 805 |
+
#
|
| 806 |
+
# alphas = self.alpha_schedule(t)
|
| 807 |
+
# sigmas = (1 - alphas ** 2).sqrt()
|
| 808 |
+
# logSNRs = (alphas / sigmas).log()
|
| 809 |
+
# h = torch.zeros_like(logSNRs)
|
| 810 |
+
# h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 811 |
+
#
|
| 812 |
+
# if prior is None:
|
| 813 |
+
# xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 814 |
+
# else:
|
| 815 |
+
# xt = torch.randn_like(prior, device=self.device) * temperature
|
| 816 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 817 |
+
#
|
| 818 |
+
# if preserve_history:
|
| 819 |
+
# x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 820 |
+
# x_history[:, 0] = xt.cpu().numpy()
|
| 821 |
+
#
|
| 822 |
+
# with torch.no_grad():
|
| 823 |
+
# condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 824 |
+
# condition_vec_cg = condition_cg
|
| 825 |
+
#
|
| 826 |
+
# for i in range(1, sample_steps + 1):
|
| 827 |
+
#
|
| 828 |
+
# x_theta, log = self.predict_x_theta(
|
| 829 |
+
# xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 830 |
+
# use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 831 |
+
#
|
| 832 |
+
# xt = (
|
| 833 |
+
# sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt +
|
| 834 |
+
# alphas[i] * (1. - (-2. * h[i]).exp()) * x_theta +
|
| 835 |
+
# sigmas[i] * (1. - (-2. * h[i]).exp()).sqrt() * torch.randn_like(xt))
|
| 836 |
+
# xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 837 |
+
#
|
| 838 |
+
# if preserve_history:
|
| 839 |
+
# x_history[:, i] = xt.cpu().numpy()
|
| 840 |
+
#
|
| 841 |
+
# log["sample_history"] = x_history
|
| 842 |
+
# if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 843 |
+
# with torch.no_grad():
|
| 844 |
+
# logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
|
| 845 |
+
# log["log_p"] = logp
|
| 846 |
+
#
|
| 847 |
+
# return xt, log
|
| 848 |
+
|
| 849 |
+
# def sample_with_ode_dpm_2(
|
| 850 |
+
# self,
|
| 851 |
+
# # ---------- the known fixed portion ---------- #
|
| 852 |
+
# prior: Optional[torch.Tensor] = None,
|
| 853 |
+
# # ----------------- sampling ----------------- #
|
| 854 |
+
# n_samples: int = 1,
|
| 855 |
+
# sample_steps: int = None,
|
| 856 |
+
# use_ema: bool = True,
|
| 857 |
+
# kappa: float = 1.0,
|
| 858 |
+
# temperature: float = 1.0,
|
| 859 |
+
# # ------------------ guidance ------------------ #
|
| 860 |
+
# condition_cfg=None,
|
| 861 |
+
# mask_cfg=None,
|
| 862 |
+
# w_cfg: float = 0.0,
|
| 863 |
+
# condition_cg=None,
|
| 864 |
+
# w_cg: float = 0.0,
|
| 865 |
+
#
|
| 866 |
+
# preserve_history: bool = False,
|
| 867 |
+
# ):
|
| 868 |
+
# raise NotImplementedError
|
| 869 |
+
|
| 870 |
+
# def sample_with_ode_dpmpp_1(
|
| 871 |
+
# self,
|
| 872 |
+
# # ---------- the known fixed portion ---------- #
|
| 873 |
+
# prior: Optional[torch.Tensor] = None,
|
| 874 |
+
# # ----------------- sampling ----------------- #
|
| 875 |
+
# n_samples: int = 1,
|
| 876 |
+
# sample_steps: int = 5,
|
| 877 |
+
# use_ema: bool = True,
|
| 878 |
+
# kappa: float = 1.0,
|
| 879 |
+
# temperature: float = 1.0,
|
| 880 |
+
# # ------------------ guidance ------------------ #
|
| 881 |
+
# condition_cfg=None,
|
| 882 |
+
# mask_cfg=None,
|
| 883 |
+
# w_cfg: float = 0.0,
|
| 884 |
+
# condition_cg=None,
|
| 885 |
+
# w_cg: float = 0.0,
|
| 886 |
+
#
|
| 887 |
+
# preserve_history: bool = False,
|
| 888 |
+
# ):
|
| 889 |
+
# raise NotImplementedError
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver_discrete.py
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from typing import Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 9 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 10 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 11 |
+
from cleandiffuser.utils import at_least_ndim
|
| 12 |
+
from .basic import DiffusionModel
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
|
| 16 |
+
"""
|
| 17 |
+
x_theta = (x - sigma * eps_theta) / alpha
|
| 18 |
+
"""
|
| 19 |
+
return (x - sigma * eps_theta) / alpha
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def xtheta_to_epstheta(x, alpha, sigma, x_theta):
|
| 23 |
+
"""
|
| 24 |
+
eps_theta = (x - alpha * x_theta) / sigma
|
| 25 |
+
"""
|
| 26 |
+
return (x - alpha * x_theta) / sigma
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class DPMSolverDiscrete(DiffusionModel):
|
| 30 |
+
|
| 31 |
+
def __init__(
|
| 32 |
+
self,
|
| 33 |
+
|
| 34 |
+
# ----------------- Neural Networks ----------------- #
|
| 35 |
+
nn_diffusion: BaseNNDiffusion,
|
| 36 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 37 |
+
|
| 38 |
+
# ----------------- Masks ----------------- #
|
| 39 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 40 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 41 |
+
# Add loss weight
|
| 42 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 43 |
+
|
| 44 |
+
# ------------------ Plugs ---------------- #
|
| 45 |
+
# Add a classifier to enable classifier-guidance
|
| 46 |
+
classifier: Optional[BaseClassifier] = None,
|
| 47 |
+
|
| 48 |
+
# ------------------ Params ---------------- #
|
| 49 |
+
grad_clip_norm: Optional[float] = None,
|
| 50 |
+
diffusion_steps: int = 1000,
|
| 51 |
+
ema_rate: float = 0.995,
|
| 52 |
+
optim_params: Optional[dict] = None,
|
| 53 |
+
|
| 54 |
+
# ------------------- DPM Params ------------------- #
|
| 55 |
+
predict_noise: bool = False,
|
| 56 |
+
noise_schedule: str = "linear", # or cosine
|
| 57 |
+
t_eps: float = 1e-3,
|
| 58 |
+
|
| 59 |
+
device: Union[torch.device, str] = "cpu"
|
| 60 |
+
):
|
| 61 |
+
super().__init__(
|
| 62 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 63 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 64 |
+
|
| 65 |
+
self.predict_noise = predict_noise
|
| 66 |
+
self.noise_schedule = noise_schedule
|
| 67 |
+
self.t_eps = t_eps
|
| 68 |
+
|
| 69 |
+
if self.noise_schedule == "linear":
|
| 70 |
+
beta0, beta1 = 0.1, 20
|
| 71 |
+
self.t_cont = torch.linspace(t_eps, 1., diffusion_steps, device=device)
|
| 72 |
+
self.alphas = (-(beta1 - beta0) / 4 * (self.t_cont ** 2) - beta0 / 2 * self.t_cont).exp()
|
| 73 |
+
elif self.noise_schedule == "cosine":
|
| 74 |
+
s = 0.008
|
| 75 |
+
self.t_cont = torch.linspace(t_eps, 0.9946, diffusion_steps, device=device)
|
| 76 |
+
self.alphas = ((torch.cos(np.pi / 2 * (self.t_cont + s) / (1 + s))).log()
|
| 77 |
+
- np.log(np.cos(np.pi / 2 * s / (1 + s)))).exp()
|
| 78 |
+
else:
|
| 79 |
+
raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
|
| 80 |
+
|
| 81 |
+
self.sigmas = (1 - self.alphas ** 2).sqrt()
|
| 82 |
+
self.logSNRs = (self.alphas / self.sigmas).log()
|
| 83 |
+
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
# Training
|
| 86 |
+
|
| 87 |
+
def add_noise(self, x0, idx=None, eps=None):
|
| 88 |
+
if idx is None:
|
| 89 |
+
idx = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device)
|
| 90 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 91 |
+
alpha = at_least_ndim(self.alphas[idx], x0.dim())
|
| 92 |
+
sigma = at_least_ndim(self.sigmas[idx], x0.dim())
|
| 93 |
+
xt = x0 * alpha + sigma * eps
|
| 94 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 95 |
+
return xt, idx, eps
|
| 96 |
+
|
| 97 |
+
def loss(self, x0, condition=None):
|
| 98 |
+
xt, idx, eps = self.add_noise(x0)
|
| 99 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 100 |
+
|
| 101 |
+
if self.predict_noise:
|
| 102 |
+
loss = (self.model["diffusion"](xt, idx, condition) - eps) ** 2
|
| 103 |
+
else:
|
| 104 |
+
loss = (self.model["diffusion"](xt, idx, condition) - x0) ** 2
|
| 105 |
+
|
| 106 |
+
return (loss * self.loss_weight * (1. - self.fix_mask)).mean()
|
| 107 |
+
|
| 108 |
+
def update(self, x0, condition=None, **kwargs):
|
| 109 |
+
self.optimizer.zero_grad()
|
| 110 |
+
loss = self.loss(x0, condition)
|
| 111 |
+
loss.backward()
|
| 112 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 113 |
+
if self.grad_clip_norm else None
|
| 114 |
+
self.optimizer.step()
|
| 115 |
+
self.ema_update()
|
| 116 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 117 |
+
return log
|
| 118 |
+
|
| 119 |
+
def update_classifier(self, x0, condition):
|
| 120 |
+
xt, idx, eps = self.add_noise(x0)
|
| 121 |
+
log = self.classifier.update(xt, idx, condition)
|
| 122 |
+
return log
|
| 123 |
+
|
| 124 |
+
# ---------------------------------------------------------------------------
|
| 125 |
+
# Inference
|
| 126 |
+
|
| 127 |
+
def predict_eps_theta(
|
| 128 |
+
self, x, idx, alpha, sigma, use_ema=False,
|
| 129 |
+
# ----------------- CFG ----------------- #
|
| 130 |
+
condition_vec_cfg=None,
|
| 131 |
+
w_cfg: float = 0.0,
|
| 132 |
+
# ----------------- CG ----------------- #
|
| 133 |
+
condition_vec_cg=None,
|
| 134 |
+
w_cg: float = 1.0,
|
| 135 |
+
):
|
| 136 |
+
b = x.shape[0]
|
| 137 |
+
model = self.model_ema if use_ema else self.model
|
| 138 |
+
|
| 139 |
+
# ----------------- CFG ----------------- #
|
| 140 |
+
with torch.no_grad():
|
| 141 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 142 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
|
| 143 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 144 |
+
pred = model["diffusion"](
|
| 145 |
+
x.repeat(*repeat_dim), idx.repeat(2), condition_vec_cfg)
|
| 146 |
+
pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
|
| 147 |
+
else:
|
| 148 |
+
pred = model["diffusion"](x, idx, condition_vec_cfg)
|
| 149 |
+
|
| 150 |
+
if self.predict_noise:
|
| 151 |
+
eps_theta = pred
|
| 152 |
+
else:
|
| 153 |
+
eps_theta = xtheta_to_epstheta(x, alpha, sigma, pred)
|
| 154 |
+
|
| 155 |
+
# ----------------- CG ----------------- #
|
| 156 |
+
if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
|
| 157 |
+
log_p, grad = self.classifier.gradients(x.clone(), idx, condition_vec_cg)
|
| 158 |
+
eps_theta = eps_theta - w_cg * sigma * grad
|
| 159 |
+
else:
|
| 160 |
+
log_p = None
|
| 161 |
+
|
| 162 |
+
eps_theta = eps_theta * (1 - self.fix_mask)
|
| 163 |
+
|
| 164 |
+
return eps_theta, {"log_p": log_p}
|
| 165 |
+
|
| 166 |
+
def predict_x_theta(
|
| 167 |
+
self, x, idx, alpha, sigma, use_ema=False,
|
| 168 |
+
# ----------------- CFG ----------------- #
|
| 169 |
+
condition_vec_cfg=None,
|
| 170 |
+
w_cfg: float = 0.0,
|
| 171 |
+
# ----------------- CG ----------------- #
|
| 172 |
+
condition_vec_cg=None,
|
| 173 |
+
w_cg: float = 1.0,
|
| 174 |
+
):
|
| 175 |
+
b = x.shape[0]
|
| 176 |
+
model = self.model_ema if use_ema else self.model
|
| 177 |
+
|
| 178 |
+
# ----------------- CFG ----------------- #
|
| 179 |
+
with torch.no_grad():
|
| 180 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 181 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
|
| 182 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 183 |
+
pred = model["diffusion"](
|
| 184 |
+
x.repeat(*repeat_dim), idx.repeat(2), condition_vec_cfg)
|
| 185 |
+
pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
|
| 186 |
+
else:
|
| 187 |
+
pred = model["diffusion"](x, idx, condition_vec_cfg)
|
| 188 |
+
|
| 189 |
+
if self.predict_noise:
|
| 190 |
+
x_theta = epstheta_to_xtheta(x, alpha, sigma, pred)
|
| 191 |
+
else:
|
| 192 |
+
x_theta = pred
|
| 193 |
+
|
| 194 |
+
# ----------------- CG ----------------- #
|
| 195 |
+
if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
|
| 196 |
+
log_p, grad = self.classifier.gradients(x.clone(), idx, condition_vec_cg)
|
| 197 |
+
x_theta = x_theta + w_cg * ((sigma ** 2) / alpha) * grad
|
| 198 |
+
else:
|
| 199 |
+
log_p = None
|
| 200 |
+
|
| 201 |
+
x_theta = x_theta * (1 - self.fix_mask) + x * self.fix_mask
|
| 202 |
+
|
| 203 |
+
return x_theta, {"log_p": log_p}
|
| 204 |
+
|
| 205 |
+
def sample_with_ode_dpm_1(
|
| 206 |
+
self,
|
| 207 |
+
# ---------- the known fixed portion ---------- #
|
| 208 |
+
prior: Optional[torch.Tensor] = None,
|
| 209 |
+
# ----------------- sampling ----------------- #
|
| 210 |
+
n_samples: int = 1,
|
| 211 |
+
sample_steps: int = None,
|
| 212 |
+
use_ema: bool = True,
|
| 213 |
+
kappa: float = 1.0,
|
| 214 |
+
temperature: float = 1.0,
|
| 215 |
+
# ------------------ guidance ------------------ #
|
| 216 |
+
condition_cfg=None,
|
| 217 |
+
mask_cfg=None,
|
| 218 |
+
w_cfg: float = 0.0,
|
| 219 |
+
condition_cg=None,
|
| 220 |
+
w_cg: float = 0.0,
|
| 221 |
+
|
| 222 |
+
preserve_history: bool = False,
|
| 223 |
+
):
|
| 224 |
+
log, x_history = {}, None
|
| 225 |
+
model = self.model_ema if use_ema else self.model
|
| 226 |
+
|
| 227 |
+
idx = torch.arange(sample_steps + 1)
|
| 228 |
+
t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
|
| 229 |
+
idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
|
| 230 |
+
t_sample = (torch.max(t_cond-1/self.diffusion_steps, torch.zeros_like(t_cond))*self.diffusion_steps).long()
|
| 231 |
+
|
| 232 |
+
alphas = self.alphas[t_sample]
|
| 233 |
+
sigmas = self.sigmas[t_sample]
|
| 234 |
+
logSNRs = self.logSNRs[t_sample]
|
| 235 |
+
h = torch.zeros_like(logSNRs)
|
| 236 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 237 |
+
|
| 238 |
+
if prior is None:
|
| 239 |
+
xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 240 |
+
else:
|
| 241 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 242 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 243 |
+
|
| 244 |
+
if preserve_history:
|
| 245 |
+
x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 246 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 247 |
+
|
| 248 |
+
with torch.no_grad():
|
| 249 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 250 |
+
condition_vec_cg = condition_cg
|
| 251 |
+
|
| 252 |
+
for i in range(1, sample_steps + 1):
|
| 253 |
+
|
| 254 |
+
eps_theta, log = self.predict_eps_theta(
|
| 255 |
+
xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 256 |
+
use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 257 |
+
|
| 258 |
+
xt = alphas[i] / alphas[i - 1] * xt - sigmas[i] * torch.expm1(h[i]) * eps_theta
|
| 259 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 260 |
+
|
| 261 |
+
if preserve_history:
|
| 262 |
+
x_history[:, i] = xt.cpu().numpy()
|
| 263 |
+
|
| 264 |
+
log["sample_history"] = x_history
|
| 265 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 266 |
+
with torch.no_grad():
|
| 267 |
+
logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
|
| 268 |
+
log["log_p"] = logp
|
| 269 |
+
|
| 270 |
+
return xt, log
|
| 271 |
+
|
| 272 |
+
def sample_with_sde_dpm_1(
|
| 273 |
+
self,
|
| 274 |
+
# ---------- the known fixed portion ---------- #
|
| 275 |
+
prior: Optional[torch.Tensor] = None,
|
| 276 |
+
# ----------------- sampling ----------------- #
|
| 277 |
+
n_samples: int = 1,
|
| 278 |
+
sample_steps: int = None,
|
| 279 |
+
use_ema: bool = True,
|
| 280 |
+
kappa: float = 1.0,
|
| 281 |
+
temperature: float = 1.0,
|
| 282 |
+
# ------------------ guidance ------------------ #
|
| 283 |
+
condition_cfg=None,
|
| 284 |
+
mask_cfg=None,
|
| 285 |
+
w_cfg: float = 0.0,
|
| 286 |
+
condition_cg=None,
|
| 287 |
+
w_cg: float = 0.0,
|
| 288 |
+
|
| 289 |
+
preserve_history: bool = False,
|
| 290 |
+
):
|
| 291 |
+
log, x_history = {}, None
|
| 292 |
+
model = self.model_ema if use_ema else self.model
|
| 293 |
+
|
| 294 |
+
idx = torch.arange(sample_steps + 1)
|
| 295 |
+
t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
|
| 296 |
+
idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
|
| 297 |
+
t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
|
| 298 |
+
torch.zeros_like(t_cond)) * self.diffusion_steps).long()
|
| 299 |
+
|
| 300 |
+
alphas = self.alphas[t_sample]
|
| 301 |
+
sigmas = self.sigmas[t_sample]
|
| 302 |
+
logSNRs = self.logSNRs[t_sample]
|
| 303 |
+
h = torch.zeros_like(logSNRs)
|
| 304 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 305 |
+
|
| 306 |
+
if prior is None:
|
| 307 |
+
xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 308 |
+
else:
|
| 309 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 310 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 311 |
+
|
| 312 |
+
if preserve_history:
|
| 313 |
+
x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 314 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 315 |
+
|
| 316 |
+
with torch.no_grad():
|
| 317 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 318 |
+
condition_vec_cg = condition_cg
|
| 319 |
+
|
| 320 |
+
for i in range(1, sample_steps + 1):
|
| 321 |
+
|
| 322 |
+
eps_theta, log = self.predict_eps_theta(
|
| 323 |
+
xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 324 |
+
use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 325 |
+
|
| 326 |
+
xt = (
|
| 327 |
+
alphas[i] / alphas[i - 1] * xt -
|
| 328 |
+
2. * sigmas[i] * torch.expm1(h[i]) * eps_theta +
|
| 329 |
+
sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
|
| 330 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 331 |
+
|
| 332 |
+
if preserve_history:
|
| 333 |
+
x_history[:, i] = xt.cpu().numpy()
|
| 334 |
+
|
| 335 |
+
log["sample_history"] = x_history
|
| 336 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 337 |
+
with torch.no_grad():
|
| 338 |
+
logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
|
| 339 |
+
log["log_p"] = logp
|
| 340 |
+
|
| 341 |
+
return xt, log
|
| 342 |
+
|
| 343 |
+
def sample_with_sde_dpmpp_1(
|
| 344 |
+
self,
|
| 345 |
+
# ---------- the known fixed portion ---------- #
|
| 346 |
+
prior: Optional[torch.Tensor] = None,
|
| 347 |
+
# ----------------- sampling ----------------- #
|
| 348 |
+
n_samples: int = 1,
|
| 349 |
+
sample_steps: int = None,
|
| 350 |
+
use_ema: bool = True,
|
| 351 |
+
kappa: float = 1.0,
|
| 352 |
+
temperature: float = 1.0,
|
| 353 |
+
# ------------------ guidance ------------------ #
|
| 354 |
+
condition_cfg=None,
|
| 355 |
+
mask_cfg=None,
|
| 356 |
+
w_cfg: float = 0.0,
|
| 357 |
+
condition_cg=None,
|
| 358 |
+
w_cg: float = 0.0,
|
| 359 |
+
|
| 360 |
+
preserve_history: bool = False,
|
| 361 |
+
):
|
| 362 |
+
log, x_history = {}, None
|
| 363 |
+
model = self.model_ema if use_ema else self.model
|
| 364 |
+
|
| 365 |
+
idx = torch.arange(sample_steps + 1)
|
| 366 |
+
t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
|
| 367 |
+
idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
|
| 368 |
+
t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
|
| 369 |
+
torch.zeros_like(t_cond)) * self.diffusion_steps).long()
|
| 370 |
+
|
| 371 |
+
alphas = self.alphas[t_sample]
|
| 372 |
+
sigmas = self.sigmas[t_sample]
|
| 373 |
+
logSNRs = self.logSNRs[t_sample]
|
| 374 |
+
h = torch.zeros_like(logSNRs)
|
| 375 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 376 |
+
|
| 377 |
+
if prior is None:
|
| 378 |
+
xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 379 |
+
else:
|
| 380 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 381 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 382 |
+
|
| 383 |
+
if preserve_history:
|
| 384 |
+
x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 385 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 386 |
+
|
| 387 |
+
with torch.no_grad():
|
| 388 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 389 |
+
condition_vec_cg = condition_cg
|
| 390 |
+
|
| 391 |
+
for i in range(1, sample_steps + 1):
|
| 392 |
+
|
| 393 |
+
x_theta, log = self.predict_x_theta(
|
| 394 |
+
xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
|
| 395 |
+
use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 396 |
+
|
| 397 |
+
xt = (
|
| 398 |
+
sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt +
|
| 399 |
+
alphas[i] * (1. - (-2. * h[i]).exp()) * x_theta +
|
| 400 |
+
sigmas[i] * (1. - (-2. * h[i]).exp()).sqrt() * torch.randn_like(xt))
|
| 401 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 402 |
+
|
| 403 |
+
if preserve_history:
|
| 404 |
+
x_history[:, i] = xt.cpu().numpy()
|
| 405 |
+
|
| 406 |
+
log["sample_history"] = x_history
|
| 407 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 408 |
+
with torch.no_grad():
|
| 409 |
+
logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
|
| 410 |
+
log["log_p"] = logp
|
| 411 |
+
|
| 412 |
+
return xt, log
|
| 413 |
+
|
| 414 |
+
def sample_with_ode_dpm_2(
|
| 415 |
+
self,
|
| 416 |
+
# ---------- the known fixed portion ---------- #
|
| 417 |
+
prior: Optional[torch.Tensor] = None,
|
| 418 |
+
# ----------------- sampling ----------------- #
|
| 419 |
+
n_samples: int = 1,
|
| 420 |
+
sample_steps: int = None,
|
| 421 |
+
use_ema: bool = True,
|
| 422 |
+
kappa: float = 1.0,
|
| 423 |
+
temperature: float = 1.0,
|
| 424 |
+
# ------------------ guidance ------------------ #
|
| 425 |
+
condition_cfg=None,
|
| 426 |
+
mask_cfg=None,
|
| 427 |
+
w_cfg: float = 0.0,
|
| 428 |
+
condition_cg=None,
|
| 429 |
+
w_cg: float = 0.0,
|
| 430 |
+
|
| 431 |
+
preserve_history: bool = False,
|
| 432 |
+
):
|
| 433 |
+
raise NotImplementedError
|
| 434 |
+
|
| 435 |
+
def sample_with_ode_dpmpp_1(
|
| 436 |
+
self,
|
| 437 |
+
# ---------- the known fixed portion ---------- #
|
| 438 |
+
prior: Optional[torch.Tensor] = None,
|
| 439 |
+
# ----------------- sampling ----------------- #
|
| 440 |
+
n_samples: int = 1,
|
| 441 |
+
sample_steps: int = 5,
|
| 442 |
+
use_ema: bool = True,
|
| 443 |
+
kappa: float = 1.0,
|
| 444 |
+
temperature: float = 1.0,
|
| 445 |
+
# ------------------ guidance ------------------ #
|
| 446 |
+
condition_cfg=None,
|
| 447 |
+
mask_cfg=None,
|
| 448 |
+
w_cfg: float = 0.0,
|
| 449 |
+
condition_cg=None,
|
| 450 |
+
w_cg: float = 0.0,
|
| 451 |
+
|
| 452 |
+
preserve_history: bool = False,
|
| 453 |
+
):
|
| 454 |
+
raise NotImplementedError
|
| 455 |
+
|
| 456 |
+
def sample_with_ode_dpmpp_2(
|
| 457 |
+
self,
|
| 458 |
+
# ---------- the known fixed portion ---------- #
|
| 459 |
+
prior: Optional[torch.Tensor] = None,
|
| 460 |
+
# ----------------- sampling ----------------- #
|
| 461 |
+
n_samples: int = 1,
|
| 462 |
+
sample_steps: int = 5,
|
| 463 |
+
use_ema: bool = True,
|
| 464 |
+
kappa: float = 1.0,
|
| 465 |
+
temperature: float = 1.0,
|
| 466 |
+
# ------------------ guidance ------------------ #
|
| 467 |
+
condition_cfg=None,
|
| 468 |
+
mask_cfg=None,
|
| 469 |
+
w_cfg: float = 0.0,
|
| 470 |
+
condition_cg=None,
|
| 471 |
+
w_cg: float = 0.0,
|
| 472 |
+
|
| 473 |
+
preserve_history: bool = False,
|
| 474 |
+
):
|
| 475 |
+
log, x_history = {}, None
|
| 476 |
+
model = self.model_ema if use_ema else self.model
|
| 477 |
+
|
| 478 |
+
idx = torch.arange(sample_steps + 1)
|
| 479 |
+
t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
|
| 480 |
+
idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
|
| 481 |
+
t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
|
| 482 |
+
torch.zeros_like(t_cond)) * self.diffusion_steps).long()
|
| 483 |
+
|
| 484 |
+
alphas = self.alphas[t_sample]
|
| 485 |
+
sigmas = self.sigmas[t_sample]
|
| 486 |
+
logSNRs = self.logSNRs[t_sample]
|
| 487 |
+
h = torch.zeros_like(logSNRs)
|
| 488 |
+
h[1:] = logSNRs[1:] - logSNRs[:-1]
|
| 489 |
+
buffer = []
|
| 490 |
+
|
| 491 |
+
if prior is None:
|
| 492 |
+
xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
|
| 493 |
+
else:
|
| 494 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 495 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 496 |
+
|
| 497 |
+
if preserve_history:
|
| 498 |
+
x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
|
| 499 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 500 |
+
|
| 501 |
+
with torch.no_grad():
|
| 502 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 503 |
+
condition_vec_cg = condition_cg
|
| 504 |
+
|
| 505 |
+
x_theta_t0, log = self.predict_x_theta(
|
| 506 |
+
xt, t_sample[0].repeat(n_samples), alphas[0], sigmas[0], use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 507 |
+
x_theta_t0 = x_theta_t0 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 508 |
+
buffer.append(x_theta_t0)
|
| 509 |
+
|
| 510 |
+
xt = sigmas[1] / sigmas[0] * xt - alphas[1] * torch.expm1(-h[1]) * buffer[-1]
|
| 511 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 512 |
+
if preserve_history:
|
| 513 |
+
x_history[:, 1] = xt.cpu().numpy()
|
| 514 |
+
|
| 515 |
+
x_theta_t1, log = self.predict_x_theta(
|
| 516 |
+
xt, t_sample[1].repeat(n_samples), alphas[1], sigmas[1], use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 517 |
+
x_theta_t1 = x_theta_t1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 518 |
+
buffer.append(x_theta_t1)
|
| 519 |
+
|
| 520 |
+
for i in range(2, sample_steps + 1):
|
| 521 |
+
|
| 522 |
+
r = h[i - 1] / h[i]
|
| 523 |
+
|
| 524 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 525 |
+
|
| 526 |
+
xt = sigmas[i] / sigmas[i - 1] * xt - alphas[i] * torch.expm1(-h[i]) * D
|
| 527 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 528 |
+
|
| 529 |
+
if i < sample_steps + 1:
|
| 530 |
+
x_theta_ti, log = self.predict_x_theta(
|
| 531 |
+
xt, t_sample[i].repeat(n_samples), alphas[i], sigmas[i],
|
| 532 |
+
use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
|
| 533 |
+
x_theta_ti = x_theta_ti * (1. - self.fix_mask) + prior * self.fix_mask
|
| 534 |
+
buffer.append(x_theta_ti.clone())
|
| 535 |
+
|
| 536 |
+
if preserve_history:
|
| 537 |
+
x_history[:, i] = xt.cpu().numpy()
|
| 538 |
+
|
| 539 |
+
log["sample_history"] = x_history
|
| 540 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 541 |
+
with torch.no_grad():
|
| 542 |
+
logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
|
| 543 |
+
log["log_p"] = logp
|
| 544 |
+
|
| 545 |
+
return xt, log
|
| 546 |
+
|
| 547 |
+
def sample(
|
| 548 |
+
self,
|
| 549 |
+
# ---------- the known fixed portion ---------- #
|
| 550 |
+
prior: Optional[torch.Tensor] = None,
|
| 551 |
+
# ----------------- sampling ----------------- #
|
| 552 |
+
n_samples: int = 1,
|
| 553 |
+
sample_steps: int = 5,
|
| 554 |
+
use_ema: bool = True,
|
| 555 |
+
kappa: float = 1.0,
|
| 556 |
+
temperature: float = 1.0,
|
| 557 |
+
solver: str = "dpmsolver++", # or ddim
|
| 558 |
+
# ------------------ guidance ------------------ #
|
| 559 |
+
condition_cfg=None,
|
| 560 |
+
mask_cfg=None,
|
| 561 |
+
w_cfg: float = 0.0,
|
| 562 |
+
condition_cg=None,
|
| 563 |
+
w_cg: float = 0.0,
|
| 564 |
+
|
| 565 |
+
preserve_history: bool = False,
|
| 566 |
+
):
|
| 567 |
+
if solver == "ode_dpm_1" or solver == "ddim":
|
| 568 |
+
return self.sample_with_ode_dpm_1(
|
| 569 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 570 |
+
condition_cg, w_cg,
|
| 571 |
+
preserve_history)
|
| 572 |
+
elif solver == "sde_dpm_1":
|
| 573 |
+
return self.sample_with_sde_dpm_1(
|
| 574 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 575 |
+
condition_cg, w_cg,
|
| 576 |
+
preserve_history)
|
| 577 |
+
elif solver == "sde_dpmpp_1":
|
| 578 |
+
return self.sample_with_sde_dpmpp_1(
|
| 579 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 580 |
+
condition_cg, w_cg,
|
| 581 |
+
preserve_history)
|
| 582 |
+
elif solver == "ode_dpm_2":
|
| 583 |
+
return self.sample_with_ode_dpm_2(
|
| 584 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 585 |
+
condition_cg, w_cg,
|
| 586 |
+
preserve_history)
|
| 587 |
+
elif solver == "ode_dpmpp_1":
|
| 588 |
+
return self.sample_with_ode_dpmpp_1(
|
| 589 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 590 |
+
condition_cg, w_cg,
|
| 591 |
+
preserve_history)
|
| 592 |
+
elif solver == "ode_dpmpp_2":
|
| 593 |
+
return self.sample_with_ode_dpmpp_2(
|
| 594 |
+
prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
|
| 595 |
+
condition_cg, w_cg,
|
| 596 |
+
preserve_history)
|
| 597 |
+
else:
|
| 598 |
+
raise ValueError(f"Solver: {solver} has not been implemented yet.")
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edm.py
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from typing import Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from cleandiffuser.utils import at_least_ndim
|
| 9 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 10 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 11 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 12 |
+
from .basic import DiffusionModel
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class EDMArchetecture(DiffusionModel):
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
|
| 20 |
+
# ----------------- Neural Networks ----------------- #
|
| 21 |
+
nn_diffusion: BaseNNDiffusion,
|
| 22 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 23 |
+
|
| 24 |
+
# ----------------- Masks ----------------- #
|
| 25 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 26 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 27 |
+
# Add loss weight
|
| 28 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 29 |
+
|
| 30 |
+
# ------------------ Plugs ---------------- #
|
| 31 |
+
# Add a classifier to enable classifier-guidance
|
| 32 |
+
classifier: Optional[BaseClassifier] = None,
|
| 33 |
+
|
| 34 |
+
# ------------------ Params ---------------- #
|
| 35 |
+
grad_clip_norm: Optional[float] = None,
|
| 36 |
+
diffusion_steps: int = 1000,
|
| 37 |
+
ema_rate: float = 0.995,
|
| 38 |
+
optim_params: Optional[dict] = None,
|
| 39 |
+
|
| 40 |
+
device: Union[torch.device, str] = "cpu"
|
| 41 |
+
):
|
| 42 |
+
super().__init__(
|
| 43 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 44 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 45 |
+
|
| 46 |
+
self.dot_scale_s = None
|
| 47 |
+
self.dot_sigma_s = None
|
| 48 |
+
self.scale_s = None
|
| 49 |
+
self.t_s = None
|
| 50 |
+
self.sigma_s = None
|
| 51 |
+
self.x_weight_s, self.D_weight_s = None, None
|
| 52 |
+
self.sample_steps = None
|
| 53 |
+
|
| 54 |
+
def set_sample_steps(self, N: int):
|
| 55 |
+
raise NotImplementedError
|
| 56 |
+
|
| 57 |
+
def c_skip(self, sigma):
|
| 58 |
+
raise NotImplementedError
|
| 59 |
+
|
| 60 |
+
def c_out(self, sigma):
|
| 61 |
+
raise NotImplementedError
|
| 62 |
+
|
| 63 |
+
def c_in(self, sigma):
|
| 64 |
+
raise NotImplementedError
|
| 65 |
+
|
| 66 |
+
def c_noise(self, sigma):
|
| 67 |
+
raise NotImplementedError
|
| 68 |
+
|
| 69 |
+
def loss_weighting(self, sigma):
|
| 70 |
+
raise NotImplementedError
|
| 71 |
+
|
| 72 |
+
def sample_noise_distribution(self, N):
|
| 73 |
+
raise NotImplementedError
|
| 74 |
+
|
| 75 |
+
def sample_scale_distribution(self, N):
|
| 76 |
+
raise NotImplementedError
|
| 77 |
+
|
| 78 |
+
def D(self, x, sigma, condition=None, use_ema=False):
|
| 79 |
+
""" Prepositioning in EDM """
|
| 80 |
+
c_skip, c_out, c_in, c_noise = self.c_skip(sigma), self.c_out(sigma), self.c_in(sigma), self.c_noise(sigma)
|
| 81 |
+
F = self.model_ema["diffusion"] if use_ema else self.model["diffusion"]
|
| 82 |
+
c_noise = at_least_ndim(c_noise.squeeze(), 1)
|
| 83 |
+
return c_skip * x + c_out * F(c_in * x, c_noise, condition)
|
| 84 |
+
|
| 85 |
+
# ---------------------------------------------------------------------------
|
| 86 |
+
# Training
|
| 87 |
+
|
| 88 |
+
def loss(self, x0, condition=None):
|
| 89 |
+
sigma = self.sample_noise_distribution(x0.shape[0])
|
| 90 |
+
sigma = at_least_ndim(sigma, x0.dim())
|
| 91 |
+
eps = torch.randn_like(x0) * sigma * (1. - self.fix_mask)
|
| 92 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 93 |
+
loss = (self.loss_weighting(sigma) * (self.D(x0 + eps, sigma, condition) - x0) ** 2)
|
| 94 |
+
return (loss * self.loss_weight).mean()
|
| 95 |
+
|
| 96 |
+
def update(self, x0, condition=None, **kwargs):
|
| 97 |
+
self.optimizer.zero_grad()
|
| 98 |
+
loss = self.loss(x0, condition)
|
| 99 |
+
loss.backward()
|
| 100 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 101 |
+
if self.grad_clip_norm else None
|
| 102 |
+
self.optimizer.step()
|
| 103 |
+
self.ema_update()
|
| 104 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 105 |
+
return log
|
| 106 |
+
|
| 107 |
+
def update_classifier(self, x0, condition):
|
| 108 |
+
sigma = self.sample_noise_distribution(x0.shape[0])
|
| 109 |
+
sigma = at_least_ndim(sigma, x0.dim())
|
| 110 |
+
noise = self.c_noise(sigma)
|
| 111 |
+
eps = torch.randn_like(x0) * sigma * (1. - self.fix_mask)
|
| 112 |
+
log = self.classifier.update(x0 + eps, at_least_ndim(noise.squeeze(), 1), condition)
|
| 113 |
+
return log
|
| 114 |
+
|
| 115 |
+
# ---------------------------------------------------------------------------
|
| 116 |
+
# Inference
|
| 117 |
+
|
| 118 |
+
def dot_x(
|
| 119 |
+
self, x, i, use_ema=False,
|
| 120 |
+
# ----------------- CFG ----------------- #
|
| 121 |
+
condition_vec_cfg=None,
|
| 122 |
+
w_cfg: float = 0.0,
|
| 123 |
+
# ----------------- CG ----------------- #
|
| 124 |
+
condition_vec_cg=None,
|
| 125 |
+
w_cg: float = 1.0,
|
| 126 |
+
):
|
| 127 |
+
b = x.shape[0]
|
| 128 |
+
sigma = at_least_ndim(self.sigma_s[i].repeat(b), x.dim())
|
| 129 |
+
noise = self.c_noise(sigma)
|
| 130 |
+
unscale = 1. / self.scale_s[i] * (1. - self.fix_mask) + self.fix_mask
|
| 131 |
+
# ----------------- CFG ----------------- #
|
| 132 |
+
with torch.no_grad():
|
| 133 |
+
if w_cfg != 0.0 and w_cfg != 1.0:
|
| 134 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
|
| 135 |
+
condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
|
| 136 |
+
D = self.D(
|
| 137 |
+
(x * unscale).repeat(*repeat_dim),
|
| 138 |
+
sigma.repeat(*repeat_dim),
|
| 139 |
+
condition_vec_cfg, use_ema)
|
| 140 |
+
D = w_cfg * D[:b] + (1. - w_cfg) * D[b:]
|
| 141 |
+
elif w_cfg == 0.0:
|
| 142 |
+
D = self.D(x * unscale, sigma, None, use_ema)
|
| 143 |
+
else:
|
| 144 |
+
D = self.D(x * unscale, sigma, condition_vec_cfg, use_ema)
|
| 145 |
+
# ----------------- CG ----------------- #
|
| 146 |
+
if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
|
| 147 |
+
log_p, grad = self.classifier.gradients(x * unscale,
|
| 148 |
+
at_least_ndim(noise.squeeze(), 1), condition_vec_cg)
|
| 149 |
+
D = D + w_cg * self.scale_s[i] * (sigma ** 2) * grad
|
| 150 |
+
else:
|
| 151 |
+
log_p = None
|
| 152 |
+
|
| 153 |
+
# do not change the fixed portion
|
| 154 |
+
dot_x = self.x_weight_s[i] * x - self.D_weight_s[i] * D
|
| 155 |
+
dot_x = dot_x * (1. - self.fix_mask)
|
| 156 |
+
|
| 157 |
+
return dot_x, {"log_p": log_p}
|
| 158 |
+
|
| 159 |
+
def sample(
|
| 160 |
+
self,
|
| 161 |
+
# ---------- the known fixed portion ---------- #
|
| 162 |
+
prior: Optional[torch.Tensor] = None,
|
| 163 |
+
# ----------------- sampling ----------------- #
|
| 164 |
+
n_samples: int = 1,
|
| 165 |
+
sample_steps: int = 5,
|
| 166 |
+
use_ema: bool = True,
|
| 167 |
+
solver: str = "euler",
|
| 168 |
+
# ------------------ guidance ------------------ #
|
| 169 |
+
condition_cfg=None,
|
| 170 |
+
mask_cfg=None,
|
| 171 |
+
w_cfg: float = 0.0,
|
| 172 |
+
condition_cg=None,
|
| 173 |
+
w_cg: float = 0.0,
|
| 174 |
+
|
| 175 |
+
preserve_history: bool = False,
|
| 176 |
+
**kwargs
|
| 177 |
+
):
|
| 178 |
+
"""
|
| 179 |
+
Sample from the diffusion model.
|
| 180 |
+
---
|
| 181 |
+
Input:
|
| 182 |
+
- prior: Optional[torch.Tensor] = None
|
| 183 |
+
The known fixed portion of the input data. Should be in the shape of `(n_samples, *x_shape)`.
|
| 184 |
+
Leave the unknown part as `0`. If `None`, which means `fix_mask` is `None`, the model takes no prior.
|
| 185 |
+
|
| 186 |
+
- sample_steps: int = 5
|
| 187 |
+
Number of sampling steps.
|
| 188 |
+
|
| 189 |
+
- use_ema: bool = True
|
| 190 |
+
Whether to use the EMA model. If `False`, you should `eval` the model.
|
| 191 |
+
|
| 192 |
+
- solver: str = "euler"
|
| 193 |
+
The solver to use. Can be either "euler" or "heun".
|
| 194 |
+
|
| 195 |
+
- condition_cfg: Optional[torch.Tensor] = None
|
| 196 |
+
The condition for the CFG. Should be in the shape of `(n_samples, *shape_of_nn_condition_input)`.
|
| 197 |
+
If `None`, the model takes no condition.
|
| 198 |
+
|
| 199 |
+
- mask_cfg: Optional[torch.Tensor] = None
|
| 200 |
+
The mask for the CFG. Should be in the shape of `(n_samples, *shape_of_nn_condition_mask)`.
|
| 201 |
+
Model will ignore the `mask_cfg==0` parts in `condition_cfg`. If `None`, the model takes no mask.
|
| 202 |
+
|
| 203 |
+
- w_cfg: float = 0.0
|
| 204 |
+
The weight for the CFG. If `0.0`, the model takes no CFG.
|
| 205 |
+
|
| 206 |
+
- condition_cg: Optional[torch.Tensor] = None
|
| 207 |
+
The condition for the CG. Should be in the shape of `(n_samples, 1)`.
|
| 208 |
+
If `None`, the model takes no condition.
|
| 209 |
+
|
| 210 |
+
- w_cg: float = 0.0
|
| 211 |
+
The weight for the CG. If `0.0`, the model takes no CG.
|
| 212 |
+
|
| 213 |
+
- preserve_history: bool = False
|
| 214 |
+
Whether to preserve the history of the sampling process. If `True`, the model will return the history.
|
| 215 |
+
|
| 216 |
+
Output:
|
| 217 |
+
- xt: torch.Tensor
|
| 218 |
+
The sampled data. Should be in the shape of `(n_samples, *x_shape)`.
|
| 219 |
+
|
| 220 |
+
- log: dict
|
| 221 |
+
The log of the sampling process. Contains the following keys:
|
| 222 |
+
- "sample_history": np.ndarray
|
| 223 |
+
The history of the sampling process. Should be in the shape of `(n_samples, N + 1, *x_shape)`.
|
| 224 |
+
If `preserve_history` is `False`, this key will not exist.
|
| 225 |
+
- "log_p": torch.Tensor
|
| 226 |
+
The log probability of the sampled data estimated by CG.
|
| 227 |
+
Should be in the shape of `(n_samples,)`.
|
| 228 |
+
"""
|
| 229 |
+
if sample_steps != self.sample_steps:
|
| 230 |
+
self.set_sample_steps(sample_steps)
|
| 231 |
+
|
| 232 |
+
N = self.sample_steps
|
| 233 |
+
model = self.model_ema if use_ema else self.model
|
| 234 |
+
log, x_history = {}, None
|
| 235 |
+
|
| 236 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 237 |
+
|
| 238 |
+
if prior is None:
|
| 239 |
+
xt = torch.randn((n_samples, *self.default_x_shape),
|
| 240 |
+
device=self.device) * self.sigma_s[0] * self.scale_s[0]
|
| 241 |
+
else:
|
| 242 |
+
xt = torch.randn_like(prior, device=self.device) * self.sigma_s[0] * self.scale_s[0]
|
| 243 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 244 |
+
|
| 245 |
+
if preserve_history:
|
| 246 |
+
x_history = np.empty((n_samples, N + 1, *xt.shape))
|
| 247 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 248 |
+
|
| 249 |
+
for i in range(N):
|
| 250 |
+
# tmp_w_cg = w_cg if i < 1 else 0.0
|
| 251 |
+
delta_x, log = self.dot_x(xt, i, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
|
| 252 |
+
delta_t = self.t_s[i] - self.t_s[i + 1]
|
| 253 |
+
x_tp1 = xt - delta_x * delta_t
|
| 254 |
+
if prior is not None:
|
| 255 |
+
x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 256 |
+
if solver == "heun":
|
| 257 |
+
if i != N - 1 and self.sigma_s[i + 1] > 0.005:
|
| 258 |
+
delta_x_2, log = self.dot_x(x_tp1, i + 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
|
| 259 |
+
x_tp1 = xt - (delta_x + delta_x_2) / 2. * delta_t
|
| 260 |
+
if prior is not None:
|
| 261 |
+
x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 262 |
+
|
| 263 |
+
xt = x_tp1
|
| 264 |
+
|
| 265 |
+
if preserve_history:
|
| 266 |
+
x_history[:, i + 1] = xt.cpu().numpy()
|
| 267 |
+
|
| 268 |
+
log["sample_history"] = x_history
|
| 269 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 270 |
+
with torch.no_grad():
|
| 271 |
+
logp = self.classifier.logp(
|
| 272 |
+
xt, at_least_ndim(self.c_noise(self.sigma_s[-1]).squeeze(), 1), condition_cg)
|
| 273 |
+
log["log_p"] = logp
|
| 274 |
+
|
| 275 |
+
return xt, log
|
| 276 |
+
|
| 277 |
+
def sample_x(
|
| 278 |
+
self,
|
| 279 |
+
# ---------- the known fixed portion ---------- #
|
| 280 |
+
prior: Optional[torch.Tensor] = None,
|
| 281 |
+
# ----------------- sampling ----------------- #
|
| 282 |
+
n_samples: int = 1,
|
| 283 |
+
sample_steps: int = 5,
|
| 284 |
+
extra_sample_steps: int = 8,
|
| 285 |
+
use_ema: bool = True,
|
| 286 |
+
solver: str = "euler",
|
| 287 |
+
# ------------------ guidance ------------------ #
|
| 288 |
+
condition_cfg=None,
|
| 289 |
+
mask_cfg=None,
|
| 290 |
+
w_cfg: float = 0.0,
|
| 291 |
+
condition_cg=None,
|
| 292 |
+
w_cg: float = 0.0,
|
| 293 |
+
|
| 294 |
+
preserve_history: bool = False,
|
| 295 |
+
):
|
| 296 |
+
if sample_steps != self.sample_steps:
|
| 297 |
+
self.set_sample_steps(sample_steps)
|
| 298 |
+
|
| 299 |
+
N = self.sample_steps
|
| 300 |
+
model = self.model_ema if use_ema else self.model
|
| 301 |
+
log, x_history = {}, None
|
| 302 |
+
|
| 303 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 304 |
+
|
| 305 |
+
if prior is None:
|
| 306 |
+
xt = torch.randn((n_samples, *self.default_x_shape),
|
| 307 |
+
device=self.device) * self.sigma_s[0] * self.scale_s[0]
|
| 308 |
+
else:
|
| 309 |
+
xt = torch.randn_like(prior, device=self.device) * self.sigma_s[0] * self.scale_s[0]
|
| 310 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 311 |
+
|
| 312 |
+
if preserve_history:
|
| 313 |
+
x_history = np.empty((n_samples, N + 1, *xt.shape))
|
| 314 |
+
x_history[:, 0] = xt.cpu().numpy()
|
| 315 |
+
|
| 316 |
+
for i in range(N):
|
| 317 |
+
# tmp_w_cg = w_cg if i < 1 else 0.0
|
| 318 |
+
delta_x, log = self.dot_x(xt, i, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
|
| 319 |
+
delta_t = self.t_s[i] - self.t_s[i + 1]
|
| 320 |
+
x_tp1 = xt - delta_x * delta_t
|
| 321 |
+
if prior is not None:
|
| 322 |
+
x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 323 |
+
if solver == "heun":
|
| 324 |
+
if i != N - 1 and self.sigma_s[i + 1] > 0.005:
|
| 325 |
+
delta_x_2, log = self.dot_x(x_tp1, i + 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
|
| 326 |
+
x_tp1 = xt - (delta_x + delta_x_2) / 2. * delta_t
|
| 327 |
+
if prior is not None:
|
| 328 |
+
x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 329 |
+
|
| 330 |
+
xt = x_tp1
|
| 331 |
+
|
| 332 |
+
if preserve_history:
|
| 333 |
+
x_history[:, i + 1] = xt.cpu().numpy()
|
| 334 |
+
|
| 335 |
+
if extra_sample_steps > 0:
|
| 336 |
+
|
| 337 |
+
delta_t = self.t_s[N - 1] - self.t_s[N]
|
| 338 |
+
|
| 339 |
+
for _ in range(extra_sample_steps):
|
| 340 |
+
|
| 341 |
+
delta_x, log = self.dot_x(xt, N - 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
|
| 342 |
+
x_tp1 = xt - delta_x * delta_t
|
| 343 |
+
if prior is not None:
|
| 344 |
+
x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
|
| 345 |
+
|
| 346 |
+
xt = x_tp1
|
| 347 |
+
|
| 348 |
+
log["sample_history"] = x_history
|
| 349 |
+
if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
|
| 350 |
+
with torch.no_grad():
|
| 351 |
+
logp = self.classifier.logp(
|
| 352 |
+
xt, at_least_ndim(self.c_noise(self.sigma_s[-1]).squeeze(), 1), condition_cg)
|
| 353 |
+
log["log_p"] = logp
|
| 354 |
+
|
| 355 |
+
return xt, log
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
class EDM(EDMArchetecture):
|
| 359 |
+
def __init__(
|
| 360 |
+
self,
|
| 361 |
+
|
| 362 |
+
# ----------------- Neural Networks ----------------- #
|
| 363 |
+
nn_diffusion: BaseNNDiffusion,
|
| 364 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 365 |
+
|
| 366 |
+
# ----------------- Masks ----------------- #
|
| 367 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 368 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 369 |
+
# Add loss weight
|
| 370 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 371 |
+
|
| 372 |
+
# ------------------ Plugs ---------------- #
|
| 373 |
+
# Add a classifier to enable classifier-guidance
|
| 374 |
+
classifier: Optional[BaseClassifier] = None,
|
| 375 |
+
|
| 376 |
+
# ------------------ Params ---------------- #
|
| 377 |
+
grad_clip_norm: Optional[float] = None,
|
| 378 |
+
diffusion_steps: int = 1000,
|
| 379 |
+
ema_rate: float = 0.995,
|
| 380 |
+
optim_params: Optional[dict] = None,
|
| 381 |
+
|
| 382 |
+
# ------------------- EDM Params ------------------- #
|
| 383 |
+
sigma_data: float = 0.5,
|
| 384 |
+
sigma_min: float = 0.002,
|
| 385 |
+
sigma_max: float = 80.,
|
| 386 |
+
rho: float = 7.,
|
| 387 |
+
P_mean: float = -1.2,
|
| 388 |
+
P_std: float = 1.2,
|
| 389 |
+
|
| 390 |
+
device: Union[torch.device, str] = "cpu"
|
| 391 |
+
):
|
| 392 |
+
super().__init__(
|
| 393 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 394 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 395 |
+
|
| 396 |
+
self.sigma_data = sigma_data
|
| 397 |
+
self.sigma_min, self.sigma_max, self.rho = sigma_min, sigma_max, rho
|
| 398 |
+
self.P_mean, self.P_std = P_mean, P_std
|
| 399 |
+
|
| 400 |
+
def set_sample_steps(self, N: int):
|
| 401 |
+
self.sample_steps = N
|
| 402 |
+
self.sigma_s = (self.sigma_max ** (1 / self.rho) + torch.arange(N + 1, device=self.device) / N *
|
| 403 |
+
(self.sigma_min ** (1 / self.rho) - self.sigma_max ** (1 / self.rho))) ** self.rho
|
| 404 |
+
self.t_s = self.sigma_s
|
| 405 |
+
self.scale_s = torch.ones_like(self.sigma_s)
|
| 406 |
+
self.dot_sigma_s = torch.ones_like(self.sigma_s)
|
| 407 |
+
self.dot_scale_s = torch.zeros_like(self.sigma_s)
|
| 408 |
+
self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
|
| 409 |
+
self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
|
| 410 |
+
|
| 411 |
+
def c_skip(self, sigma): return self.sigma_data ** 2 / (self.sigma_data ** 2 + sigma ** 2)
|
| 412 |
+
|
| 413 |
+
def c_out(self, sigma): return sigma * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 414 |
+
|
| 415 |
+
def c_in(self, sigma): return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 416 |
+
|
| 417 |
+
def c_noise(self, sigma): return 0.25 * sigma.log()
|
| 418 |
+
|
| 419 |
+
def loss_weighting(self, sigma): return (self.sigma_data ** 2 + sigma ** 2) / ((sigma * self.sigma_data) ** 2)
|
| 420 |
+
|
| 421 |
+
def sample_noise_distribution(self, N):
|
| 422 |
+
log_sigma = torch.randn(N, device=self.device) * self.P_std + self.P_mean
|
| 423 |
+
return log_sigma.exp()
|
| 424 |
+
|
| 425 |
+
def sample_scale_distribution(self, N):
|
| 426 |
+
return torch.ones(N, device=self.device)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edmddim.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union, Optional
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 7 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 8 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 9 |
+
from .edm import EDMArchetecture
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class EDMDDIM(EDMArchetecture):
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
|
| 16 |
+
# ----------------- Neural Networks ----------------- #
|
| 17 |
+
nn_diffusion: BaseNNDiffusion,
|
| 18 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 19 |
+
|
| 20 |
+
# ----------------- Masks ----------------- #
|
| 21 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 22 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 23 |
+
# Add loss weight
|
| 24 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 25 |
+
|
| 26 |
+
# ------------------ Plugs ---------------- #
|
| 27 |
+
# Add a classifier to enable classifier-guidance
|
| 28 |
+
classifier: Optional[BaseClassifier] = None,
|
| 29 |
+
|
| 30 |
+
# ------------------ Params ---------------- #
|
| 31 |
+
grad_clip_norm: Optional[float] = None,
|
| 32 |
+
diffusion_steps: int = 1000,
|
| 33 |
+
ema_rate: float = 0.995,
|
| 34 |
+
optim_params: Optional[dict] = None,
|
| 35 |
+
|
| 36 |
+
# ------------------- EDM Params ------------------- #
|
| 37 |
+
C1: float = 0.001,
|
| 38 |
+
C2: float = 0.008,
|
| 39 |
+
j0: float = 8,
|
| 40 |
+
|
| 41 |
+
device: Union[torch.device, str] = "cpu"
|
| 42 |
+
):
|
| 43 |
+
super().__init__(
|
| 44 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 45 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 46 |
+
|
| 47 |
+
self.C1, self.C2, self.j0 = C1, C2, j0
|
| 48 |
+
self.u = None
|
| 49 |
+
|
| 50 |
+
def set_sample_steps(self, N: int):
|
| 51 |
+
self.sample_steps = N
|
| 52 |
+
bar_alpha = torch.sin(
|
| 53 |
+
torch.arange(self.diffusion_steps + 1, device=self.device) /
|
| 54 |
+
(self.diffusion_steps * (self.C2 + 1)) * np.pi / 2) ** 2
|
| 55 |
+
tmp = torch.max(bar_alpha[:-1] / bar_alpha[1:], torch.tensor(self.C1, device=self.device))
|
| 56 |
+
self.u = torch.empty_like(bar_alpha[:-1])
|
| 57 |
+
for i in range(self.diffusion_steps):
|
| 58 |
+
if i == 0:
|
| 59 |
+
self.u[-1 - i] = (1 / tmp[-1 - i] - 1).sqrt()
|
| 60 |
+
else:
|
| 61 |
+
self.u[-1 - i] = ((self.u[-i] ** 2 + 1) / tmp[-1 - i] - 1).sqrt()
|
| 62 |
+
idx = torch.arange(N, device=self.device)
|
| 63 |
+
self.t_s = self.u[torch.floor(self.j0 + (self.diffusion_steps - 1 - self.j0) /
|
| 64 |
+
(N - 1) * idx + 0.5).long()]
|
| 65 |
+
self.sigma_s = self.t_s
|
| 66 |
+
self.scale_s = torch.ones_like(self.sigma_s) * 1.0
|
| 67 |
+
self.dot_sigma_s = torch.ones_like(self.sigma_s) * 1.0
|
| 68 |
+
self.dot_scale_s = torch.zeros_like(self.sigma_s)
|
| 69 |
+
self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
|
| 70 |
+
self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
|
| 71 |
+
|
| 72 |
+
def c_skip(self, sigma): return torch.ones_like(sigma)
|
| 73 |
+
|
| 74 |
+
def c_out(self, sigma): return -sigma
|
| 75 |
+
|
| 76 |
+
def c_in(self, sigma): return 1 / (1 + sigma ** 2).sqrt()
|
| 77 |
+
|
| 78 |
+
def c_noise(self, sigma): return sigma
|
| 79 |
+
|
| 80 |
+
def loss_weighting(self, sigma): return 1 / (sigma ** 2)
|
| 81 |
+
|
| 82 |
+
def sample_noise_distribution(self, N):
|
| 83 |
+
j = torch.randint(0, self.diffusion_steps, (N, 1), device=self.device)
|
| 84 |
+
return self.u[j]
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/koopman_net.py
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Any, Dict, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
# =========================
|
| 9 |
+
# Utilities
|
| 10 |
+
# =========================
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def load_koopman_checkpoint(path: str, map_location: Any = "cpu") -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
| 14 |
+
"""
|
| 15 |
+
Loads a Koopman student checkpoint. Supports legacy flat state_dict or
|
| 16 |
+
wrapped dicts: {"state_dict": ..., "gamma_mode": "z"|"z0"|"obs0", ...}.
|
| 17 |
+
"""
|
| 18 |
+
obj = torch.load(path, map_location=map_location)
|
| 19 |
+
if isinstance(obj, dict) and "state_dict" in obj:
|
| 20 |
+
meta = {k: v for k, v in obj.items() if k != "state_dict"}
|
| 21 |
+
return obj["state_dict"], meta
|
| 22 |
+
return obj, {}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def orthogonal_init_(tensor, scale=1.0):
|
| 26 |
+
with torch.no_grad():
|
| 27 |
+
nn.init.orthogonal_(tensor)
|
| 28 |
+
tensor.mul_(scale)
|
| 29 |
+
|
| 30 |
+
def build_encoder(input_dim: int, latent_dim: int) -> nn.Module:
|
| 31 |
+
return nn.Sequential(
|
| 32 |
+
nn.Conv1d(input_dim, 128, kernel_size=5, padding=2),
|
| 33 |
+
nn.ReLU(),
|
| 34 |
+
nn.Conv1d(128, 256, kernel_size=5, padding=2),
|
| 35 |
+
nn.ReLU(),
|
| 36 |
+
nn.AdaptiveAvgPool1d(1),
|
| 37 |
+
nn.Flatten(),
|
| 38 |
+
nn.Linear(256, latent_dim),
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
def build_decoder(latent_dim: int, horizon: int, input_dim: int) -> nn.Module:
|
| 42 |
+
return nn.Sequential(
|
| 43 |
+
nn.Linear(latent_dim, 1024),
|
| 44 |
+
nn.ReLU(),
|
| 45 |
+
nn.Linear(1024, horizon * input_dim),
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
class BaseDistiller(nn.Module):
|
| 49 |
+
def get_spectral_penalty(self) -> torch.Tensor:
|
| 50 |
+
p = next(self.parameters())
|
| 51 |
+
return torch.tensor(0.0, device=p.device)
|
| 52 |
+
|
| 53 |
+
def get_pinv_loss(self) -> torch.Tensor:
|
| 54 |
+
p = next(self.parameters())
|
| 55 |
+
return torch.tensor(0.0, device=p.device)
|
| 56 |
+
|
| 57 |
+
# =========================
|
| 58 |
+
# KDM: static linear
|
| 59 |
+
# =========================
|
| 60 |
+
class KDMKoopmanDistiller(BaseDistiller):
|
| 61 |
+
def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
|
| 62 |
+
detach_clean=False, shared_encoder=False, noisy_latent=0.2):
|
| 63 |
+
super().__init__()
|
| 64 |
+
self.horizon = horizon
|
| 65 |
+
self.input_dim = input_dim
|
| 66 |
+
self.latent_dim = latent_dim
|
| 67 |
+
self.num_iters = num_iters
|
| 68 |
+
self.detach_clean = detach_clean
|
| 69 |
+
self.shared_encoder = shared_encoder
|
| 70 |
+
self.noisy_latent = noisy_latent
|
| 71 |
+
|
| 72 |
+
if shared_encoder:
|
| 73 |
+
self.encoder = build_encoder(input_dim, latent_dim)
|
| 74 |
+
else:
|
| 75 |
+
self.encoder_clean = build_encoder(input_dim, latent_dim)
|
| 76 |
+
self.encoder_noise = build_encoder(input_dim, latent_dim)
|
| 77 |
+
|
| 78 |
+
self.koopman = nn.Linear(latent_dim, latent_dim, bias=False)
|
| 79 |
+
orthogonal_init_(self.koopman.weight, scale=0.9)
|
| 80 |
+
|
| 81 |
+
self.decoder = build_decoder(latent_dim, horizon, input_dim)
|
| 82 |
+
|
| 83 |
+
def encode_clean(self, x):
|
| 84 |
+
return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_clean(x.permute(0, 2, 1))
|
| 85 |
+
|
| 86 |
+
def encode_noise(self, x):
|
| 87 |
+
return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_noise(x.permute(0, 2, 1))
|
| 88 |
+
|
| 89 |
+
def decode(self, z):
|
| 90 |
+
return self.decoder(z).view(-1, self.horizon, self.input_dim)
|
| 91 |
+
|
| 92 |
+
def forward(self, x_noise, x_clean=None):
|
| 93 |
+
z_noise = self.encode_noise(x_noise)
|
| 94 |
+
if self.training and self.noisy_latent > 0:
|
| 95 |
+
z_noise = z_noise + torch.randn_like(z_noise) * self.noisy_latent
|
| 96 |
+
|
| 97 |
+
z_pred = z_noise
|
| 98 |
+
for _ in range(self.num_iters):
|
| 99 |
+
z_pred = self.koopman(z_pred)
|
| 100 |
+
|
| 101 |
+
if self.training:
|
| 102 |
+
assert x_clean is not None
|
| 103 |
+
z_clean = self.encode_clean(x_clean)
|
| 104 |
+
z_clean_noisy = z_clean + torch.randn_like(z_clean) * self.noisy_latent if self.noisy_latent > 0 else z_clean
|
| 105 |
+
z_clean_target = z_clean.detach() if self.detach_clean else z_clean
|
| 106 |
+
|
| 107 |
+
x_recon = self.decode(z_clean_noisy)
|
| 108 |
+
x_pred = self.decode(z_pred)
|
| 109 |
+
return z_clean_target, z_pred, x_recon, x_pred
|
| 110 |
+
else:
|
| 111 |
+
return self.decode(z_pred)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# =========================
|
| 115 |
+
# KDM-F: static factorized (Aligned with NeurIPS 2025 FastKoopmanMatrix)
|
| 116 |
+
# =========================
|
| 117 |
+
class StaticFactorizedKoopman(nn.Module):
|
| 118 |
+
"""
|
| 119 |
+
KDM-F 风格:严格对齐 NeurIPS 2025 的 FastKoopmanMatrix
|
| 120 |
+
1. 采用 QR 分解进行正交初始化
|
| 121 |
+
2. 采用双重指数 exp(-exp(nu)) 严格约束特征值模长在 (0, 1) 内
|
| 122 |
+
"""
|
| 123 |
+
def __init__(self, latent_dim: int):
|
| 124 |
+
super().__init__()
|
| 125 |
+
self.d = latent_dim
|
| 126 |
+
scale = 1.0 / math.sqrt(2 * self.d)
|
| 127 |
+
|
| 128 |
+
# 1. QR 分解正交初始化 P_re 和 P_im
|
| 129 |
+
P_re_init = self.random_orthonormal_matrix(self.d) * scale
|
| 130 |
+
P_im_init = self.random_orthonormal_matrix(self.d) * scale
|
| 131 |
+
self.P_re = nn.Parameter(P_re_init)
|
| 132 |
+
self.P_im = nn.Parameter(P_im_init)
|
| 133 |
+
|
| 134 |
+
# P_inv 原版使用标准高斯初始化,依赖 P_inv Loss 在训练中对齐
|
| 135 |
+
self.P_inv_re = nn.Parameter(torch.randn(self.d, self.d) * scale)
|
| 136 |
+
self.P_inv_im = nn.Parameter(torch.randn(self.d, self.d) * scale)
|
| 137 |
+
|
| 138 |
+
# 2. 严格收缩的特征值参数化 (防止动力学发散)
|
| 139 |
+
rmin, rmax = 0.0, 1.0
|
| 140 |
+
max_phase = 6.283 # 2 * pi
|
| 141 |
+
|
| 142 |
+
u1 = torch.rand(self.d)
|
| 143 |
+
u2 = torch.rand(self.d)
|
| 144 |
+
|
| 145 |
+
# nu_log 映射,使得 lambda_mod = exp(-exp(nu_log))
|
| 146 |
+
init_nu = torch.log(-0.5 * torch.log(u1 * (rmax + rmin) * (rmax - rmin) + rmin**2))
|
| 147 |
+
self.nu_log = nn.Parameter(init_nu)
|
| 148 |
+
|
| 149 |
+
# theta_log 映射,使得 phase = exp(theta_log)
|
| 150 |
+
self.theta_log = nn.Parameter(torch.log(max_phase * u2))
|
| 151 |
+
|
| 152 |
+
def random_orthonormal_matrix(self, size):
|
| 153 |
+
"""生成随机正交矩阵用于初始化"""
|
| 154 |
+
q, _ = torch.linalg.qr(torch.randn(size, size))
|
| 155 |
+
return q
|
| 156 |
+
|
| 157 |
+
def normalize_rows(self, x, eps=1e-8):
|
| 158 |
+
"""行归一化,原版前向传播时使用"""
|
| 159 |
+
return x / (x.norm(dim=-1, keepdim=True) + eps)
|
| 160 |
+
|
| 161 |
+
def to_real_block_matrix(self, re, im):
|
| 162 |
+
"""将复数矩阵重组为实数块对角矩阵"""
|
| 163 |
+
top = torch.cat([re, -im], dim=1)
|
| 164 |
+
bottom = torch.cat([im, re], dim=1)
|
| 165 |
+
return torch.cat([top, bottom], dim=0)
|
| 166 |
+
|
| 167 |
+
def forward(self, z):
|
| 168 |
+
# 归一化特征向量
|
| 169 |
+
P_re_norm = self.normalize_rows(self.P_re)
|
| 170 |
+
P_im_norm = self.normalize_rows(self.P_im)
|
| 171 |
+
|
| 172 |
+
P = self.to_real_block_matrix(P_re_norm, P_im_norm)
|
| 173 |
+
P_inv = self.to_real_block_matrix(self.P_inv_re, self.P_inv_im)
|
| 174 |
+
|
| 175 |
+
# 还原严格有界的特征值
|
| 176 |
+
lambda_mod = torch.exp(-torch.exp(self.nu_log))
|
| 177 |
+
phase = torch.exp(self.theta_log)
|
| 178 |
+
lambda_re = lambda_mod * torch.cos(phase)
|
| 179 |
+
lambda_im = lambda_mod * torch.sin(phase)
|
| 180 |
+
|
| 181 |
+
Lambda = self.to_real_block_matrix(torch.diag(lambda_re), torch.diag(lambda_im))
|
| 182 |
+
|
| 183 |
+
# 状态增广并演化
|
| 184 |
+
z_aug = torch.cat([z, torch.zeros_like(z)], dim=-1)
|
| 185 |
+
z_pushed = z_aug @ P_inv.T @ Lambda.T @ P.T
|
| 186 |
+
|
| 187 |
+
# 截断回原维度
|
| 188 |
+
return z_pushed[..., :self.d]
|
| 189 |
+
|
| 190 |
+
def get_spectral_penalty(self):
|
| 191 |
+
# 对应原版 EigenSpecKoopmanLossTypes.OnTheCircle
|
| 192 |
+
# 因为 lambda_mod 已经被双重指数限制在 (0, 1) 之间
|
| 193 |
+
# 谱惩罚的作用是将它们推向单位圆边缘,防止过度收缩导致特征消失
|
| 194 |
+
lambda_mod = torch.exp(-torch.exp(self.nu_log))
|
| 195 |
+
return torch.abs(lambda_mod - 1.0).mean()
|
| 196 |
+
|
| 197 |
+
def get_pinv_loss(self):
|
| 198 |
+
# 逆矩阵对齐损失
|
| 199 |
+
P_re_norm = self.normalize_rows(self.P_re)
|
| 200 |
+
P_im_norm = self.normalize_rows(self.P_im)
|
| 201 |
+
P = self.to_real_block_matrix(P_re_norm, P_im_norm)
|
| 202 |
+
P_inv = self.to_real_block_matrix(self.P_inv_re, self.P_inv_im)
|
| 203 |
+
|
| 204 |
+
I = torch.eye(2 * self.d, device=P.device)
|
| 205 |
+
return F.mse_loss(P_inv @ P, I) + F.mse_loss(P @ P_inv, I)
|
| 206 |
+
|
| 207 |
+
class KDMFactorizedDistiller(BaseDistiller):
|
| 208 |
+
def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
|
| 209 |
+
spectral_penalty_coef=0.01, detach_clean=False,
|
| 210 |
+
shared_encoder=False, noisy_latent=0.2):
|
| 211 |
+
super().__init__()
|
| 212 |
+
self.horizon = horizon
|
| 213 |
+
self.input_dim = input_dim
|
| 214 |
+
self.latent_dim = latent_dim
|
| 215 |
+
self.num_iters = num_iters
|
| 216 |
+
self.detach_clean = detach_clean
|
| 217 |
+
self.spectral_penalty_coef = spectral_penalty_coef
|
| 218 |
+
self.shared_encoder = shared_encoder
|
| 219 |
+
self.noisy_latent = noisy_latent
|
| 220 |
+
|
| 221 |
+
if shared_encoder:
|
| 222 |
+
self.encoder = build_encoder(input_dim, latent_dim)
|
| 223 |
+
else:
|
| 224 |
+
self.encoder_clean = build_encoder(input_dim, latent_dim)
|
| 225 |
+
self.encoder_noise = build_encoder(input_dim, latent_dim)
|
| 226 |
+
|
| 227 |
+
self.koopman = StaticFactorizedKoopman(latent_dim)
|
| 228 |
+
self.decoder = build_decoder(latent_dim, horizon, input_dim)
|
| 229 |
+
|
| 230 |
+
def encode_clean(self, x):
|
| 231 |
+
return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_clean(x.permute(0, 2, 1))
|
| 232 |
+
|
| 233 |
+
def encode_noise(self, x):
|
| 234 |
+
return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_noise(x.permute(0, 2, 1))
|
| 235 |
+
|
| 236 |
+
def decode(self, z):
|
| 237 |
+
return self.decoder(z).view(-1, self.horizon, self.input_dim)
|
| 238 |
+
|
| 239 |
+
def forward(self, x_noise, x_clean=None):
|
| 240 |
+
z_noise = self.encode_noise(x_noise)
|
| 241 |
+
if self.training and self.noisy_latent > 0:
|
| 242 |
+
z_noise = z_noise + torch.randn_like(z_noise) * self.noisy_latent
|
| 243 |
+
|
| 244 |
+
z_pred = z_noise
|
| 245 |
+
for _ in range(self.num_iters):
|
| 246 |
+
z_pred = self.koopman(z_pred)
|
| 247 |
+
|
| 248 |
+
if self.training:
|
| 249 |
+
assert x_clean is not None
|
| 250 |
+
z_clean = self.encode_clean(x_clean)
|
| 251 |
+
z_clean_noisy = z_clean + torch.randn_like(z_clean) * self.noisy_latent if self.noisy_latent > 0 else z_clean
|
| 252 |
+
z_clean_target = z_clean.detach() if self.detach_clean else z_clean
|
| 253 |
+
|
| 254 |
+
x_recon = self.decode(z_clean_noisy)
|
| 255 |
+
x_pred = self.decode(z_pred)
|
| 256 |
+
return z_clean_target, z_pred, x_recon, x_pred
|
| 257 |
+
else:
|
| 258 |
+
return self.decode(z_pred)
|
| 259 |
+
|
| 260 |
+
def get_spectral_penalty(self):
|
| 261 |
+
return self.koopman.get_spectral_penalty() * self.spectral_penalty_coef
|
| 262 |
+
|
| 263 |
+
def get_pinv_loss(self):
|
| 264 |
+
return self.koopman.get_pinv_loss()
|
| 265 |
+
|
| 266 |
+
# =========================
|
| 267 |
+
# Regression baseline
|
| 268 |
+
# =========================
|
| 269 |
+
class ResidualMLPEvolver(nn.Module):
|
| 270 |
+
def __init__(self, latent_dim: int, hidden_dim: int = 1024, depth: int = 2):
|
| 271 |
+
super().__init__()
|
| 272 |
+
layers = []
|
| 273 |
+
d_in = latent_dim
|
| 274 |
+
for i in range(depth):
|
| 275 |
+
d_out = hidden_dim if i < depth - 1 else latent_dim
|
| 276 |
+
layers.append(nn.Linear(d_in, d_out))
|
| 277 |
+
if i < depth - 1:
|
| 278 |
+
layers.append(nn.ReLU())
|
| 279 |
+
d_in = d_out
|
| 280 |
+
self.net = nn.Sequential(*layers)
|
| 281 |
+
|
| 282 |
+
def forward(self, z):
|
| 283 |
+
return z + self.net(z)
|
| 284 |
+
|
| 285 |
+
class RegressionDistiller(BaseDistiller):
|
| 286 |
+
def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1, detach_clean=False):
|
| 287 |
+
super().__init__()
|
| 288 |
+
self.horizon = horizon
|
| 289 |
+
self.input_dim = input_dim
|
| 290 |
+
self.latent_dim = latent_dim
|
| 291 |
+
self.num_iters = num_iters
|
| 292 |
+
self.detach_clean = detach_clean
|
| 293 |
+
|
| 294 |
+
self.encoder = build_encoder(input_dim, latent_dim)
|
| 295 |
+
self.decoder = build_decoder(latent_dim, horizon, input_dim)
|
| 296 |
+
self.evolver = ResidualMLPEvolver(latent_dim, hidden_dim=1024, depth=2)
|
| 297 |
+
|
| 298 |
+
def forward(self, x_noise, x_clean=None):
|
| 299 |
+
z_noise = self.encoder(x_noise.permute(0, 2, 1))
|
| 300 |
+
|
| 301 |
+
z_pred = z_noise
|
| 302 |
+
for _ in range(self.num_iters):
|
| 303 |
+
z_pred = self.evolver(z_pred)
|
| 304 |
+
|
| 305 |
+
if self.training:
|
| 306 |
+
assert x_clean is not None
|
| 307 |
+
z_clean = self.encoder(x_clean.permute(0, 2, 1))
|
| 308 |
+
z_clean_target = z_clean.detach() if self.detach_clean else z_clean
|
| 309 |
+
|
| 310 |
+
x_recon = self.decoder(z_clean).view(-1, self.horizon, self.input_dim)
|
| 311 |
+
x_pred = self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
|
| 312 |
+
return z_clean_target, z_pred, x_recon, x_pred
|
| 313 |
+
else:
|
| 314 |
+
return self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
|
| 315 |
+
|
| 316 |
+
# =========================
|
| 317 |
+
# Ours: dynamic factorized Koopman
|
| 318 |
+
# =========================
|
| 319 |
+
class FactorizedDynamicKoopman(nn.Module):
|
| 320 |
+
"""
|
| 321 |
+
Modal gains gamma: eigen_net input is either current latent z (default),
|
| 322 |
+
fixed initial latent z0 (gamma_mode='z0'), or first-timestep obs slice (gamma_mode='obs0').
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
def __init__(self, latent_dim: int, gamma_mode: str = "z", obs_dim: Optional[int] = None):
|
| 326 |
+
super().__init__()
|
| 327 |
+
self.latent_dim = latent_dim
|
| 328 |
+
self.gamma_mode = gamma_mode
|
| 329 |
+
if gamma_mode not in ("z", "z0", "obs0"):
|
| 330 |
+
raise ValueError(f"gamma_mode must be 'z', 'z0', or 'obs0', got {gamma_mode!r}")
|
| 331 |
+
if gamma_mode == "obs0" and obs_dim is None:
|
| 332 |
+
raise ValueError("obs0 gamma_mode requires obs_dim")
|
| 333 |
+
|
| 334 |
+
self.obs_dim = obs_dim
|
| 335 |
+
cond_dim = obs_dim if gamma_mode == "obs0" else latent_dim
|
| 336 |
+
|
| 337 |
+
self.P = nn.Linear(latent_dim, latent_dim, bias=False)
|
| 338 |
+
self.P_inv = nn.Linear(latent_dim, latent_dim, bias=False)
|
| 339 |
+
|
| 340 |
+
nn.init.orthogonal_(self.P.weight)
|
| 341 |
+
self.P_inv.weight.data = self.P.weight.data.T.clone()
|
| 342 |
+
|
| 343 |
+
self.eigen_net = nn.Sequential(
|
| 344 |
+
nn.Linear(cond_dim, 128),
|
| 345 |
+
nn.ReLU(),
|
| 346 |
+
nn.Linear(128, latent_dim)
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
def forward(self, z: torch.Tensor, c_context: Optional[torch.Tensor] = None):
|
| 350 |
+
if self.gamma_mode == "z":
|
| 351 |
+
gamma_in = z
|
| 352 |
+
else:
|
| 353 |
+
if c_context is None:
|
| 354 |
+
raise ValueError(f"gamma_mode={self.gamma_mode!r} requires c_context (z0 or obs0).")
|
| 355 |
+
gamma_in = c_context
|
| 356 |
+
eigen_values = self.eigen_net(gamma_in)
|
| 357 |
+
z_projected = self.P_inv(z)
|
| 358 |
+
z_evolved = z_projected * eigen_values
|
| 359 |
+
z_next = self.P(z_evolved)
|
| 360 |
+
return z_next, eigen_values
|
| 361 |
+
|
| 362 |
+
def get_pinv_loss(self):
|
| 363 |
+
I = torch.eye(self.latent_dim, device=self.P.weight.device)
|
| 364 |
+
return F.mse_loss(self.P.weight @ self.P_inv.weight, I)
|
| 365 |
+
|
| 366 |
+
class KoopmanDistiller(BaseDistiller):
|
| 367 |
+
def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
|
| 368 |
+
spectral_penalty_coef=0.01, detach_clean=True,
|
| 369 |
+
gamma_mode: str = "z", obs_dim: Optional[int] = None):
|
| 370 |
+
super().__init__()
|
| 371 |
+
self.horizon = horizon
|
| 372 |
+
self.input_dim = input_dim
|
| 373 |
+
self.latent_dim = latent_dim
|
| 374 |
+
self.num_iters = num_iters
|
| 375 |
+
self.spectral_penalty_coef = spectral_penalty_coef
|
| 376 |
+
self.detach_clean = detach_clean
|
| 377 |
+
self.gamma_mode = gamma_mode
|
| 378 |
+
if gamma_mode == "obs0" and obs_dim is None:
|
| 379 |
+
raise ValueError("KoopmanDistiller: obs0 gamma_mode requires obs_dim")
|
| 380 |
+
self.obs_dim = obs_dim
|
| 381 |
+
|
| 382 |
+
self.encoder = build_encoder(input_dim, latent_dim)
|
| 383 |
+
self.decoder = build_decoder(latent_dim, horizon, input_dim)
|
| 384 |
+
self.koopman = FactorizedDynamicKoopman(latent_dim, gamma_mode=gamma_mode, obs_dim=obs_dim)
|
| 385 |
+
|
| 386 |
+
def gamma_context(self, x_noise: torch.Tensor) -> Optional[torch.Tensor]:
|
| 387 |
+
"""Conditioning vector for eigen_net when gamma_mode is z0 or obs0; else None."""
|
| 388 |
+
if self.gamma_mode == "z":
|
| 389 |
+
return None
|
| 390 |
+
if self.gamma_mode == "z0":
|
| 391 |
+
return self.encoder(x_noise.permute(0, 2, 1))
|
| 392 |
+
return x_noise[:, 0, : self.obs_dim]
|
| 393 |
+
|
| 394 |
+
def forward(self, x_noise, x_clean=None):
|
| 395 |
+
z_noise = self.encoder(x_noise.permute(0, 2, 1))
|
| 396 |
+
c_ctx = self.gamma_context(x_noise)
|
| 397 |
+
|
| 398 |
+
all_eigen_values = []
|
| 399 |
+
z_pred = z_noise
|
| 400 |
+
for _ in range(self.num_iters):
|
| 401 |
+
z_pred, eigen_vals = self.koopman(z_pred, c_ctx)
|
| 402 |
+
all_eigen_values.append(eigen_vals)
|
| 403 |
+
|
| 404 |
+
self.current_eigen_values = torch.cat(all_eigen_values, dim=0)
|
| 405 |
+
|
| 406 |
+
if self.training:
|
| 407 |
+
assert x_clean is not None
|
| 408 |
+
z_clean = self.encoder(x_clean.permute(0, 2, 1))
|
| 409 |
+
z_clean_target = z_clean.detach() if self.detach_clean else z_clean
|
| 410 |
+
|
| 411 |
+
x_recon = self.decoder(z_clean).view(-1, self.horizon, self.input_dim)
|
| 412 |
+
x_pred = self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
|
| 413 |
+
return z_clean_target, z_pred, x_recon, x_pred
|
| 414 |
+
else:
|
| 415 |
+
return self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
|
| 416 |
+
|
| 417 |
+
def get_spectral_penalty(self):
|
| 418 |
+
if not hasattr(self, "current_eigen_values"):
|
| 419 |
+
p = next(self.parameters())
|
| 420 |
+
return torch.tensor(0.0, device=p.device)
|
| 421 |
+
|
| 422 |
+
radii = torch.abs(self.current_eigen_values)
|
| 423 |
+
penalty = F.relu(radii - 1.0).mean()
|
| 424 |
+
return penalty * self.spectral_penalty_coef
|
| 425 |
+
|
| 426 |
+
def get_pinv_loss(self):
|
| 427 |
+
return self.koopman.get_pinv_loss()
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/newedm.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import (
|
| 11 |
+
at_least_ndim)
|
| 12 |
+
from .basic import DiffusionModel
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ContinuousEDM(DiffusionModel):
|
| 16 |
+
"""Continuous-time EDM
|
| 17 |
+
|
| 18 |
+
EDM posits that the concepts of `t` in the diffusion process and the noise schedule are equivalent.
|
| 19 |
+
Previous noise schedules can be interpreted as perturbing the data with Gaussian noise
|
| 20 |
+
followed by scaling. EDM sets the standard deviation of noise as `t`, the scale as 1,
|
| 21 |
+
and devises a series of preconditioning steps to aid in model learning.
|
| 22 |
+
|
| 23 |
+
The current implementation of EDM only supports continuous-time ODEs.
|
| 24 |
+
The sampling steps are required to be greater than 1.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
- nn_diffusion: BaseNNDiffusion
|
| 28 |
+
The neural network backbone for the Diffusion model.
|
| 29 |
+
- nn_condition: Optional[BaseNNCondition]
|
| 30 |
+
The neural network backbone for the condition embedding.
|
| 31 |
+
|
| 32 |
+
- fix_mask: Union[list, np.ndarray, torch.Tensor]
|
| 33 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 34 |
+
The mask should be in the shape of `x_shape`.
|
| 35 |
+
- loss_weight: Union[list, np.ndarray, torch.Tensor]
|
| 36 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 37 |
+
|
| 38 |
+
- classifier: Optional[BaseClassifier]
|
| 39 |
+
Add a classifier to enable classifier-guidance.
|
| 40 |
+
|
| 41 |
+
- grad_clip_norm: Optional[float]
|
| 42 |
+
Gradient clipping norm.
|
| 43 |
+
- ema_rate: float
|
| 44 |
+
Exponential moving average rate.
|
| 45 |
+
- optim_params: Optional[dict]
|
| 46 |
+
Optimizer parameters.
|
| 47 |
+
|
| 48 |
+
- sigma_data: float
|
| 49 |
+
The standard deviation of the data. Default: 0.5.
|
| 50 |
+
- sigma_min: float
|
| 51 |
+
The minimum standard deviation of the noise. Default: 0.002.
|
| 52 |
+
- sigma_max: float
|
| 53 |
+
The maximum standard deviation of the noise. Default: 80.
|
| 54 |
+
- rho: float
|
| 55 |
+
The power of the noise schedule. Default: 7.
|
| 56 |
+
- P_mean: float
|
| 57 |
+
Hyperparameter for noise sampling during training. Default: -1.2.
|
| 58 |
+
- P_std: float
|
| 59 |
+
Hyperparameter for noise sampling during training. Default: 1.2.
|
| 60 |
+
|
| 61 |
+
- x_max: Optional[torch.Tensor]
|
| 62 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 63 |
+
- x_min: Optional[torch.Tensor]
|
| 64 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 65 |
+
|
| 66 |
+
- device: Union[torch.device, str]
|
| 67 |
+
The device to run the model.
|
| 68 |
+
"""
|
| 69 |
+
def __init__(
|
| 70 |
+
self,
|
| 71 |
+
|
| 72 |
+
# ----------------- Neural Networks ----------------- #
|
| 73 |
+
nn_diffusion: BaseNNDiffusion,
|
| 74 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 75 |
+
|
| 76 |
+
# ----------------- Masks ----------------- #
|
| 77 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 78 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 79 |
+
# Add loss weight
|
| 80 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 81 |
+
|
| 82 |
+
# ------------------ Plugins ---------------- #
|
| 83 |
+
# Add a classifier to enable classifier-guidance
|
| 84 |
+
classifier: Optional[BaseClassifier] = None,
|
| 85 |
+
|
| 86 |
+
# ------------------ Training Params ---------------- #
|
| 87 |
+
grad_clip_norm: Optional[float] = None,
|
| 88 |
+
ema_rate: float = 0.995,
|
| 89 |
+
optim_params: Optional[dict] = None,
|
| 90 |
+
|
| 91 |
+
# ------------------- Diffusion Params ------------------- #
|
| 92 |
+
sigma_data: float = 0.5,
|
| 93 |
+
sigma_min: float = 0.002,
|
| 94 |
+
sigma_max: float = 80.,
|
| 95 |
+
rho: float = 7.,
|
| 96 |
+
P_mean: float = -1.2,
|
| 97 |
+
P_std: float = 1.2,
|
| 98 |
+
|
| 99 |
+
x_max: Optional[torch.Tensor] = None,
|
| 100 |
+
x_min: Optional[torch.Tensor] = None,
|
| 101 |
+
|
| 102 |
+
device: Union[torch.device, str] = "cpu"
|
| 103 |
+
):
|
| 104 |
+
super().__init__(
|
| 105 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 106 |
+
0, ema_rate, optim_params, device)
|
| 107 |
+
|
| 108 |
+
self.sigma_data, self.sigma_min, self.sigma_max = sigma_data, sigma_min, sigma_max
|
| 109 |
+
self.rho, self.P_mean, self.P_std = rho, P_mean, P_std
|
| 110 |
+
|
| 111 |
+
self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
|
| 112 |
+
self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
|
| 113 |
+
|
| 114 |
+
# ==================== Continuous Time-step Range ====================
|
| 115 |
+
self.t_diffusion = [sigma_min, sigma_max]
|
| 116 |
+
|
| 117 |
+
# ======================= Noise Schedule =========================
|
| 118 |
+
# scale(t) = 1., dot_scale(t) = 0.
|
| 119 |
+
# sigma(t) = t , dot_sigma(t) = 1.
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def supported_solvers(self):
|
| 123 |
+
return ["euler", "heun"]
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def clip_pred(self):
|
| 127 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 128 |
+
|
| 129 |
+
# ===================== EDM Pre-conditioning =========================
|
| 130 |
+
def c_skip(self, sigma):
|
| 131 |
+
return self.sigma_data ** 2 / (self.sigma_data ** 2 + sigma ** 2)
|
| 132 |
+
|
| 133 |
+
def c_out(self, sigma):
|
| 134 |
+
return sigma * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 135 |
+
|
| 136 |
+
def c_in(self, sigma):
|
| 137 |
+
return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
|
| 138 |
+
|
| 139 |
+
def c_noise(self, sigma):
|
| 140 |
+
return 0.25 * sigma.log()
|
| 141 |
+
|
| 142 |
+
def D(self, x, sigma, condition=None, model=None):
|
| 143 |
+
""" Prepositioning in EDM """
|
| 144 |
+
c_skip, c_out, c_in, c_noise = self.c_skip(sigma), self.c_out(sigma), self.c_in(sigma), self.c_noise(sigma)
|
| 145 |
+
if model is None:
|
| 146 |
+
model = self.model
|
| 147 |
+
c_skip, c_in, c_out = at_least_ndim(c_skip, x.dim()), at_least_ndim(c_in, x.dim()), at_least_ndim(c_out, x.dim())
|
| 148 |
+
return c_skip * x + c_out * model["diffusion"](c_in * x, c_noise, condition)
|
| 149 |
+
|
| 150 |
+
# ==================== Training: Score Matching ======================
|
| 151 |
+
|
| 152 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 153 |
+
|
| 154 |
+
t = (torch.randn((x0.shape[0], ), device=self.device) * self.P_std + self.P_mean).exp() if t is None else t
|
| 155 |
+
|
| 156 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 157 |
+
|
| 158 |
+
scale = 1.
|
| 159 |
+
sigma = at_least_ndim(t, x0.dim())
|
| 160 |
+
|
| 161 |
+
xt = scale * x0 + sigma * eps
|
| 162 |
+
xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
|
| 163 |
+
|
| 164 |
+
return xt, t, eps
|
| 165 |
+
|
| 166 |
+
def loss(self, x0, condition=None):
|
| 167 |
+
|
| 168 |
+
xt, t, eps = self.add_noise(x0)
|
| 169 |
+
|
| 170 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 171 |
+
|
| 172 |
+
loss = (self.D(xt, t, condition) - x0) ** 2
|
| 173 |
+
|
| 174 |
+
edm_loss_weight = at_least_ndim((t ** 2 + self.sigma_data ** 2) / ((t * self.sigma_data) ** 2), x0.dim())
|
| 175 |
+
|
| 176 |
+
return (loss * self.loss_weight * (1 - self.fix_mask) * edm_loss_weight).mean()
|
| 177 |
+
|
| 178 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 179 |
+
"""One-step gradient update.
|
| 180 |
+
Inputs:
|
| 181 |
+
- x0: torch.Tensor
|
| 182 |
+
Samples from the target distribution.
|
| 183 |
+
- condition: Optional
|
| 184 |
+
Condition of x0. `None` indicates no condition.
|
| 185 |
+
- update_ema: bool
|
| 186 |
+
Whether to update the exponential moving average model.
|
| 187 |
+
|
| 188 |
+
Outputs:
|
| 189 |
+
- log: dict
|
| 190 |
+
The log dictionary.
|
| 191 |
+
"""
|
| 192 |
+
loss = self.loss(x0, condition)
|
| 193 |
+
|
| 194 |
+
loss.backward()
|
| 195 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 196 |
+
if self.grad_clip_norm else None
|
| 197 |
+
self.optimizer.step()
|
| 198 |
+
self.optimizer.zero_grad()
|
| 199 |
+
|
| 200 |
+
if update_ema:
|
| 201 |
+
self.ema_update()
|
| 202 |
+
|
| 203 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 204 |
+
|
| 205 |
+
return log
|
| 206 |
+
|
| 207 |
+
def update_classifier(self, x0, condition):
|
| 208 |
+
|
| 209 |
+
xt, t, eps = self.add_noise(x0)
|
| 210 |
+
|
| 211 |
+
log = self.classifier.update(xt, t.log() / 4., condition)
|
| 212 |
+
|
| 213 |
+
return log
|
| 214 |
+
|
| 215 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 216 |
+
|
| 217 |
+
def classifier_guidance(
|
| 218 |
+
self, xt, t, sigma,
|
| 219 |
+
model, condition=None, w: float = 1.0,
|
| 220 |
+
pred=None):
|
| 221 |
+
"""
|
| 222 |
+
Guided Sampling CG:
|
| 223 |
+
bar_eps = eps - w * sigma * grad
|
| 224 |
+
bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
|
| 225 |
+
"""
|
| 226 |
+
if pred is None:
|
| 227 |
+
pred = self.D(xt, t, None, model)
|
| 228 |
+
if self.classifier is None or w == 0.0 or condition is None:
|
| 229 |
+
return pred, None
|
| 230 |
+
else:
|
| 231 |
+
log_p, grad = self.classifier.gradients(
|
| 232 |
+
xt.clone(), t.log() / 4., condition)
|
| 233 |
+
pred = pred + w * (at_least_ndim(sigma, pred.dim()) ** 2) * grad
|
| 234 |
+
|
| 235 |
+
return pred, log_p
|
| 236 |
+
|
| 237 |
+
def classifier_free_guidance(
|
| 238 |
+
self, xt, t,
|
| 239 |
+
model, condition=None, w: float = 1.0,
|
| 240 |
+
pred=None, pred_uncond=None,
|
| 241 |
+
requires_grad: bool = False):
|
| 242 |
+
"""
|
| 243 |
+
Guided Sampling CFG:
|
| 244 |
+
bar_eps = w * pred + (1 - w) * pred_uncond
|
| 245 |
+
bar_x0 = w * pred + (1 - w) * pred_uncond
|
| 246 |
+
"""
|
| 247 |
+
with torch.set_grad_enabled(requires_grad):
|
| 248 |
+
if w != 0.0 and w != 1.0:
|
| 249 |
+
if pred is None or pred_uncond is None:
|
| 250 |
+
b = xt.shape[0]
|
| 251 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 252 |
+
condition = torch.cat([condition, torch.zeros_like(condition)], 0)
|
| 253 |
+
pred_all = self.D(
|
| 254 |
+
xt.repeat(*repeat_dim),
|
| 255 |
+
t.repeat(2), condition, model)
|
| 256 |
+
pred, pred_uncond = pred_all[:b], pred_all[b:]
|
| 257 |
+
elif w == 0.0:
|
| 258 |
+
pred = 0.
|
| 259 |
+
pred_uncond = self.D(xt, t, None, model)
|
| 260 |
+
else:
|
| 261 |
+
pred = self.D(xt, t, condition, model)
|
| 262 |
+
pred_uncond = 0.
|
| 263 |
+
|
| 264 |
+
bar_pred = w * pred + (1 - w) * pred_uncond
|
| 265 |
+
|
| 266 |
+
return bar_pred
|
| 267 |
+
|
| 268 |
+
def guided_sampling(
|
| 269 |
+
self, xt, t, sigma,
|
| 270 |
+
model,
|
| 271 |
+
condition_cfg=None, w_cfg: float = 0.0,
|
| 272 |
+
condition_cg=None, w_cg: float = 0.0,
|
| 273 |
+
requires_grad: bool = False):
|
| 274 |
+
"""
|
| 275 |
+
One-step epsilon/x0 prediction with guidance.
|
| 276 |
+
"""
|
| 277 |
+
|
| 278 |
+
pred = self.classifier_free_guidance(
|
| 279 |
+
xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
|
| 280 |
+
|
| 281 |
+
pred, logp = self.classifier_guidance(
|
| 282 |
+
xt, t, sigma, model, condition_cg, w_cg, pred)
|
| 283 |
+
|
| 284 |
+
return pred, logp
|
| 285 |
+
|
| 286 |
+
def sample(
|
| 287 |
+
self,
|
| 288 |
+
# ---------- the known fixed portion ---------- #
|
| 289 |
+
prior: torch.Tensor,
|
| 290 |
+
# ----------------- sampling ----------------- #
|
| 291 |
+
solver: str = "euler", # euler or heun
|
| 292 |
+
n_samples: int = 1,
|
| 293 |
+
sample_steps: int = 5,
|
| 294 |
+
use_ema: bool = True,
|
| 295 |
+
temperature: float = 1.0,
|
| 296 |
+
# ------------------ guidance ------------------ #
|
| 297 |
+
condition_cfg=None,
|
| 298 |
+
mask_cfg=None,
|
| 299 |
+
w_cfg: float = 0.0,
|
| 300 |
+
condition_cg=None,
|
| 301 |
+
w_cg: float = 0.0,
|
| 302 |
+
# ----------- Diffusion-X sampling ----------
|
| 303 |
+
diffusion_x_sampling_steps: int = 0,
|
| 304 |
+
# ----------- Warm-Starting -----------
|
| 305 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 306 |
+
warm_start_forward_level: float = 0.3,
|
| 307 |
+
# ------------------ others ------------------ #
|
| 308 |
+
requires_grad: bool = False,
|
| 309 |
+
preserve_history: bool = False,
|
| 310 |
+
**kwargs,
|
| 311 |
+
):
|
| 312 |
+
"""Sampling.
|
| 313 |
+
|
| 314 |
+
Inputs:
|
| 315 |
+
- prior: torch.Tensor
|
| 316 |
+
The known fixed portion of the input data. Should be in the shape of generated data.
|
| 317 |
+
Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
|
| 318 |
+
|
| 319 |
+
- solver: str
|
| 320 |
+
The solver for the reverse process. Check `supported_solvers` property for available solvers.
|
| 321 |
+
- n_samples: int
|
| 322 |
+
The number of samples to generate.
|
| 323 |
+
- sample_steps: int
|
| 324 |
+
The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
|
| 325 |
+
- sample_step_schedule: Union[str, Callable]
|
| 326 |
+
The schedule for the sampling steps.
|
| 327 |
+
- use_ema: bool
|
| 328 |
+
Whether to use the exponential moving average model.
|
| 329 |
+
- temperature: float
|
| 330 |
+
The temperature for sampling.
|
| 331 |
+
|
| 332 |
+
- condition_cfg: Optional
|
| 333 |
+
Condition for Classifier-free-guidance.
|
| 334 |
+
- mask_cfg: Optional
|
| 335 |
+
Mask for Classifier-guidance.
|
| 336 |
+
- w_cfg: float
|
| 337 |
+
Weight for Classifier-free-guidance.
|
| 338 |
+
- condition_cg: Optional
|
| 339 |
+
Condition for Classifier-guidance.
|
| 340 |
+
- w_cg: float
|
| 341 |
+
Weight for Classifier-guidance.
|
| 342 |
+
|
| 343 |
+
- diffusion_x_sampling_steps: int
|
| 344 |
+
The number of diffusion steps for diffusion-x sampling.
|
| 345 |
+
|
| 346 |
+
- warm_start_reference: Optional[torch.Tensor]
|
| 347 |
+
Reference data for warm-starting sampling. `None` indicates no warm-starting.
|
| 348 |
+
- warm_start_forward_level: float
|
| 349 |
+
The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
|
| 350 |
+
|
| 351 |
+
- requires_grad: bool
|
| 352 |
+
Whether to preserve gradients.
|
| 353 |
+
- preserve_history: bool
|
| 354 |
+
Whether to preserve the sampling history.
|
| 355 |
+
|
| 356 |
+
Outputs:
|
| 357 |
+
- x0: torch.Tensor
|
| 358 |
+
Generated samples. Be in the shape of `(n_samples, *x_shape)`.
|
| 359 |
+
- log: dict
|
| 360 |
+
The log dictionary.
|
| 361 |
+
"""
|
| 362 |
+
assert solver in ["euler", "heun"], f"Solver {solver} is not supported. Use 'euler' or 'heun' instead."
|
| 363 |
+
|
| 364 |
+
# ===================== Initialization =====================
|
| 365 |
+
log = {
|
| 366 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 367 |
+
|
| 368 |
+
model = self.model if not use_ema else self.model_ema
|
| 369 |
+
|
| 370 |
+
prior = prior.to(self.device)
|
| 371 |
+
if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
|
| 372 |
+
fwd_sigma = self.sigma_min + (self.sigma_max - self.sigma_min) * warm_start_forward_level
|
| 373 |
+
xt = warm_start_reference + fwd_sigma * torch.randn_like(warm_start_reference)
|
| 374 |
+
else:
|
| 375 |
+
fwd_sigma = self.sigma_max
|
| 376 |
+
xt = torch.randn_like(prior) * self.sigma_max * temperature
|
| 377 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 378 |
+
if preserve_history:
|
| 379 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 380 |
+
|
| 381 |
+
with torch.set_grad_enabled(requires_grad):
|
| 382 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 383 |
+
condition_vec_cg = condition_cg
|
| 384 |
+
|
| 385 |
+
# ===================== Sampling Schedule ====================
|
| 386 |
+
sample_step_schedule = ((self.sigma_min ** (1 / self.rho) + torch.arange(sample_steps + 1, device=self.device)
|
| 387 |
+
/ sample_steps * (fwd_sigma ** (1 / self.rho) - self.sigma_min ** (1 / self.rho)))
|
| 388 |
+
** self.rho)
|
| 389 |
+
|
| 390 |
+
sigmas = sample_step_schedule
|
| 391 |
+
|
| 392 |
+
# ===================== Denoising Loop ========================
|
| 393 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
|
| 394 |
+
for i in reversed(loop_steps):
|
| 395 |
+
|
| 396 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
|
| 397 |
+
|
| 398 |
+
# guided sampling
|
| 399 |
+
pred, logp = self.guided_sampling(
|
| 400 |
+
xt, t, sigmas[i],
|
| 401 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 402 |
+
|
| 403 |
+
# clip the prediction
|
| 404 |
+
if self.clip_pred:
|
| 405 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 406 |
+
|
| 407 |
+
# one-step update
|
| 408 |
+
dot_x = (xt - pred) / at_least_ndim(sigmas[i], xt.dim())
|
| 409 |
+
delta_t = sample_step_schedule[i] - sample_step_schedule[i - 1]
|
| 410 |
+
x_next = xt - dot_x * delta_t
|
| 411 |
+
x_next = x_next * (1. - self.fix_mask) + prior * self.fix_mask
|
| 412 |
+
|
| 413 |
+
if solver == "heun" and i > 1:
|
| 414 |
+
pred, logp = self.guided_sampling(
|
| 415 |
+
x_next, t / sample_step_schedule[i] * sample_step_schedule[i - 1], sigmas[i - 1],
|
| 416 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 417 |
+
if self.clip_pred:
|
| 418 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 419 |
+
dot_x_prime = (x_next - pred) / at_least_ndim(sigmas[i - 1], xt.dim())
|
| 420 |
+
x_next = xt - (dot_x + dot_x_prime) / 2. * delta_t
|
| 421 |
+
x_next = x_next * (1. - self.fix_mask) + prior * self.fix_mask
|
| 422 |
+
|
| 423 |
+
xt = x_next
|
| 424 |
+
|
| 425 |
+
if preserve_history:
|
| 426 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 427 |
+
|
| 428 |
+
# ================= Post-processing =================
|
| 429 |
+
if self.classifier is not None:
|
| 430 |
+
with torch.no_grad():
|
| 431 |
+
t = torch.ones((n_samples,), dtype=torch.long, device=self.device) * self.sigma_min
|
| 432 |
+
logp = self.classifier.logp(xt, t.log() / 4., condition_vec_cg)
|
| 433 |
+
log["log_p"] = logp
|
| 434 |
+
|
| 435 |
+
if self.clip_pred:
|
| 436 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 437 |
+
|
| 438 |
+
return xt, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/rectifiedflow.py
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union, Callable
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import (
|
| 11 |
+
at_least_ndim,
|
| 12 |
+
SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
|
| 13 |
+
from .basic import DiffusionModel
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class DiscreteRectifiedFlow(DiffusionModel):
|
| 17 |
+
"""Discrete-time Rectified Flow
|
| 18 |
+
|
| 19 |
+
Rectified flow learns a straight flow for transporting between two probability distributions.
|
| 20 |
+
The straight characteristic of this flow enables ODEs to achieve high-quality data generation
|
| 21 |
+
with very few sampling steps (achieving one-step sampling when the ODE is completely straight).
|
| 22 |
+
Rectified flow can continuously optimize model performance by refining the learned ODE through multiple reflow procedures.
|
| 23 |
+
Since it does not require Gaussian to be the source distribution, our implementation supports transformations
|
| 24 |
+
between two arbitrary distributions. However, if not specifically specified during training,
|
| 25 |
+
the model will default to learning the transport from a Gaussian distribution to the target distribution.
|
| 26 |
+
|
| 27 |
+
The DiscreteRectifiedFlow is the discrete-time version of Rectified flow.
|
| 28 |
+
It discretizes the continuous time interval into a finite number of diffusion steps
|
| 29 |
+
and only estimates the score function on these steps.
|
| 30 |
+
Therefore, in sampling, solvers can only work on these learned steps.
|
| 31 |
+
The sampling steps are required to be greater than 1 and less than or equal to the number of diffusion steps.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
- nn_diffusion: BaseNNDiffusion
|
| 35 |
+
The neural network backbone for the Diffusion model.
|
| 36 |
+
- nn_condition: Optional[BaseNNCondition]
|
| 37 |
+
The neural network backbone for the condition embedding.
|
| 38 |
+
|
| 39 |
+
- fix_mask: Union[list, np.ndarray, torch.Tensor]
|
| 40 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 41 |
+
The mask should be in the shape of `x_shape`.
|
| 42 |
+
- loss_weight: Union[list, np.ndarray, torch.Tensor]
|
| 43 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 44 |
+
|
| 45 |
+
- classifier: Optional[BaseClassifier]
|
| 46 |
+
Add a classifier to enable classifier-guidance.
|
| 47 |
+
|
| 48 |
+
- grad_clip_norm: Optional[float]
|
| 49 |
+
Gradient clipping norm.
|
| 50 |
+
- ema_rate: float
|
| 51 |
+
Exponential moving average rate.
|
| 52 |
+
- optim_params: Optional[dict]
|
| 53 |
+
Optimizer parameters.
|
| 54 |
+
|
| 55 |
+
- diffusion_steps: int
|
| 56 |
+
The discretization steps for discrete-time diffusion models.
|
| 57 |
+
- discretization: Union[str, Callable]
|
| 58 |
+
The discretization method for the diffusion steps.
|
| 59 |
+
|
| 60 |
+
- x_max: Optional[torch.Tensor]
|
| 61 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 62 |
+
- x_min: Optional[torch.Tensor]
|
| 63 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 64 |
+
|
| 65 |
+
- device: Union[torch.device, str]
|
| 66 |
+
The device to run the model.
|
| 67 |
+
"""
|
| 68 |
+
def __init__(
|
| 69 |
+
self,
|
| 70 |
+
|
| 71 |
+
# ----------------- Neural Networks ----------------- #
|
| 72 |
+
nn_diffusion: BaseNNDiffusion,
|
| 73 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 74 |
+
|
| 75 |
+
# ----------------- Masks ----------------- #
|
| 76 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 77 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 78 |
+
# Add loss weight
|
| 79 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 80 |
+
|
| 81 |
+
# ------------------ Plugins ---------------- #
|
| 82 |
+
# Add a classifier to enable classifier-guidance
|
| 83 |
+
classifier: Optional[BaseClassifier] = None,
|
| 84 |
+
|
| 85 |
+
# ------------------ Training Params ---------------- #
|
| 86 |
+
grad_clip_norm: Optional[float] = None,
|
| 87 |
+
ema_rate: float = 0.995,
|
| 88 |
+
optim_params: Optional[dict] = None,
|
| 89 |
+
|
| 90 |
+
# ------------------- Diffusion Params ------------------- #
|
| 91 |
+
diffusion_steps: int = 1000,
|
| 92 |
+
|
| 93 |
+
discretization: Union[str, Callable] = "uniform",
|
| 94 |
+
|
| 95 |
+
x_max: Optional[torch.Tensor] = None,
|
| 96 |
+
x_min: Optional[torch.Tensor] = None,
|
| 97 |
+
|
| 98 |
+
device: Union[torch.device, str] = "cpu"
|
| 99 |
+
):
|
| 100 |
+
super().__init__(
|
| 101 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 102 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 103 |
+
|
| 104 |
+
assert classifier is None, "Rectified Flow does not support classifier-guidance."
|
| 105 |
+
|
| 106 |
+
self.x_max, self.x_min = x_max, x_min
|
| 107 |
+
|
| 108 |
+
# ================= Discretization =================
|
| 109 |
+
# - Map the continuous range [0., 1.] to the discrete range [0, T-1]
|
| 110 |
+
if isinstance(discretization, str):
|
| 111 |
+
if discretization in SUPPORTED_DISCRETIZATIONS.keys():
|
| 112 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, 0.).to(device)
|
| 113 |
+
else:
|
| 114 |
+
Warning(f"Discretization method {discretization} is not supported. "
|
| 115 |
+
f"Using uniform discretization instead.")
|
| 116 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, 0.).to(device)
|
| 117 |
+
elif callable(discretization):
|
| 118 |
+
self.t_diffusion = discretization(diffusion_steps, 0.).to(device)
|
| 119 |
+
else:
|
| 120 |
+
raise ValueError("discretization must be a callable or a string")
|
| 121 |
+
|
| 122 |
+
@property
|
| 123 |
+
def supported_solvers(self):
|
| 124 |
+
return ["euler"]
|
| 125 |
+
|
| 126 |
+
@property
|
| 127 |
+
def clip_pred(self):
|
| 128 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 129 |
+
|
| 130 |
+
# ==================== Training: Straighten Flow ======================
|
| 131 |
+
|
| 132 |
+
def loss(self, x0, x1=None, condition=None):
|
| 133 |
+
|
| 134 |
+
# x1 is the samples of source distribution.
|
| 135 |
+
# If x1 is None, then we assume x1 is from a standard Gaussian distribution.
|
| 136 |
+
if x1 is None:
|
| 137 |
+
x1 = torch.randn_like(x0)
|
| 138 |
+
else:
|
| 139 |
+
assert x0.shape == x1.shape, "x0 and x1 must have the same shape"
|
| 140 |
+
|
| 141 |
+
t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device)
|
| 142 |
+
t_c = at_least_ndim(self.t_diffusion[t], x0.dim())
|
| 143 |
+
|
| 144 |
+
xt = t_c * x1 + (1 - t_c) * x0
|
| 145 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 146 |
+
|
| 147 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 148 |
+
|
| 149 |
+
loss = (self.model["diffusion"](xt, t, condition) - (x0 - x1)) ** 2
|
| 150 |
+
|
| 151 |
+
return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
|
| 152 |
+
|
| 153 |
+
def update(self, x0, condition=None, update_ema=True, x1=None, **kwargs):
|
| 154 |
+
"""One-step gradient update.
|
| 155 |
+
Inputs:
|
| 156 |
+
- x0: torch.Tensor
|
| 157 |
+
Samples from the target distribution.
|
| 158 |
+
- condition: Optional
|
| 159 |
+
Condition of x0. `None` indicates no condition.
|
| 160 |
+
- update_ema: bool
|
| 161 |
+
Whether to update the exponential moving average model.
|
| 162 |
+
- x1: torch.Tensor
|
| 163 |
+
Samples from the source distribution. `None` indicates standard Gaussian samples.
|
| 164 |
+
|
| 165 |
+
Outputs:
|
| 166 |
+
- log: dict
|
| 167 |
+
The log dictionary.
|
| 168 |
+
"""
|
| 169 |
+
loss = self.loss(x0, x1, condition)
|
| 170 |
+
|
| 171 |
+
loss.backward()
|
| 172 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 173 |
+
if self.grad_clip_norm else None
|
| 174 |
+
self.optimizer.step()
|
| 175 |
+
self.optimizer.zero_grad()
|
| 176 |
+
|
| 177 |
+
if update_ema:
|
| 178 |
+
self.ema_update()
|
| 179 |
+
|
| 180 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 181 |
+
|
| 182 |
+
return log
|
| 183 |
+
|
| 184 |
+
# ==================== Sampling: Solving a straight ODE flow ======================
|
| 185 |
+
|
| 186 |
+
def sample(
|
| 187 |
+
self,
|
| 188 |
+
# ---------- the known fixed portion ---------- #
|
| 189 |
+
prior: torch.Tensor,
|
| 190 |
+
x1: torch.Tensor = None,
|
| 191 |
+
# ----------------- sampling ----------------- #
|
| 192 |
+
n_samples: int = 1,
|
| 193 |
+
sample_steps: int = 5,
|
| 194 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 195 |
+
use_ema: bool = True,
|
| 196 |
+
temperature: float = 1.0,
|
| 197 |
+
# ------------------ guidance ------------------ #
|
| 198 |
+
condition_cfg=None,
|
| 199 |
+
mask_cfg=None,
|
| 200 |
+
w_cfg: float = 0.0,
|
| 201 |
+
condition_cg=None,
|
| 202 |
+
w_cg: float = 0.0,
|
| 203 |
+
# ----------- Diffusion-X sampling ----------
|
| 204 |
+
diffusion_x_sampling_steps: int = 0,
|
| 205 |
+
# ----------- Warm-Starting -----------
|
| 206 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 207 |
+
warm_start_forward_level: float = 0.3,
|
| 208 |
+
# ------------------ others ------------------ #
|
| 209 |
+
requires_grad: bool = False,
|
| 210 |
+
preserve_history: bool = False,
|
| 211 |
+
**kwargs,
|
| 212 |
+
):
|
| 213 |
+
"""Sampling.
|
| 214 |
+
|
| 215 |
+
Inputs:
|
| 216 |
+
- prior: torch.Tensor
|
| 217 |
+
The known fixed portion of the input data. Should be in the shape of generated data.
|
| 218 |
+
Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
|
| 219 |
+
- x1: torch.Tensor
|
| 220 |
+
The samples from the source distribution. `None` indicates standard Gaussian samples.
|
| 221 |
+
|
| 222 |
+
- n_samples: int
|
| 223 |
+
The number of samples to generate.
|
| 224 |
+
- sample_steps: int
|
| 225 |
+
The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
|
| 226 |
+
- sample_step_schedule: Union[str, Callable]
|
| 227 |
+
The schedule for the sampling steps.
|
| 228 |
+
- use_ema: bool
|
| 229 |
+
Whether to use the exponential moving average model.
|
| 230 |
+
- temperature: float
|
| 231 |
+
The temperature for sampling.
|
| 232 |
+
|
| 233 |
+
- condition_cfg: Optional
|
| 234 |
+
Condition for Classifier-free-guidance.
|
| 235 |
+
- mask_cfg: Optional
|
| 236 |
+
Mask for Classifier-guidance.
|
| 237 |
+
- w_cfg: float
|
| 238 |
+
Weight for Classifier-free-guidance.
|
| 239 |
+
- condition_cg: Optional
|
| 240 |
+
Condition for Classifier-guidance.
|
| 241 |
+
- w_cg: float
|
| 242 |
+
Weight for Classifier-guidance.
|
| 243 |
+
|
| 244 |
+
- diffusion_x_sampling_steps: int
|
| 245 |
+
The number of diffusion steps for diffusion-x sampling.
|
| 246 |
+
|
| 247 |
+
- warm_start_reference: Optional[torch.Tensor]
|
| 248 |
+
Reference data for warm-starting sampling. `None` indicates no warm-starting.
|
| 249 |
+
- warm_start_forward_level: float
|
| 250 |
+
The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
|
| 251 |
+
|
| 252 |
+
- requires_grad: bool
|
| 253 |
+
Whether to preserve gradients.
|
| 254 |
+
- preserve_history: bool
|
| 255 |
+
Whether to preserve the sampling history.
|
| 256 |
+
|
| 257 |
+
Outputs:
|
| 258 |
+
- x0: torch.Tensor
|
| 259 |
+
Generated samples. Be in the shape of `(n_samples, *x_shape)`.
|
| 260 |
+
- log: dict
|
| 261 |
+
The log dictionary.
|
| 262 |
+
"""
|
| 263 |
+
assert w_cg == 0.0 and condition_cg is None, "Rectified Flow does not support classifier-guidance."
|
| 264 |
+
|
| 265 |
+
prior = prior.to(self.device)
|
| 266 |
+
if isinstance(warm_start_reference, torch.Tensor):
|
| 267 |
+
diffusion_steps = int(warm_start_forward_level * self.diffusion_steps)
|
| 268 |
+
t_c = at_least_ndim(self.t_diffusion[diffusion_steps], prior.dim())
|
| 269 |
+
x1 = torch.randn_like(prior) * t_c + warm_start_reference * (1 - t_c)
|
| 270 |
+
else:
|
| 271 |
+
diffusion_steps = self.diffusion_steps
|
| 272 |
+
if x1 is None:
|
| 273 |
+
x1 = torch.randn_like(prior) * temperature
|
| 274 |
+
else:
|
| 275 |
+
assert prior.shape == x1.shape, "prior and x1 must have the same shape"
|
| 276 |
+
|
| 277 |
+
# ===================== Initialization =====================
|
| 278 |
+
log = {
|
| 279 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 280 |
+
|
| 281 |
+
model = self.model if not use_ema else self.model_ema
|
| 282 |
+
|
| 283 |
+
xt = x1.clone()
|
| 284 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 285 |
+
if preserve_history:
|
| 286 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 287 |
+
|
| 288 |
+
with torch.set_grad_enabled(requires_grad):
|
| 289 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 290 |
+
|
| 291 |
+
# ===================== Sampling Schedule ====================
|
| 292 |
+
if isinstance(sample_step_schedule, str):
|
| 293 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 294 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 295 |
+
diffusion_steps, sample_steps)
|
| 296 |
+
else:
|
| 297 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 298 |
+
elif callable(sample_step_schedule):
|
| 299 |
+
sample_step_schedule = sample_step_schedule(diffusion_steps, sample_steps)
|
| 300 |
+
else:
|
| 301 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 302 |
+
|
| 303 |
+
# ===================== Denoising Loop ========================
|
| 304 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
|
| 305 |
+
for i in reversed(loop_steps):
|
| 306 |
+
|
| 307 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 308 |
+
|
| 309 |
+
start_t, end_t = self.t_diffusion[sample_step_schedule[i]], self.t_diffusion[sample_step_schedule[i - 1]]
|
| 310 |
+
delta_t = start_t - end_t
|
| 311 |
+
|
| 312 |
+
# velocity
|
| 313 |
+
if w_cfg != 0.0 and w_cfg != 1.0 and condition_vec_cfg is not None:
|
| 314 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 315 |
+
vel_all = model["diffusion"](
|
| 316 |
+
xt.repeat(*repeat_dim), t.repeat(2),
|
| 317 |
+
torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0))
|
| 318 |
+
vel_cond, vel_uncond = vel_all.chunk(2, dim=0)
|
| 319 |
+
vel = w_cfg * vel_cond + (1 - w_cfg) * vel_uncond
|
| 320 |
+
elif w_cfg == 0.0 or condition_vec_cfg is None:
|
| 321 |
+
vel = model["diffusion"](xt, t, None)
|
| 322 |
+
else:
|
| 323 |
+
vel = model["diffusion"](xt, t, condition_vec_cfg)
|
| 324 |
+
|
| 325 |
+
# one-step update
|
| 326 |
+
xt = xt + delta_t * vel
|
| 327 |
+
|
| 328 |
+
# fix the known portion, and preserve the sampling history
|
| 329 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 330 |
+
if preserve_history:
|
| 331 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 332 |
+
|
| 333 |
+
# ================= Post-processing =================
|
| 334 |
+
if self.clip_pred:
|
| 335 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 336 |
+
|
| 337 |
+
return xt, log
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
class ContinuousRectifiedFlow(DiffusionModel):
|
| 341 |
+
"""Continuous-time Rectified Flow
|
| 342 |
+
|
| 343 |
+
Rectified flow learns a straight flow for transporting between two probability distributions.
|
| 344 |
+
The straight characteristic of this flow enables ODEs to achieve high-quality data generation
|
| 345 |
+
with very few sampling steps (achieving one-step sampling when the ODE is completely straight).
|
| 346 |
+
Rectified flow can continuously optimize model performance by refining the learned ODE through multiple reflow procedures.
|
| 347 |
+
Since it does not require Gaussian to be the source distribution, our implementation supports transformations
|
| 348 |
+
between two arbitrary distributions. However, if not specifically specified during training,
|
| 349 |
+
the model will default to learning the transport from a Gaussian distribution to the target distribution.
|
| 350 |
+
|
| 351 |
+
The ContinuousRectifiedFlow is the continuous-time version of Rectified flow.
|
| 352 |
+
It estimates the score function at any $t\in[0,T]$ and solves the reverse process in continuous time.
|
| 353 |
+
The sampling steps are required to be greater than 1.
|
| 354 |
+
|
| 355 |
+
Args:
|
| 356 |
+
- nn_diffusion: BaseNNDiffusion
|
| 357 |
+
The neural network backbone for the Diffusion model.
|
| 358 |
+
- nn_condition: Optional[BaseNNCondition]
|
| 359 |
+
The neural network backbone for the condition embedding.
|
| 360 |
+
|
| 361 |
+
- fix_mask: Union[list, np.ndarray, torch.Tensor]
|
| 362 |
+
Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 363 |
+
The mask should be in the shape of `x_shape`.
|
| 364 |
+
- loss_weight: Union[list, np.ndarray, torch.Tensor]
|
| 365 |
+
Add loss weight. The weight should be in the shape of `x_shape`.
|
| 366 |
+
|
| 367 |
+
- classifier: Optional[BaseClassifier]
|
| 368 |
+
Add a classifier to enable classifier-guidance.
|
| 369 |
+
|
| 370 |
+
- grad_clip_norm: Optional[float]
|
| 371 |
+
Gradient clipping norm.
|
| 372 |
+
- ema_rate: float
|
| 373 |
+
Exponential moving average rate.
|
| 374 |
+
- optim_params: Optional[dict]
|
| 375 |
+
Optimizer parameters.
|
| 376 |
+
|
| 377 |
+
- x_max: Optional[torch.Tensor]
|
| 378 |
+
The maximum value for the input data. `None` indicates no constraint.
|
| 379 |
+
- x_min: Optional[torch.Tensor]
|
| 380 |
+
The minimum value for the input data. `None` indicates no constraint.
|
| 381 |
+
|
| 382 |
+
- device: Union[torch.device, str]
|
| 383 |
+
The device to run the model.
|
| 384 |
+
"""
|
| 385 |
+
def __init__(
|
| 386 |
+
self,
|
| 387 |
+
|
| 388 |
+
# ----------------- Neural Networks ----------------- #
|
| 389 |
+
nn_diffusion: BaseNNDiffusion,
|
| 390 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 391 |
+
|
| 392 |
+
# ----------------- Masks ----------------- #
|
| 393 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 394 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 395 |
+
# Add loss weight
|
| 396 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 397 |
+
|
| 398 |
+
# ------------------ Plugins ---------------- #
|
| 399 |
+
# Add a classifier to enable classifier-guidance
|
| 400 |
+
classifier: Optional[BaseClassifier] = None,
|
| 401 |
+
|
| 402 |
+
# ------------------ Training Params ---------------- #
|
| 403 |
+
grad_clip_norm: Optional[float] = None,
|
| 404 |
+
ema_rate: float = 0.995,
|
| 405 |
+
optim_params: Optional[dict] = None,
|
| 406 |
+
|
| 407 |
+
# ------------------- Diffusion Params ------------------- #
|
| 408 |
+
x_max: Optional[torch.Tensor] = None,
|
| 409 |
+
x_min: Optional[torch.Tensor] = None,
|
| 410 |
+
|
| 411 |
+
device: Union[torch.device, str] = "cpu"
|
| 412 |
+
):
|
| 413 |
+
super().__init__(
|
| 414 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 415 |
+
0, ema_rate, optim_params, device)
|
| 416 |
+
|
| 417 |
+
assert classifier is None, "Rectified Flow does not support classifier-guidance."
|
| 418 |
+
|
| 419 |
+
self.x_max, self.x_min = x_max, x_min
|
| 420 |
+
|
| 421 |
+
@property
|
| 422 |
+
def supported_solvers(self):
|
| 423 |
+
return ["euler"]
|
| 424 |
+
|
| 425 |
+
@property
|
| 426 |
+
def clip_pred(self):
|
| 427 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 428 |
+
|
| 429 |
+
# ==================== Training: Straighten Flow ======================
|
| 430 |
+
|
| 431 |
+
def loss(self, x0, x1=None, condition=None):
|
| 432 |
+
|
| 433 |
+
# x1 is the samples of source distribution.
|
| 434 |
+
# If x1 is None, then we assume x1 is from a standard Gaussian distribution.
|
| 435 |
+
if x1 is None:
|
| 436 |
+
x1 = torch.randn_like(x0)
|
| 437 |
+
else:
|
| 438 |
+
assert x0.shape == x1.shape, "x0 and x1 must have the same shape"
|
| 439 |
+
|
| 440 |
+
t = torch.rand((x0.shape[0],), device=self.device)
|
| 441 |
+
|
| 442 |
+
xt = x0 + at_least_ndim(t, x0.dim()) * (x1 - x0)
|
| 443 |
+
|
| 444 |
+
xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
|
| 445 |
+
|
| 446 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 447 |
+
|
| 448 |
+
loss = (self.model["diffusion"](xt, t, condition) - (x0 - x1)) ** 2
|
| 449 |
+
|
| 450 |
+
return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
|
| 451 |
+
|
| 452 |
+
def update(self, x0, condition=None, update_ema=True, x1=None, **kwargs):
|
| 453 |
+
"""One-step gradient update.
|
| 454 |
+
Inputs:
|
| 455 |
+
- x0: torch.Tensor
|
| 456 |
+
Samples from the target distribution.
|
| 457 |
+
- condition: Optional
|
| 458 |
+
Condition of x0. `None` indicates no condition.
|
| 459 |
+
- update_ema: bool
|
| 460 |
+
Whether to update the exponential moving average model.
|
| 461 |
+
- x1: torch.Tensor
|
| 462 |
+
Samples from the source distribution. `None` indicates standard Gaussian samples.
|
| 463 |
+
|
| 464 |
+
Outputs:
|
| 465 |
+
- log: dict
|
| 466 |
+
The log dictionary.
|
| 467 |
+
"""
|
| 468 |
+
loss = self.loss(x0, x1, condition)
|
| 469 |
+
|
| 470 |
+
loss.backward()
|
| 471 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 472 |
+
if self.grad_clip_norm else None
|
| 473 |
+
self.optimizer.step()
|
| 474 |
+
self.optimizer.zero_grad()
|
| 475 |
+
|
| 476 |
+
if update_ema:
|
| 477 |
+
self.ema_update()
|
| 478 |
+
|
| 479 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 480 |
+
|
| 481 |
+
return log
|
| 482 |
+
|
| 483 |
+
# ==================== Sampling: Solving a straight ODE flow ======================
|
| 484 |
+
|
| 485 |
+
def sample(
|
| 486 |
+
self,
|
| 487 |
+
# ---------- the known fixed portion ---------- #
|
| 488 |
+
prior: torch.Tensor,
|
| 489 |
+
x1: torch.Tensor = None,
|
| 490 |
+
# ----------------- sampling ----------------- #
|
| 491 |
+
n_samples: int = 1,
|
| 492 |
+
sample_steps: int = 5,
|
| 493 |
+
sample_step_schedule: Union[str, Callable] = "uniform_continuous",
|
| 494 |
+
use_ema: bool = True,
|
| 495 |
+
temperature: float = 1.0,
|
| 496 |
+
# ------------------ guidance ------------------ #
|
| 497 |
+
condition_cfg=None,
|
| 498 |
+
mask_cfg=None,
|
| 499 |
+
w_cfg: float = 0.0,
|
| 500 |
+
condition_cg=None,
|
| 501 |
+
w_cg: float = 0.0,
|
| 502 |
+
# ----------- Diffusion-X sampling ----------
|
| 503 |
+
diffusion_x_sampling_steps: int = 0,
|
| 504 |
+
# ----------- Warm-Starting -----------
|
| 505 |
+
warm_start_reference: Optional[torch.Tensor] = None,
|
| 506 |
+
warm_start_forward_level: float = 0.3,
|
| 507 |
+
# ------------------ others ------------------ #
|
| 508 |
+
requires_grad: bool = False,
|
| 509 |
+
preserve_history: bool = False,
|
| 510 |
+
**kwargs,
|
| 511 |
+
):
|
| 512 |
+
"""Sampling.
|
| 513 |
+
|
| 514 |
+
Inputs:
|
| 515 |
+
- prior: torch.Tensor
|
| 516 |
+
The known fixed portion of the input data. Should be in the shape of generated data.
|
| 517 |
+
Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
|
| 518 |
+
- x1: torch.Tensor
|
| 519 |
+
The samples from the source distribution. `None` indicates standard Gaussian samples.
|
| 520 |
+
|
| 521 |
+
- n_samples: int
|
| 522 |
+
The number of samples to generate.
|
| 523 |
+
- sample_steps: int
|
| 524 |
+
The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
|
| 525 |
+
- sample_step_schedule: Union[str, Callable]
|
| 526 |
+
The schedule for the sampling steps.
|
| 527 |
+
- use_ema: bool
|
| 528 |
+
Whether to use the exponential moving average model.
|
| 529 |
+
- temperature: float
|
| 530 |
+
The temperature for sampling.
|
| 531 |
+
|
| 532 |
+
- condition_cfg: Optional
|
| 533 |
+
Condition for Classifier-free-guidance.
|
| 534 |
+
- mask_cfg: Optional
|
| 535 |
+
Mask for Classifier-guidance.
|
| 536 |
+
- w_cfg: float
|
| 537 |
+
Weight for Classifier-free-guidance.
|
| 538 |
+
- condition_cg: Optional
|
| 539 |
+
Condition for Classifier-guidance.
|
| 540 |
+
- w_cg: float
|
| 541 |
+
Weight for Classifier-guidance.
|
| 542 |
+
|
| 543 |
+
- diffusion_x_sampling_steps: int
|
| 544 |
+
The number of diffusion steps for diffusion-x sampling.
|
| 545 |
+
|
| 546 |
+
- requires_grad: bool
|
| 547 |
+
Whether to preserve gradients.
|
| 548 |
+
- preserve_history: bool
|
| 549 |
+
Whether to preserve the sampling history.
|
| 550 |
+
|
| 551 |
+
Outputs:
|
| 552 |
+
- x0: torch.Tensor
|
| 553 |
+
Generated samples. Be in the shape of `(n_samples, *x_shape)`.
|
| 554 |
+
- log: dict
|
| 555 |
+
The log dictionary.
|
| 556 |
+
"""
|
| 557 |
+
assert w_cg == 0.0 and condition_cg is None, "Rectified Flow does not support classifier-guidance."
|
| 558 |
+
|
| 559 |
+
prior = prior.to(self.device)
|
| 560 |
+
if isinstance(warm_start_reference, torch.Tensor):
|
| 561 |
+
t_c = torch.ones_like(prior) * warm_start_forward_level
|
| 562 |
+
x1 = torch.randn_like(prior) * t_c + warm_start_reference * (1 - t_c)
|
| 563 |
+
else:
|
| 564 |
+
if x1 is None:
|
| 565 |
+
x1 = torch.randn_like(prior) * temperature
|
| 566 |
+
else:
|
| 567 |
+
assert prior.shape == x1.shape, "prior and x1 must have the same shape"
|
| 568 |
+
|
| 569 |
+
# ===================== Initialization =====================
|
| 570 |
+
log = {
|
| 571 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 572 |
+
|
| 573 |
+
model = self.model if not use_ema else self.model_ema
|
| 574 |
+
|
| 575 |
+
xt = x1.clone()
|
| 576 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 577 |
+
if preserve_history:
|
| 578 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 579 |
+
|
| 580 |
+
with torch.set_grad_enabled(requires_grad):
|
| 581 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 582 |
+
|
| 583 |
+
# ===================== Sampling Schedule ====================
|
| 584 |
+
if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
|
| 585 |
+
final_t = warm_start_forward_level
|
| 586 |
+
else:
|
| 587 |
+
final_t = 1.
|
| 588 |
+
if isinstance(sample_step_schedule, str):
|
| 589 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 590 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 591 |
+
[0., final_t], sample_steps)
|
| 592 |
+
else:
|
| 593 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 594 |
+
elif callable(sample_step_schedule):
|
| 595 |
+
sample_step_schedule = sample_step_schedule([0., final_t], sample_steps)
|
| 596 |
+
else:
|
| 597 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 598 |
+
|
| 599 |
+
# ===================== Denoising Loop ========================
|
| 600 |
+
loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
|
| 601 |
+
for i in reversed(loop_steps):
|
| 602 |
+
|
| 603 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
|
| 604 |
+
|
| 605 |
+
delta_t = sample_step_schedule[i] - sample_step_schedule[i - 1]
|
| 606 |
+
|
| 607 |
+
# velocity
|
| 608 |
+
if w_cfg != 0.0 and w_cfg != 1.0 and condition_vec_cfg is not None:
|
| 609 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 610 |
+
vel_all = model["diffusion"](
|
| 611 |
+
xt.repeat(*repeat_dim), t.repeat(2),
|
| 612 |
+
torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0))
|
| 613 |
+
vel_cond, vel_uncond = vel_all.chunk(2, dim=0)
|
| 614 |
+
vel = w_cfg * vel_cond + (1 - w_cfg) * vel_uncond
|
| 615 |
+
elif w_cfg == 0.0 or condition_vec_cfg is None:
|
| 616 |
+
vel = model["diffusion"](xt, t, None)
|
| 617 |
+
else:
|
| 618 |
+
vel = model["diffusion"](xt, t, condition_vec_cfg)
|
| 619 |
+
|
| 620 |
+
# one-step update
|
| 621 |
+
xt = xt + delta_t * vel
|
| 622 |
+
|
| 623 |
+
# fix the known portion, and preserve the sampling history
|
| 624 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 625 |
+
if preserve_history:
|
| 626 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 627 |
+
|
| 628 |
+
# ================= Post-processing =================
|
| 629 |
+
if self.clip_pred:
|
| 630 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 631 |
+
|
| 632 |
+
return xt, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/veode.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from typing import Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from .edm import EDMArchetecture
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class VEODE(EDMArchetecture):
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
|
| 17 |
+
# ----------------- Neural Networks ----------------- #
|
| 18 |
+
nn_diffusion: BaseNNDiffusion,
|
| 19 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 20 |
+
|
| 21 |
+
# ----------------- Masks ----------------- #
|
| 22 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 23 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 24 |
+
# Add loss weight
|
| 25 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 26 |
+
|
| 27 |
+
# ------------------ Plugs ---------------- #
|
| 28 |
+
# Add a classifier to enable classifier-guidance
|
| 29 |
+
classifier: Optional[BaseClassifier] = None,
|
| 30 |
+
|
| 31 |
+
# ------------------ Params ---------------- #
|
| 32 |
+
grad_clip_norm: Optional[float] = None,
|
| 33 |
+
diffusion_steps: int = 1000,
|
| 34 |
+
ema_rate: float = 0.995,
|
| 35 |
+
optim_params: Optional[dict] = None,
|
| 36 |
+
|
| 37 |
+
# ------------------- EDM Params ------------------- #
|
| 38 |
+
sigma_min: float = 0.02,
|
| 39 |
+
sigma_max: float = 100.,
|
| 40 |
+
|
| 41 |
+
device: Union[torch.device, str] = "cpu"
|
| 42 |
+
):
|
| 43 |
+
super().__init__(
|
| 44 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 45 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 46 |
+
|
| 47 |
+
self.sigma_min, self.sigma_max = sigma_min, sigma_max
|
| 48 |
+
|
| 49 |
+
def set_sample_steps(self, N: int):
|
| 50 |
+
self.sample_steps = N
|
| 51 |
+
self.sigma_s = self.sigma_max * (self.sigma_min / self.sigma_max) ** (
|
| 52 |
+
torch.arange(N, device=self.device) / (N - 1))
|
| 53 |
+
self.t_s = self.sigma_s ** 2
|
| 54 |
+
self.scale_s = torch.ones_like(self.sigma_s) * 1.0
|
| 55 |
+
self.dot_sigma_s = 1 / (2 * self.sigma_s)
|
| 56 |
+
self.dot_scale_s = torch.zeros_like(self.sigma_s)
|
| 57 |
+
self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
|
| 58 |
+
self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
|
| 59 |
+
|
| 60 |
+
def c_skip(self, sigma): return torch.ones_like(sigma)
|
| 61 |
+
|
| 62 |
+
def c_out(self, sigma): return sigma
|
| 63 |
+
|
| 64 |
+
def c_in(self, sigma): return torch.ones_like(sigma)
|
| 65 |
+
|
| 66 |
+
def c_noise(self, sigma): return (0.5 * sigma).log()
|
| 67 |
+
|
| 68 |
+
def loss_weighting(self, sigma): return 1 / (sigma ** 2)
|
| 69 |
+
|
| 70 |
+
def sample_noise_distribution(self, N):
|
| 71 |
+
log_sigma = torch.rand((N, 1), device=self.device) * np.log(self.sigma_max / self.sigma_min) + np.log(
|
| 72 |
+
self.sigma_min)
|
| 73 |
+
return log_sigma.exp()
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpode.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from typing import Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from .edm import EDMArchetecture
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class VPODE(EDMArchetecture):
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
|
| 17 |
+
# ----------------- Neural Networks ----------------- #
|
| 18 |
+
nn_diffusion: BaseNNDiffusion,
|
| 19 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 20 |
+
|
| 21 |
+
# ----------------- Masks ----------------- #
|
| 22 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 23 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 24 |
+
# Add loss weight
|
| 25 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 26 |
+
|
| 27 |
+
# ------------------ Plugs ---------------- #
|
| 28 |
+
# Add a classifier to enable classifier-guidance
|
| 29 |
+
classifier: Optional[BaseClassifier] = None,
|
| 30 |
+
|
| 31 |
+
# ------------------ Params ---------------- #
|
| 32 |
+
grad_clip_norm: Optional[float] = None,
|
| 33 |
+
diffusion_steps: int = 1000,
|
| 34 |
+
ema_rate: float = 0.995,
|
| 35 |
+
optim_params: Optional[dict] = None,
|
| 36 |
+
|
| 37 |
+
# ------------------- EDM Params ------------------- #
|
| 38 |
+
beta_min: float = 0.1,
|
| 39 |
+
beta_max: float = 20.,
|
| 40 |
+
eps_s: float = 1e-3,
|
| 41 |
+
eps_t: float = 1e-5,
|
| 42 |
+
|
| 43 |
+
device: Union[torch.device, str] = "cpu"
|
| 44 |
+
):
|
| 45 |
+
super().__init__(
|
| 46 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 47 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 48 |
+
|
| 49 |
+
self.beta_min, self.beta_max = beta_min, beta_max
|
| 50 |
+
self.eps_s, self.eps_t = eps_s, eps_t
|
| 51 |
+
self.beta_d = beta_max - beta_min
|
| 52 |
+
|
| 53 |
+
def set_sample_steps(self, N: int):
|
| 54 |
+
self.sample_steps = N
|
| 55 |
+
self.t_s = torch.arange(N, device=self.device) / (N - 1) * (1e-3 - 1) + 1
|
| 56 |
+
self.sigma_s = ((0.5 * self.beta_d * (self.t_s ** 2) + self.beta_min * self.t_s).exp() - 1.).sqrt()
|
| 57 |
+
self.scale_s = 1 / (1 + self.sigma_s ** 2).sqrt()
|
| 58 |
+
self.dot_sigma_s = (0.5 * (self.sigma_s ** 2 + 1) * (self.beta_d * self.t_s + self.beta_min) / self.sigma_s)
|
| 59 |
+
self.dot_scale_s = -self.sigma_s / (1 + self.sigma_s ** 2) ** 1.5 * self.dot_sigma_s
|
| 60 |
+
self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
|
| 61 |
+
self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
|
| 62 |
+
|
| 63 |
+
def c_skip(self, sigma): return torch.ones_like(sigma)
|
| 64 |
+
|
| 65 |
+
def c_out(self, sigma): return -sigma
|
| 66 |
+
|
| 67 |
+
def c_in(self, sigma): return 1 / (1 + sigma ** 2).sqrt()
|
| 68 |
+
|
| 69 |
+
def c_noise(self, sigma):
|
| 70 |
+
scale = 1 / (1 + sigma ** 2).sqrt()
|
| 71 |
+
t = ((self.beta_min ** 2 - 4 * self.beta_d * scale.log()).sqrt() - self.beta_min) / self.beta_d
|
| 72 |
+
return ((self.diffusion_steps - 1) * t).long()
|
| 73 |
+
|
| 74 |
+
def loss_weighting(self, sigma): return 1 / (sigma ** 2)
|
| 75 |
+
|
| 76 |
+
def sample_noise_distribution(self, N):
|
| 77 |
+
t = torch.rand((N, 1), device=self.device) * (1 - self.eps_t) + self.eps_t
|
| 78 |
+
return ((0.5 * self.beta_d * t ** 2 + self.beta_min * t).exp() - 1).sqrt()
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpsde.py
ADDED
|
@@ -0,0 +1,1099 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Union, Callable, Dict
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from cleandiffuser.classifier import BaseClassifier
|
| 8 |
+
from cleandiffuser.nn_condition import BaseNNCondition
|
| 9 |
+
from cleandiffuser.nn_diffusion import BaseNNDiffusion
|
| 10 |
+
from cleandiffuser.utils import (
|
| 11 |
+
at_least_ndim,
|
| 12 |
+
SUPPORTED_NOISE_SCHEDULES, SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
|
| 13 |
+
from .basic import DiffusionModel
|
| 14 |
+
|
| 15 |
+
SUPPORTED_SOLVERS = [
|
| 16 |
+
"ddpm", "ddim", "ode_dpmsolver++_1", "sde_dpmsolver++_1", "ode_dpmsolver++_2M"
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
|
| 21 |
+
"""
|
| 22 |
+
x_theta = (x - sigma * eps_theta) / alpha
|
| 23 |
+
"""
|
| 24 |
+
return (x - sigma * eps_theta) / alpha
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def xtheta_to_epstheta(x, alpha, sigma, x_theta):
|
| 28 |
+
"""
|
| 29 |
+
eps_theta = (x - alpha * x_theta) / sigma
|
| 30 |
+
"""
|
| 31 |
+
return (x - alpha * x_theta) / sigma
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class DiscreteVPSDE(DiffusionModel):
|
| 35 |
+
|
| 36 |
+
def __init__(
|
| 37 |
+
self,
|
| 38 |
+
|
| 39 |
+
# ----------------- Neural Networks ----------------- #
|
| 40 |
+
nn_diffusion: BaseNNDiffusion,
|
| 41 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 42 |
+
|
| 43 |
+
# ----------------- Masks ----------------- #
|
| 44 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 45 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 46 |
+
# Add loss weight
|
| 47 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 48 |
+
|
| 49 |
+
# ------------------ Plugins ---------------- #
|
| 50 |
+
# Add a classifier to enable classifier-guidance
|
| 51 |
+
classifier: Optional[BaseClassifier] = None,
|
| 52 |
+
|
| 53 |
+
# ------------------ Training Params ---------------- #
|
| 54 |
+
grad_clip_norm: Optional[float] = None,
|
| 55 |
+
ema_rate: float = 0.995,
|
| 56 |
+
optim_params: Optional[dict] = None,
|
| 57 |
+
|
| 58 |
+
# ------------------- Diffusion Params ------------------- #
|
| 59 |
+
epsilon: float = 1e-3,
|
| 60 |
+
diffusion_steps: int = 1000,
|
| 61 |
+
|
| 62 |
+
discretization: Union[str, Callable] = "uniform",
|
| 63 |
+
noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
|
| 64 |
+
noise_schedule_params: Optional[dict] = None,
|
| 65 |
+
|
| 66 |
+
x_max: Optional[torch.Tensor] = None,
|
| 67 |
+
x_min: Optional[torch.Tensor] = None,
|
| 68 |
+
|
| 69 |
+
predict_noise: bool = True,
|
| 70 |
+
|
| 71 |
+
device: Union[torch.device, str] = "cpu"
|
| 72 |
+
):
|
| 73 |
+
super().__init__(
|
| 74 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 75 |
+
diffusion_steps, ema_rate, optim_params, device)
|
| 76 |
+
|
| 77 |
+
self.predict_noise = predict_noise
|
| 78 |
+
self.epsilon = epsilon
|
| 79 |
+
self.x_max, self.x_min = x_max, x_min
|
| 80 |
+
|
| 81 |
+
if 1. / diffusion_steps < epsilon:
|
| 82 |
+
raise ValueError("epsilon is too large for the number of diffusion steps")
|
| 83 |
+
|
| 84 |
+
# ================= Discretization =================
|
| 85 |
+
# - Map the continuous range [epsilon, 1.] to the discrete range [0, T-1]
|
| 86 |
+
if isinstance(discretization, str):
|
| 87 |
+
if discretization in SUPPORTED_DISCRETIZATIONS.keys():
|
| 88 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, epsilon).to(device)
|
| 89 |
+
else:
|
| 90 |
+
Warning(f"Discretization method {discretization} is not supported. "
|
| 91 |
+
f"Using uniform discretization instead.")
|
| 92 |
+
self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, epsilon).to(device)
|
| 93 |
+
elif callable(discretization):
|
| 94 |
+
self.t_diffusion = discretization(diffusion_steps, epsilon).to(device)
|
| 95 |
+
else:
|
| 96 |
+
raise ValueError("discretization must be a callable or a string")
|
| 97 |
+
|
| 98 |
+
# ================= Noise Schedule =================
|
| 99 |
+
if isinstance(noise_schedule, str):
|
| 100 |
+
if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
|
| 101 |
+
self.alpha, self.sigma = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["forward"](
|
| 102 |
+
self.t_diffusion, **(noise_schedule_params or {}))
|
| 103 |
+
else:
|
| 104 |
+
raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
|
| 105 |
+
elif isinstance(noise_schedule, dict):
|
| 106 |
+
self.alpha, self.sigma = noise_schedule["forward"](self.t_diffusion, **(noise_schedule_params or {}))
|
| 107 |
+
else:
|
| 108 |
+
raise ValueError("noise_schedule must be a callable or a string")
|
| 109 |
+
|
| 110 |
+
self.logSNR = torch.log(self.alpha / self.sigma)
|
| 111 |
+
|
| 112 |
+
# # ================= Extra Steps for DPM-Solver 2 ==================
|
| 113 |
+
# self.inter_logSNR = torch.zeros_like(self.logSNR)
|
| 114 |
+
# self.inter_logSNR[1:] = (self.logSNR[1:] + self.logSNR[:-1]) / 2.
|
| 115 |
+
# # inter_logSNR[0] is not correctly calculated, but it will not be used.
|
| 116 |
+
# if isinstance(noise_schedule, str):
|
| 117 |
+
# if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
|
| 118 |
+
# self.inter_t_diffusion = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["reverse"](
|
| 119 |
+
# logSNR=self.inter_logSNR, **(noise_schedule_params or {}))
|
| 120 |
+
# else:
|
| 121 |
+
# raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
|
| 122 |
+
# elif isinstance(noise_schedule, dict):
|
| 123 |
+
# self.inter_t_diffusion = noise_schedule["reverse"](
|
| 124 |
+
# logSNR=self.inter_logSNR, **(noise_schedule_params or {}))
|
| 125 |
+
# else:
|
| 126 |
+
# raise ValueError("noise_schedule must be a callable or a string")
|
| 127 |
+
|
| 128 |
+
@property
|
| 129 |
+
def clip_pred(self):
|
| 130 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 131 |
+
|
| 132 |
+
# ==================== Training: Score Matching ======================
|
| 133 |
+
|
| 134 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 135 |
+
|
| 136 |
+
t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
|
| 137 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 138 |
+
|
| 139 |
+
alpha, sigma = at_least_ndim(self.alpha[t], x0.dim()), at_least_ndim(self.sigma[t], x0.dim())
|
| 140 |
+
|
| 141 |
+
xt = alpha * x0 + sigma * eps
|
| 142 |
+
xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
|
| 143 |
+
|
| 144 |
+
return xt, t, eps
|
| 145 |
+
|
| 146 |
+
def loss(self, x0, condition=None):
|
| 147 |
+
|
| 148 |
+
xt, t, eps = self.add_noise(x0)
|
| 149 |
+
|
| 150 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 151 |
+
|
| 152 |
+
if self.predict_noise:
|
| 153 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 154 |
+
else:
|
| 155 |
+
loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
|
| 156 |
+
|
| 157 |
+
return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
|
| 158 |
+
|
| 159 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 160 |
+
|
| 161 |
+
loss = self.loss(x0, condition)
|
| 162 |
+
|
| 163 |
+
loss.backward()
|
| 164 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 165 |
+
if self.grad_clip_norm else None
|
| 166 |
+
self.optimizer.step()
|
| 167 |
+
self.optimizer.zero_grad()
|
| 168 |
+
|
| 169 |
+
if update_ema:
|
| 170 |
+
self.ema_update()
|
| 171 |
+
|
| 172 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 173 |
+
|
| 174 |
+
return log
|
| 175 |
+
|
| 176 |
+
def update_classifier(self, x0, condition):
|
| 177 |
+
|
| 178 |
+
xt, t, eps = self.add_noise(x0)
|
| 179 |
+
|
| 180 |
+
log = self.classifier.update(xt, t, condition)
|
| 181 |
+
|
| 182 |
+
return log
|
| 183 |
+
|
| 184 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 185 |
+
|
| 186 |
+
def classifier_guidance(
|
| 187 |
+
self, xt, t, alpha, sigma,
|
| 188 |
+
model, condition=None, w: float = 1.0,
|
| 189 |
+
pred=None):
|
| 190 |
+
"""
|
| 191 |
+
Guided Sampling CG:
|
| 192 |
+
bar_eps = eps - w * sigma * grad
|
| 193 |
+
bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
|
| 194 |
+
"""
|
| 195 |
+
if pred is None:
|
| 196 |
+
pred = model["diffusion"](xt, t, None)
|
| 197 |
+
if self.classifier is None or w == 0.0 or condition is None:
|
| 198 |
+
return pred, None
|
| 199 |
+
else:
|
| 200 |
+
log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
|
| 201 |
+
if self.predict_noise:
|
| 202 |
+
pred = pred - w * sigma * grad
|
| 203 |
+
else:
|
| 204 |
+
pred = pred + w * ((sigma ** 2) / alpha) * grad
|
| 205 |
+
|
| 206 |
+
return pred, log_p
|
| 207 |
+
|
| 208 |
+
def classifier_free_guidance(
|
| 209 |
+
self, xt, t,
|
| 210 |
+
model, condition=None, w: float = 1.0,
|
| 211 |
+
pred=None, pred_uncond=None,
|
| 212 |
+
requires_grad: bool = False):
|
| 213 |
+
"""
|
| 214 |
+
Guided Sampling CFG:
|
| 215 |
+
bar_eps = w * pred + (1 - w) * pred_uncond
|
| 216 |
+
bar_x0 = w * pred + (1 - w) * pred_uncond
|
| 217 |
+
"""
|
| 218 |
+
with torch.set_grad_enabled(requires_grad):
|
| 219 |
+
if w != 0.0 and w != 1.0:
|
| 220 |
+
if pred is None or pred_uncond is None:
|
| 221 |
+
b = xt.shape[0]
|
| 222 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 223 |
+
condition = torch.cat([condition, torch.zeros_like(condition)], 0)
|
| 224 |
+
pred_all = model["diffusion"](
|
| 225 |
+
xt.repeat(*repeat_dim), t.repeat(2), condition)
|
| 226 |
+
pred, pred_uncond = pred_all[:b], pred_all[b:]
|
| 227 |
+
elif w == 0.0:
|
| 228 |
+
pred = 0.
|
| 229 |
+
pred_uncond = model["diffusion"](xt, t, None)
|
| 230 |
+
else:
|
| 231 |
+
pred = model["diffusion"](xt, t, condition)
|
| 232 |
+
pred_uncond = 0.
|
| 233 |
+
|
| 234 |
+
if self.predict_noise or not self.predict_noise:
|
| 235 |
+
bar_pred = w * pred + (1 - w) * pred_uncond
|
| 236 |
+
else:
|
| 237 |
+
bar_pred = pred
|
| 238 |
+
|
| 239 |
+
return bar_pred
|
| 240 |
+
|
| 241 |
+
def clip_prediction(self, pred, xt, alpha, sigma):
|
| 242 |
+
"""
|
| 243 |
+
Clip the prediction at each sampling step to stablize the generation.
|
| 244 |
+
(xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
|
| 245 |
+
x_min <= x0 <= x_max
|
| 246 |
+
"""
|
| 247 |
+
if self.predict_noise:
|
| 248 |
+
if self.clip_pred:
|
| 249 |
+
upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
|
| 250 |
+
lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
|
| 251 |
+
pred = pred.clip(lower_bound, upper_bound)
|
| 252 |
+
else:
|
| 253 |
+
if self.clip_pred:
|
| 254 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 255 |
+
|
| 256 |
+
return pred
|
| 257 |
+
|
| 258 |
+
def guided_sampling(
|
| 259 |
+
self, xt, t, alpha, sigma,
|
| 260 |
+
model,
|
| 261 |
+
condition_cfg=None, w_cfg: float = 0.0,
|
| 262 |
+
condition_cg=None, w_cg: float = 0.0,
|
| 263 |
+
requires_grad: bool = False):
|
| 264 |
+
"""
|
| 265 |
+
One-step epsilon/x0 prediction with guidance.
|
| 266 |
+
"""
|
| 267 |
+
|
| 268 |
+
pred = self.classifier_free_guidance(
|
| 269 |
+
xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
|
| 270 |
+
|
| 271 |
+
pred, logp = self.classifier_guidance(
|
| 272 |
+
xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
|
| 273 |
+
|
| 274 |
+
return pred, logp
|
| 275 |
+
|
| 276 |
+
def ddpm_solver(
|
| 277 |
+
self,
|
| 278 |
+
# ---------- the known fixed portion ---------- #
|
| 279 |
+
prior: torch.Tensor,
|
| 280 |
+
# ----------------- sampling ----------------- #
|
| 281 |
+
n_samples: int = 1,
|
| 282 |
+
sample_steps: int = 5,
|
| 283 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 284 |
+
use_ema: bool = True,
|
| 285 |
+
temperature: float = 1.0,
|
| 286 |
+
# ------------------ guidance ------------------ #
|
| 287 |
+
condition_cfg=None,
|
| 288 |
+
mask_cfg=None,
|
| 289 |
+
w_cfg: float = 0.0,
|
| 290 |
+
condition_cg=None,
|
| 291 |
+
w_cg: float = 0.0,
|
| 292 |
+
# ------------------ others ------------------ #
|
| 293 |
+
requires_grad: bool = False,
|
| 294 |
+
preserve_history: bool = False,
|
| 295 |
+
**kwargs,
|
| 296 |
+
):
|
| 297 |
+
|
| 298 |
+
# ===================== Initialization =====================
|
| 299 |
+
log = {
|
| 300 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 301 |
+
|
| 302 |
+
model = self.model if not use_ema else self.model_ema
|
| 303 |
+
|
| 304 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 305 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 306 |
+
if preserve_history:
|
| 307 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 308 |
+
|
| 309 |
+
with torch.set_grad_enabled(requires_grad):
|
| 310 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 311 |
+
condition_vec_cg = condition_cg
|
| 312 |
+
|
| 313 |
+
# ===================== Sampling Schedule ====================
|
| 314 |
+
if isinstance(sample_step_schedule, str):
|
| 315 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 316 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 317 |
+
self.diffusion_steps, sample_steps)
|
| 318 |
+
else:
|
| 319 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 320 |
+
elif callable(sample_step_schedule):
|
| 321 |
+
sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
|
| 322 |
+
else:
|
| 323 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 324 |
+
|
| 325 |
+
alphas = self.alpha[sample_step_schedule]
|
| 326 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 327 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 328 |
+
hs = torch.zeros_like(logSNRs)
|
| 329 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 330 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 331 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 332 |
+
|
| 333 |
+
# ===================== Denoising Loop ========================
|
| 334 |
+
|
| 335 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 336 |
+
|
| 337 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 338 |
+
|
| 339 |
+
# guided sampling
|
| 340 |
+
pred, logp = self.guided_sampling(
|
| 341 |
+
xt, t, alphas[i], sigmas[i],
|
| 342 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 343 |
+
|
| 344 |
+
# clip the prediction
|
| 345 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 346 |
+
|
| 347 |
+
# transform to eps_theta
|
| 348 |
+
eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
|
| 349 |
+
|
| 350 |
+
# one-step update
|
| 351 |
+
xt = (
|
| 352 |
+
(alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
|
| 353 |
+
(sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
|
| 354 |
+
if i > 1:
|
| 355 |
+
xt += (stds[i] * torch.randn_like(xt))
|
| 356 |
+
|
| 357 |
+
# fix the known portion, and preserve the sampling history
|
| 358 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 359 |
+
if preserve_history:
|
| 360 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 361 |
+
|
| 362 |
+
# ================= Post-processing =================
|
| 363 |
+
if self.classifier is not None:
|
| 364 |
+
with torch.no_grad():
|
| 365 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 366 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 367 |
+
log["log_p"] = logp
|
| 368 |
+
|
| 369 |
+
if self.clip_pred:
|
| 370 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 371 |
+
|
| 372 |
+
return xt, log
|
| 373 |
+
|
| 374 |
+
def ddim_solver(
|
| 375 |
+
self,
|
| 376 |
+
# ---------- the known fixed portion ---------- #
|
| 377 |
+
prior: torch.Tensor,
|
| 378 |
+
# ----------------- sampling ----------------- #
|
| 379 |
+
n_samples: int = 1,
|
| 380 |
+
sample_steps: int = 5,
|
| 381 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 382 |
+
use_ema: bool = True,
|
| 383 |
+
temperature: float = 1.0,
|
| 384 |
+
# ------------------ guidance ------------------ #
|
| 385 |
+
condition_cfg=None,
|
| 386 |
+
mask_cfg=None,
|
| 387 |
+
w_cfg: float = 0.0,
|
| 388 |
+
condition_cg=None,
|
| 389 |
+
w_cg: float = 0.0,
|
| 390 |
+
# ------------------ others ------------------ #
|
| 391 |
+
requires_grad: bool = False,
|
| 392 |
+
preserve_history: bool = False,
|
| 393 |
+
**kwargs,
|
| 394 |
+
):
|
| 395 |
+
|
| 396 |
+
# ===================== Initialization =====================
|
| 397 |
+
log = {
|
| 398 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 399 |
+
|
| 400 |
+
model = self.model if not use_ema else self.model_ema
|
| 401 |
+
|
| 402 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 403 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 404 |
+
if preserve_history:
|
| 405 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 406 |
+
|
| 407 |
+
with torch.set_grad_enabled(requires_grad):
|
| 408 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 409 |
+
condition_vec_cg = condition_cg
|
| 410 |
+
|
| 411 |
+
# ===================== Sampling Schedule ====================
|
| 412 |
+
if isinstance(sample_step_schedule, str):
|
| 413 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 414 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 415 |
+
self.diffusion_steps, sample_steps)
|
| 416 |
+
else:
|
| 417 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 418 |
+
elif callable(sample_step_schedule):
|
| 419 |
+
sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
|
| 420 |
+
else:
|
| 421 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 422 |
+
|
| 423 |
+
alphas = self.alpha[sample_step_schedule]
|
| 424 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 425 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 426 |
+
hs = torch.zeros_like(logSNRs)
|
| 427 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 428 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 429 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 430 |
+
|
| 431 |
+
# ===================== Denoising Loop ========================
|
| 432 |
+
|
| 433 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 434 |
+
|
| 435 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 436 |
+
|
| 437 |
+
# guided sampling
|
| 438 |
+
pred, logp = self.guided_sampling(
|
| 439 |
+
xt, t, alphas[i], sigmas[i],
|
| 440 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 441 |
+
|
| 442 |
+
# clip the prediction
|
| 443 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 444 |
+
|
| 445 |
+
# transform to eps_theta
|
| 446 |
+
eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
|
| 447 |
+
|
| 448 |
+
# one-step update
|
| 449 |
+
xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
|
| 450 |
+
|
| 451 |
+
# fix the known portion, and preserve the sampling history
|
| 452 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 453 |
+
if preserve_history:
|
| 454 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 455 |
+
|
| 456 |
+
# ================= Post-processing =================
|
| 457 |
+
if self.classifier is not None:
|
| 458 |
+
with torch.no_grad():
|
| 459 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 460 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 461 |
+
log["log_p"] = logp
|
| 462 |
+
|
| 463 |
+
if self.clip_pred:
|
| 464 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 465 |
+
|
| 466 |
+
return xt, log
|
| 467 |
+
|
| 468 |
+
def ode_dpm_solver_pp_1(
|
| 469 |
+
self,
|
| 470 |
+
# ---------- the known fixed portion ---------- #
|
| 471 |
+
prior: torch.Tensor,
|
| 472 |
+
# ----------------- sampling ----------------- #
|
| 473 |
+
n_samples: int = 1,
|
| 474 |
+
sample_steps: int = 5,
|
| 475 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 476 |
+
use_ema: bool = True,
|
| 477 |
+
temperature: float = 1.0,
|
| 478 |
+
# ------------------ guidance ------------------ #
|
| 479 |
+
condition_cfg=None,
|
| 480 |
+
mask_cfg=None,
|
| 481 |
+
w_cfg: float = 0.0,
|
| 482 |
+
condition_cg=None,
|
| 483 |
+
w_cg: float = 0.0,
|
| 484 |
+
# ------------------ others ------------------ #
|
| 485 |
+
requires_grad: bool = False,
|
| 486 |
+
preserve_history: bool = False,
|
| 487 |
+
**kwargs,
|
| 488 |
+
):
|
| 489 |
+
|
| 490 |
+
# ===================== Initialization =====================
|
| 491 |
+
log = {
|
| 492 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 493 |
+
|
| 494 |
+
model = self.model if not use_ema else self.model_ema
|
| 495 |
+
|
| 496 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 497 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 498 |
+
if preserve_history:
|
| 499 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 500 |
+
|
| 501 |
+
with torch.set_grad_enabled(requires_grad):
|
| 502 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 503 |
+
condition_vec_cg = condition_cg
|
| 504 |
+
|
| 505 |
+
# ===================== Sampling Schedule ====================
|
| 506 |
+
if isinstance(sample_step_schedule, str):
|
| 507 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 508 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 509 |
+
self.diffusion_steps, sample_steps)
|
| 510 |
+
else:
|
| 511 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 512 |
+
elif callable(sample_step_schedule):
|
| 513 |
+
sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
|
| 514 |
+
else:
|
| 515 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 516 |
+
|
| 517 |
+
alphas = self.alpha[sample_step_schedule]
|
| 518 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 519 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 520 |
+
hs = torch.zeros_like(logSNRs)
|
| 521 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 522 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 523 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 524 |
+
|
| 525 |
+
# ===================== Denoising Loop ========================
|
| 526 |
+
|
| 527 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 528 |
+
|
| 529 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 530 |
+
|
| 531 |
+
# guided sampling
|
| 532 |
+
pred, logp = self.guided_sampling(
|
| 533 |
+
xt, t, alphas[i], sigmas[i],
|
| 534 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 535 |
+
|
| 536 |
+
# clip the prediction
|
| 537 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 538 |
+
|
| 539 |
+
# transform to eps_theta
|
| 540 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 541 |
+
|
| 542 |
+
# one-step update
|
| 543 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 544 |
+
|
| 545 |
+
# fix the known portion, and preserve the sampling history
|
| 546 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 547 |
+
if preserve_history:
|
| 548 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 549 |
+
|
| 550 |
+
# ================= Post-processing =================
|
| 551 |
+
if self.classifier is not None:
|
| 552 |
+
with torch.no_grad():
|
| 553 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 554 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 555 |
+
log["log_p"] = logp
|
| 556 |
+
|
| 557 |
+
if self.clip_pred:
|
| 558 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 559 |
+
|
| 560 |
+
return xt, log
|
| 561 |
+
|
| 562 |
+
def sde_dpm_solver_pp_1(
|
| 563 |
+
self,
|
| 564 |
+
# ---------- the known fixed portion ---------- #
|
| 565 |
+
prior: torch.Tensor,
|
| 566 |
+
# ----------------- sampling ----------------- #
|
| 567 |
+
n_samples: int = 1,
|
| 568 |
+
sample_steps: int = 5,
|
| 569 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 570 |
+
use_ema: bool = True,
|
| 571 |
+
temperature: float = 1.0,
|
| 572 |
+
# ------------------ guidance ------------------ #
|
| 573 |
+
condition_cfg=None,
|
| 574 |
+
mask_cfg=None,
|
| 575 |
+
w_cfg: float = 0.0,
|
| 576 |
+
condition_cg=None,
|
| 577 |
+
w_cg: float = 0.0,
|
| 578 |
+
# ------------------ others ------------------ #
|
| 579 |
+
requires_grad: bool = False,
|
| 580 |
+
preserve_history: bool = False,
|
| 581 |
+
**kwargs,
|
| 582 |
+
):
|
| 583 |
+
|
| 584 |
+
# ===================== Initialization =====================
|
| 585 |
+
log = {
|
| 586 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 587 |
+
|
| 588 |
+
model = self.model if not use_ema else self.model_ema
|
| 589 |
+
|
| 590 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 591 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 592 |
+
if preserve_history:
|
| 593 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 594 |
+
|
| 595 |
+
with torch.set_grad_enabled(requires_grad):
|
| 596 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 597 |
+
condition_vec_cg = condition_cg
|
| 598 |
+
|
| 599 |
+
# ===================== Sampling Schedule ====================
|
| 600 |
+
if isinstance(sample_step_schedule, str):
|
| 601 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 602 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 603 |
+
self.diffusion_steps, sample_steps)
|
| 604 |
+
else:
|
| 605 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 606 |
+
elif callable(sample_step_schedule):
|
| 607 |
+
sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
|
| 608 |
+
else:
|
| 609 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 610 |
+
|
| 611 |
+
alphas = self.alpha[sample_step_schedule]
|
| 612 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 613 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 614 |
+
hs = torch.zeros_like(logSNRs)
|
| 615 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 616 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 617 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 618 |
+
|
| 619 |
+
# ===================== Denoising Loop ========================
|
| 620 |
+
|
| 621 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 622 |
+
|
| 623 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 624 |
+
|
| 625 |
+
# guided sampling
|
| 626 |
+
pred, logp = self.guided_sampling(
|
| 627 |
+
xt, t, alphas[i], sigmas[i],
|
| 628 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 629 |
+
|
| 630 |
+
# clip the prediction
|
| 631 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 632 |
+
|
| 633 |
+
# transform to eps_theta
|
| 634 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 635 |
+
|
| 636 |
+
# one-step update
|
| 637 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 638 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 639 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 640 |
+
|
| 641 |
+
# fix the known portion, and preserve the sampling history
|
| 642 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 643 |
+
if preserve_history:
|
| 644 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 645 |
+
|
| 646 |
+
# ================= Post-processing =================
|
| 647 |
+
if self.classifier is not None:
|
| 648 |
+
with torch.no_grad():
|
| 649 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 650 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 651 |
+
log["log_p"] = logp
|
| 652 |
+
|
| 653 |
+
if self.clip_pred:
|
| 654 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 655 |
+
|
| 656 |
+
return xt, log
|
| 657 |
+
|
| 658 |
+
def ode_dpm_solver_pp_2M(
|
| 659 |
+
self,
|
| 660 |
+
# ---------- the known fixed portion ---------- #
|
| 661 |
+
prior: torch.Tensor,
|
| 662 |
+
# ----------------- sampling ----------------- #
|
| 663 |
+
n_samples: int = 1,
|
| 664 |
+
sample_steps: int = 5,
|
| 665 |
+
sample_step_schedule: Union[str, Callable] = "uniform",
|
| 666 |
+
use_ema: bool = True,
|
| 667 |
+
temperature: float = 1.0,
|
| 668 |
+
# ------------------ guidance ------------------ #
|
| 669 |
+
condition_cfg=None,
|
| 670 |
+
mask_cfg=None,
|
| 671 |
+
w_cfg: float = 0.0,
|
| 672 |
+
condition_cg=None,
|
| 673 |
+
w_cg: float = 0.0,
|
| 674 |
+
# ------------------ others ------------------ #
|
| 675 |
+
requires_grad: bool = False,
|
| 676 |
+
preserve_history: bool = False,
|
| 677 |
+
**kwargs,
|
| 678 |
+
):
|
| 679 |
+
|
| 680 |
+
# ===================== Initialization =====================
|
| 681 |
+
log = {
|
| 682 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 683 |
+
|
| 684 |
+
model = self.model if not use_ema else self.model_ema
|
| 685 |
+
|
| 686 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 687 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 688 |
+
if preserve_history:
|
| 689 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 690 |
+
|
| 691 |
+
with torch.set_grad_enabled(requires_grad):
|
| 692 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 693 |
+
condition_vec_cg = condition_cg
|
| 694 |
+
|
| 695 |
+
# ===================== Sampling Schedule ====================
|
| 696 |
+
if isinstance(sample_step_schedule, str):
|
| 697 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 698 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 699 |
+
self.diffusion_steps, sample_steps)
|
| 700 |
+
else:
|
| 701 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 702 |
+
elif callable(sample_step_schedule):
|
| 703 |
+
sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
|
| 704 |
+
else:
|
| 705 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 706 |
+
|
| 707 |
+
alphas = self.alpha[sample_step_schedule]
|
| 708 |
+
sigmas = self.sigma[sample_step_schedule]
|
| 709 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 710 |
+
hs = torch.zeros_like(logSNRs)
|
| 711 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 712 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 713 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 714 |
+
|
| 715 |
+
buffer = []
|
| 716 |
+
|
| 717 |
+
# ===================== Denoising Loop ========================
|
| 718 |
+
|
| 719 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 720 |
+
|
| 721 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
|
| 722 |
+
|
| 723 |
+
# guided sampling
|
| 724 |
+
pred, logp = self.guided_sampling(
|
| 725 |
+
xt, t, alphas[i], sigmas[i],
|
| 726 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 727 |
+
|
| 728 |
+
# clip the prediction
|
| 729 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 730 |
+
|
| 731 |
+
# transform to x_theta
|
| 732 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 733 |
+
|
| 734 |
+
buffer.append(x_theta)
|
| 735 |
+
|
| 736 |
+
# one-step update
|
| 737 |
+
if i < sample_steps:
|
| 738 |
+
r = hs[i + 1] / hs[i]
|
| 739 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 740 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
|
| 741 |
+
else:
|
| 742 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 743 |
+
|
| 744 |
+
# fix the known portion, and preserve the sampling history
|
| 745 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 746 |
+
if preserve_history:
|
| 747 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 748 |
+
|
| 749 |
+
# ================= Post-processing =================
|
| 750 |
+
if self.classifier is not None:
|
| 751 |
+
with torch.no_grad():
|
| 752 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 753 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 754 |
+
log["log_p"] = logp
|
| 755 |
+
|
| 756 |
+
if self.clip_pred:
|
| 757 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 758 |
+
|
| 759 |
+
return xt, log
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
class ContinuousVPSDE(DiffusionModel):
|
| 763 |
+
|
| 764 |
+
def __init__(
|
| 765 |
+
self,
|
| 766 |
+
|
| 767 |
+
# ----------------- Neural Networks ----------------- #
|
| 768 |
+
nn_diffusion: BaseNNDiffusion,
|
| 769 |
+
nn_condition: Optional[BaseNNCondition] = None,
|
| 770 |
+
|
| 771 |
+
# ----------------- Masks ----------------- #
|
| 772 |
+
# Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
|
| 773 |
+
fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 774 |
+
# Add loss weight
|
| 775 |
+
loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
|
| 776 |
+
|
| 777 |
+
# ------------------ Plugins ---------------- #
|
| 778 |
+
# Add a classifier to enable classifier-guidance
|
| 779 |
+
classifier: Optional[BaseClassifier] = None,
|
| 780 |
+
|
| 781 |
+
# ------------------ Training Params ---------------- #
|
| 782 |
+
grad_clip_norm: Optional[float] = None,
|
| 783 |
+
ema_rate: float = 0.995,
|
| 784 |
+
optim_params: Optional[dict] = None,
|
| 785 |
+
|
| 786 |
+
# ------------------- Diffusion Params ------------------- #
|
| 787 |
+
epsilon: float = 1e-3,
|
| 788 |
+
|
| 789 |
+
discretization: Union[str, Callable] = "uniform",
|
| 790 |
+
noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
|
| 791 |
+
noise_schedule_params: Optional[dict] = None,
|
| 792 |
+
|
| 793 |
+
x_max: Optional[torch.Tensor] = None,
|
| 794 |
+
x_min: Optional[torch.Tensor] = None,
|
| 795 |
+
|
| 796 |
+
predict_noise: bool = True,
|
| 797 |
+
|
| 798 |
+
device: Union[torch.device, str] = "cpu"
|
| 799 |
+
):
|
| 800 |
+
super().__init__(
|
| 801 |
+
nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
|
| 802 |
+
0, ema_rate, optim_params, device)
|
| 803 |
+
|
| 804 |
+
self.predict_noise = predict_noise
|
| 805 |
+
self.epsilon = epsilon
|
| 806 |
+
self.x_max, self.x_min = x_max, x_min
|
| 807 |
+
|
| 808 |
+
# ==================== Continuous Time-step Range ====================
|
| 809 |
+
if noise_schedule == "cosine":
|
| 810 |
+
self.t_diffusion = [epsilon, 0.9946]
|
| 811 |
+
else:
|
| 812 |
+
self.t_diffusion = [epsilon, 1.]
|
| 813 |
+
|
| 814 |
+
# ===================== Noise Schedule ======================
|
| 815 |
+
if isinstance(noise_schedule, str):
|
| 816 |
+
if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
|
| 817 |
+
self.noise_schedule_funcs = SUPPORTED_NOISE_SCHEDULES[noise_schedule]
|
| 818 |
+
self.noise_schedule_params = noise_schedule_params
|
| 819 |
+
else:
|
| 820 |
+
raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
|
| 821 |
+
elif isinstance(noise_schedule, dict):
|
| 822 |
+
self.noise_schedule_funcs = noise_schedule
|
| 823 |
+
self.noise_schedule_params = noise_schedule_params
|
| 824 |
+
else:
|
| 825 |
+
raise ValueError("noise_schedule must be a callable or a string")
|
| 826 |
+
|
| 827 |
+
@property
|
| 828 |
+
def clip_pred(self):
|
| 829 |
+
return (self.x_max is not None) or (self.x_min is not None)
|
| 830 |
+
|
| 831 |
+
# ==================== Training: Score Matching ======================
|
| 832 |
+
|
| 833 |
+
def add_noise(self, x0, t=None, eps=None):
|
| 834 |
+
|
| 835 |
+
t = (torch.rand((x0.shape[0],), device=self.device) *
|
| 836 |
+
(self.t_diffusion[1] - self.t_diffusion[0]) + self.t_diffusion[0]) if t is None else t
|
| 837 |
+
|
| 838 |
+
eps = torch.randn_like(x0) if eps is None else eps
|
| 839 |
+
|
| 840 |
+
alpha, sigma = self.noise_schedule_funcs["forward"](t, **(self.noise_schedule_params or {}))
|
| 841 |
+
alpha = at_least_ndim(alpha, x0.dim())
|
| 842 |
+
sigma = at_least_ndim(sigma, x0.dim())
|
| 843 |
+
|
| 844 |
+
xt = alpha * x0 + sigma * eps
|
| 845 |
+
xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
|
| 846 |
+
|
| 847 |
+
return xt, t, eps
|
| 848 |
+
|
| 849 |
+
def loss(self, x0, condition=None):
|
| 850 |
+
|
| 851 |
+
xt, t, eps = self.add_noise(x0)
|
| 852 |
+
|
| 853 |
+
condition = self.model["condition"](condition) if condition is not None else None
|
| 854 |
+
|
| 855 |
+
if self.predict_noise:
|
| 856 |
+
loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
|
| 857 |
+
else:
|
| 858 |
+
loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
|
| 859 |
+
|
| 860 |
+
return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
|
| 861 |
+
|
| 862 |
+
def update(self, x0, condition=None, update_ema=True, **kwargs):
|
| 863 |
+
|
| 864 |
+
loss = self.loss(x0, condition)
|
| 865 |
+
|
| 866 |
+
loss.backward()
|
| 867 |
+
grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
|
| 868 |
+
if self.grad_clip_norm else None
|
| 869 |
+
self.optimizer.step()
|
| 870 |
+
self.optimizer.zero_grad()
|
| 871 |
+
|
| 872 |
+
if update_ema:
|
| 873 |
+
self.ema_update()
|
| 874 |
+
|
| 875 |
+
log = {"loss": loss.item(), "grad_norm": grad_norm}
|
| 876 |
+
|
| 877 |
+
return log
|
| 878 |
+
|
| 879 |
+
def update_classifier(self, x0, condition):
|
| 880 |
+
|
| 881 |
+
xt, t, eps = self.add_noise(x0)
|
| 882 |
+
|
| 883 |
+
log = self.classifier.update(xt, t, condition)
|
| 884 |
+
|
| 885 |
+
return log
|
| 886 |
+
|
| 887 |
+
# ==================== Sampling: Solving SDE/ODE ======================
|
| 888 |
+
|
| 889 |
+
def classifier_guidance(
|
| 890 |
+
self, xt, t, alpha, sigma,
|
| 891 |
+
model, condition=None, w: float = 1.0,
|
| 892 |
+
pred=None):
|
| 893 |
+
"""
|
| 894 |
+
Guided Sampling CG:
|
| 895 |
+
bar_eps = eps - w * sigma * grad
|
| 896 |
+
bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
|
| 897 |
+
"""
|
| 898 |
+
if pred is None:
|
| 899 |
+
pred = model["diffusion"](xt, t, None)
|
| 900 |
+
if self.classifier is None or w == 0.0 or condition is None:
|
| 901 |
+
return pred, None
|
| 902 |
+
else:
|
| 903 |
+
log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
|
| 904 |
+
if self.predict_noise:
|
| 905 |
+
pred = pred - w * sigma * grad
|
| 906 |
+
else:
|
| 907 |
+
pred = pred + w * ((sigma ** 2) / alpha) * grad
|
| 908 |
+
|
| 909 |
+
return pred, log_p
|
| 910 |
+
|
| 911 |
+
def classifier_free_guidance(
|
| 912 |
+
self, xt, t,
|
| 913 |
+
model, condition=None, w: float = 1.0,
|
| 914 |
+
pred=None, pred_uncond=None,
|
| 915 |
+
requires_grad: bool = False):
|
| 916 |
+
"""
|
| 917 |
+
Guided Sampling CFG:
|
| 918 |
+
bar_eps = w * pred + (1 - w) * pred_uncond
|
| 919 |
+
bar_x0 = w * pred + (1 - w) * pred_uncond
|
| 920 |
+
"""
|
| 921 |
+
with torch.set_grad_enabled(requires_grad):
|
| 922 |
+
if w != 0.0 and w != 1.0:
|
| 923 |
+
if pred is None or pred_uncond is None:
|
| 924 |
+
b = xt.shape[0]
|
| 925 |
+
repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
|
| 926 |
+
condition = torch.cat([condition, torch.zeros_like(condition)], 0)
|
| 927 |
+
pred_all = model["diffusion"](
|
| 928 |
+
xt.repeat(*repeat_dim), t.repeat(2), condition)
|
| 929 |
+
pred, pred_uncond = pred_all[:b], pred_all[b:]
|
| 930 |
+
elif w == 0.0:
|
| 931 |
+
pred = 0.
|
| 932 |
+
pred_uncond = model["diffusion"](xt, t, None)
|
| 933 |
+
else:
|
| 934 |
+
pred = model["diffusion"](xt, t, condition)
|
| 935 |
+
pred_uncond = 0.
|
| 936 |
+
|
| 937 |
+
if self.predict_noise or not self.predict_noise:
|
| 938 |
+
bar_pred = w * pred + (1 - w) * pred_uncond
|
| 939 |
+
else:
|
| 940 |
+
bar_pred = pred
|
| 941 |
+
|
| 942 |
+
return bar_pred
|
| 943 |
+
|
| 944 |
+
def clip_prediction(self, pred, xt, alpha, sigma):
|
| 945 |
+
"""
|
| 946 |
+
Clip the prediction at each sampling step to stablize the generation.
|
| 947 |
+
(xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
|
| 948 |
+
x_min <= x0 <= x_max
|
| 949 |
+
"""
|
| 950 |
+
if self.clip_pred:
|
| 951 |
+
if self.predict_noise:
|
| 952 |
+
upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
|
| 953 |
+
lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
|
| 954 |
+
pred = pred.clip(lower_bound, upper_bound)
|
| 955 |
+
else:
|
| 956 |
+
pred = pred.clip(self.x_min, self.x_max)
|
| 957 |
+
return pred
|
| 958 |
+
|
| 959 |
+
def guided_sampling(
|
| 960 |
+
self, xt, t, alpha, sigma,
|
| 961 |
+
model,
|
| 962 |
+
condition_cfg=None, w_cfg: float = 0.0,
|
| 963 |
+
condition_cg=None, w_cg: float = 0.0,
|
| 964 |
+
requires_grad: bool = False):
|
| 965 |
+
"""
|
| 966 |
+
One-step epsilon/x0 prediction with guidance.
|
| 967 |
+
"""
|
| 968 |
+
|
| 969 |
+
pred = self.classifier_free_guidance(
|
| 970 |
+
xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
|
| 971 |
+
|
| 972 |
+
pred, logp = self.classifier_guidance(
|
| 973 |
+
xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
|
| 974 |
+
|
| 975 |
+
return pred, logp
|
| 976 |
+
|
| 977 |
+
def sample(
|
| 978 |
+
self,
|
| 979 |
+
# ---------- the known fixed portion ---------- #
|
| 980 |
+
prior: torch.Tensor,
|
| 981 |
+
# ----------------- sampling ----------------- #
|
| 982 |
+
solver: str = "ddpm",
|
| 983 |
+
n_samples: int = 1,
|
| 984 |
+
sample_steps: int = 5,
|
| 985 |
+
sample_step_schedule: Union[str, Callable] = "uniform_continuous",
|
| 986 |
+
use_ema: bool = True,
|
| 987 |
+
temperature: float = 1.0,
|
| 988 |
+
# ------------------ guidance ------------------ #
|
| 989 |
+
condition_cfg=None,
|
| 990 |
+
mask_cfg=None,
|
| 991 |
+
w_cfg: float = 0.0,
|
| 992 |
+
condition_cg=None,
|
| 993 |
+
w_cg: float = 0.0,
|
| 994 |
+
# ------------------ others ------------------ #
|
| 995 |
+
requires_grad: bool = False,
|
| 996 |
+
preserve_history: bool = False,
|
| 997 |
+
**kwargs,
|
| 998 |
+
):
|
| 999 |
+
assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
|
| 1000 |
+
|
| 1001 |
+
# ===================== Initialization =====================
|
| 1002 |
+
log = {
|
| 1003 |
+
"sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
|
| 1004 |
+
|
| 1005 |
+
model = self.model if not use_ema else self.model_ema9999
|
| 1006 |
+
|
| 1007 |
+
xt = torch.randn_like(prior, device=self.device) * temperature
|
| 1008 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 1009 |
+
if preserve_history:
|
| 1010 |
+
log["sample_history"][:, 0] = xt.cpu().numpy()
|
| 1011 |
+
|
| 1012 |
+
with torch.set_grad_enabled(requires_grad):
|
| 1013 |
+
condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
|
| 1014 |
+
condition_vec_cg = condition_cg
|
| 1015 |
+
|
| 1016 |
+
# ===================== Sampling Schedule ====================
|
| 1017 |
+
if isinstance(sample_step_schedule, str):
|
| 1018 |
+
if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
|
| 1019 |
+
sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
|
| 1020 |
+
self.t_diffusion, sample_steps)
|
| 1021 |
+
else:
|
| 1022 |
+
raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
|
| 1023 |
+
elif callable(sample_step_schedule):
|
| 1024 |
+
sample_step_schedule = sample_step_schedule(self.t_diffusion, sample_steps)
|
| 1025 |
+
else:
|
| 1026 |
+
raise ValueError("sample_step_schedule must be a callable or a string")
|
| 1027 |
+
|
| 1028 |
+
alphas, sigmas = self.noise_schedule_funcs["forward"](
|
| 1029 |
+
sample_step_schedule, **(self.noise_schedule_params or {}))
|
| 1030 |
+
logSNRs = torch.log(alphas / sigmas)
|
| 1031 |
+
hs = torch.zeros_like(logSNRs)
|
| 1032 |
+
hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
|
| 1033 |
+
stds = torch.zeros((sample_steps + 1,), device=self.device)
|
| 1034 |
+
stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
|
| 1035 |
+
|
| 1036 |
+
buffer = []
|
| 1037 |
+
|
| 1038 |
+
# ===================== Denoising Loop ========================
|
| 1039 |
+
|
| 1040 |
+
for i in reversed(range(1, sample_steps + 1)):
|
| 1041 |
+
|
| 1042 |
+
t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
|
| 1043 |
+
|
| 1044 |
+
# guided sampling
|
| 1045 |
+
pred, logp = self.guided_sampling(
|
| 1046 |
+
xt, t, alphas[i], sigmas[i],
|
| 1047 |
+
model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
|
| 1048 |
+
|
| 1049 |
+
# clip the prediction
|
| 1050 |
+
pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
|
| 1051 |
+
|
| 1052 |
+
# transform to eps_theta
|
| 1053 |
+
eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
|
| 1054 |
+
x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
|
| 1055 |
+
|
| 1056 |
+
# one-step update
|
| 1057 |
+
if solver == "ddpm":
|
| 1058 |
+
xt = (
|
| 1059 |
+
(alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
|
| 1060 |
+
(sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
|
| 1061 |
+
if i > 1:
|
| 1062 |
+
xt += (stds[i] * torch.randn_like(xt))
|
| 1063 |
+
|
| 1064 |
+
elif solver == "ddim":
|
| 1065 |
+
xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
|
| 1066 |
+
|
| 1067 |
+
elif solver == "ode_dpmsolver++_1":
|
| 1068 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 1069 |
+
|
| 1070 |
+
elif solver == "sde_dpmsolver++_1":
|
| 1071 |
+
xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
|
| 1072 |
+
alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
|
| 1073 |
+
sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
|
| 1074 |
+
|
| 1075 |
+
elif solver == "ode_dpmsolver++_2M":
|
| 1076 |
+
buffer.append(x_theta)
|
| 1077 |
+
if i < sample_steps:
|
| 1078 |
+
r = hs[i + 1] / hs[i]
|
| 1079 |
+
D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
|
| 1080 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
|
| 1081 |
+
else:
|
| 1082 |
+
xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
|
| 1083 |
+
|
| 1084 |
+
# fix the known portion, and preserve the sampling history
|
| 1085 |
+
xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
|
| 1086 |
+
if preserve_history:
|
| 1087 |
+
log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
|
| 1088 |
+
|
| 1089 |
+
# ================= Post-processing =================
|
| 1090 |
+
if self.classifier is not None:
|
| 1091 |
+
with torch.no_grad():
|
| 1092 |
+
t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
|
| 1093 |
+
logp = self.classifier.logp(xt, t, condition_vec_cg)
|
| 1094 |
+
log["log_p"] = logp
|
| 1095 |
+
|
| 1096 |
+
if self.clip_pred:
|
| 1097 |
+
xt = xt.clip(self.x_min, self.x_max)
|
| 1098 |
+
|
| 1099 |
+
return xt, log
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/async_vector_env.py
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Back ported methods: call, set_attr from v0.26
|
| 3 |
+
Disabled auto-reset after done
|
| 4 |
+
Added render method.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import multiprocessing as mp
|
| 10 |
+
import time
|
| 11 |
+
import sys
|
| 12 |
+
from enum import Enum
|
| 13 |
+
from copy import deepcopy
|
| 14 |
+
|
| 15 |
+
from gym import logger
|
| 16 |
+
from gym.vector.vector_env import VectorEnv
|
| 17 |
+
from gym.error import (
|
| 18 |
+
AlreadyPendingCallError,
|
| 19 |
+
NoAsyncCallError,
|
| 20 |
+
ClosedEnvironmentError,
|
| 21 |
+
CustomSpaceError,
|
| 22 |
+
)
|
| 23 |
+
from gym.vector.utils import (
|
| 24 |
+
create_shared_memory,
|
| 25 |
+
create_empty_array,
|
| 26 |
+
write_to_shared_memory,
|
| 27 |
+
read_from_shared_memory,
|
| 28 |
+
concatenate,
|
| 29 |
+
CloudpickleWrapper,
|
| 30 |
+
clear_mpi_env_vars,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
__all__ = ["AsyncVectorEnv"]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class AsyncState(Enum):
|
| 37 |
+
DEFAULT = "default"
|
| 38 |
+
WAITING_RESET = "reset"
|
| 39 |
+
WAITING_STEP = "step"
|
| 40 |
+
WAITING_CALL = "call"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class AsyncVectorEnv(VectorEnv):
|
| 44 |
+
"""Vectorized environment that runs multiple environments in parallel. It
|
| 45 |
+
uses `multiprocessing` processes, and pipes for communication.
|
| 46 |
+
Parameters
|
| 47 |
+
----------
|
| 48 |
+
env_fns : iterable of callable
|
| 49 |
+
Functions that create the environments.
|
| 50 |
+
observation_space : `gym.spaces.Space` instance, optional
|
| 51 |
+
Observation space of a single environment. If `None`, then the
|
| 52 |
+
observation space of the first environment is taken.
|
| 53 |
+
action_space : `gym.spaces.Space` instance, optional
|
| 54 |
+
Action space of a single environment. If `None`, then the action space
|
| 55 |
+
of the first environment is taken.
|
| 56 |
+
shared_memory : bool (default: `True`)
|
| 57 |
+
If `True`, then the observations from the worker processes are
|
| 58 |
+
communicated back through shared variables. This can improve the
|
| 59 |
+
efficiency if the observations are large (e.g. images).
|
| 60 |
+
copy : bool (default: `True`)
|
| 61 |
+
If `True`, then the `reset` and `step` methods return a copy of the
|
| 62 |
+
observations.
|
| 63 |
+
context : str, optional
|
| 64 |
+
Context for multiprocessing. If `None`, then the default context is used.
|
| 65 |
+
Only available in Python 3.
|
| 66 |
+
daemon : bool (default: `True`)
|
| 67 |
+
If `True`, then subprocesses have `daemon` flag turned on; that is, they
|
| 68 |
+
will quit if the head process quits. However, `daemon=True` prevents
|
| 69 |
+
subprocesses to spawn children, so for some environments you may want
|
| 70 |
+
to have it set to `False`
|
| 71 |
+
worker : function, optional
|
| 72 |
+
WARNING - advanced mode option! If set, then use that worker in a subprocess
|
| 73 |
+
instead of a default one. Can be useful to override some inner vector env
|
| 74 |
+
logic, for instance, how resets on done are handled. Provides high
|
| 75 |
+
degree of flexibility and a high chance to shoot yourself in the foot; thus,
|
| 76 |
+
if you are writing your own worker, it is recommended to start from the code
|
| 77 |
+
for `_worker` (or `_worker_shared_memory`) method below, and add changes
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
def __init__(
|
| 81 |
+
self,
|
| 82 |
+
env_fns,
|
| 83 |
+
dummy_env_fn=None,
|
| 84 |
+
observation_space=None,
|
| 85 |
+
action_space=None,
|
| 86 |
+
shared_memory=True,
|
| 87 |
+
copy=True,
|
| 88 |
+
context=None,
|
| 89 |
+
daemon=True,
|
| 90 |
+
worker=None,
|
| 91 |
+
):
|
| 92 |
+
ctx = mp.get_context(context)
|
| 93 |
+
self.env_fns = env_fns
|
| 94 |
+
self.shared_memory = shared_memory
|
| 95 |
+
self.copy = copy
|
| 96 |
+
|
| 97 |
+
# Added dummy_env_fn to fix OpenGL error in Mujoco
|
| 98 |
+
# disable any OpenGL rendering in dummy_env_fn, since it
|
| 99 |
+
# will conflict with OpenGL context in the forked child process
|
| 100 |
+
if dummy_env_fn is None:
|
| 101 |
+
dummy_env_fn = env_fns[0]
|
| 102 |
+
dummy_env = dummy_env_fn()
|
| 103 |
+
self.metadata = dummy_env.metadata
|
| 104 |
+
|
| 105 |
+
if (observation_space is None) or (action_space is None):
|
| 106 |
+
observation_space = observation_space or dummy_env.observation_space
|
| 107 |
+
action_space = action_space or dummy_env.action_space
|
| 108 |
+
dummy_env.close()
|
| 109 |
+
del dummy_env
|
| 110 |
+
super(AsyncVectorEnv, self).__init__(
|
| 111 |
+
num_envs=len(env_fns),
|
| 112 |
+
observation_space=observation_space,
|
| 113 |
+
action_space=action_space,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
if self.shared_memory:
|
| 117 |
+
try:
|
| 118 |
+
_obs_buffer = create_shared_memory(
|
| 119 |
+
self.single_observation_space, n=self.num_envs, ctx=ctx
|
| 120 |
+
)
|
| 121 |
+
self.observations = read_from_shared_memory(
|
| 122 |
+
_obs_buffer, self.single_observation_space, n=self.num_envs
|
| 123 |
+
)
|
| 124 |
+
except CustomSpaceError:
|
| 125 |
+
raise ValueError(
|
| 126 |
+
"Using `shared_memory=True` in `AsyncVectorEnv` "
|
| 127 |
+
"is incompatible with non-standard Gym observation spaces "
|
| 128 |
+
"(i.e. custom spaces inheriting from `gym.Space`), and is "
|
| 129 |
+
"only compatible with default Gym spaces (e.g. `Box`, "
|
| 130 |
+
"`Tuple`, `Dict`) for batching. Set `shared_memory=False` "
|
| 131 |
+
"if you use custom observation spaces."
|
| 132 |
+
)
|
| 133 |
+
else:
|
| 134 |
+
_obs_buffer = None
|
| 135 |
+
self.observations = create_empty_array(
|
| 136 |
+
self.single_observation_space, n=self.num_envs, fn=np.zeros
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
self.parent_pipes, self.processes = [], []
|
| 140 |
+
self.error_queue = ctx.Queue()
|
| 141 |
+
target = _worker_shared_memory if self.shared_memory else _worker
|
| 142 |
+
target = worker or target
|
| 143 |
+
with clear_mpi_env_vars():
|
| 144 |
+
for idx, env_fn in enumerate(self.env_fns):
|
| 145 |
+
parent_pipe, child_pipe = ctx.Pipe()
|
| 146 |
+
process = ctx.Process(
|
| 147 |
+
target=target,
|
| 148 |
+
name="Worker<{0}>-{1}".format(type(self).__name__, idx),
|
| 149 |
+
args=(
|
| 150 |
+
idx,
|
| 151 |
+
CloudpickleWrapper(env_fn),
|
| 152 |
+
child_pipe,
|
| 153 |
+
parent_pipe,
|
| 154 |
+
_obs_buffer,
|
| 155 |
+
self.error_queue,
|
| 156 |
+
),
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
self.parent_pipes.append(parent_pipe)
|
| 160 |
+
self.processes.append(process)
|
| 161 |
+
|
| 162 |
+
process.daemon = daemon
|
| 163 |
+
process.start()
|
| 164 |
+
child_pipe.close()
|
| 165 |
+
|
| 166 |
+
self._state = AsyncState.DEFAULT
|
| 167 |
+
self._check_observation_spaces()
|
| 168 |
+
|
| 169 |
+
def seed(self, seeds=None):
|
| 170 |
+
self._assert_is_running()
|
| 171 |
+
if seeds is None:
|
| 172 |
+
seeds = [None for _ in range(self.num_envs)]
|
| 173 |
+
if isinstance(seeds, int):
|
| 174 |
+
seeds = [seeds + i for i in range(self.num_envs)]
|
| 175 |
+
assert len(seeds) == self.num_envs
|
| 176 |
+
|
| 177 |
+
if self._state != AsyncState.DEFAULT:
|
| 178 |
+
raise AlreadyPendingCallError(
|
| 179 |
+
"Calling `seed` while waiting "
|
| 180 |
+
"for a pending call to `{0}` to complete.".format(self._state.value),
|
| 181 |
+
self._state.value,
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
for pipe, seed in zip(self.parent_pipes, seeds):
|
| 185 |
+
pipe.send(("seed", seed))
|
| 186 |
+
_, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 187 |
+
self._raise_if_errors(successes)
|
| 188 |
+
|
| 189 |
+
def reset_async(self):
|
| 190 |
+
self._assert_is_running()
|
| 191 |
+
if self._state != AsyncState.DEFAULT:
|
| 192 |
+
raise AlreadyPendingCallError(
|
| 193 |
+
"Calling `reset_async` while waiting "
|
| 194 |
+
"for a pending call to `{0}` to complete".format(self._state.value),
|
| 195 |
+
self._state.value,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
for pipe in self.parent_pipes:
|
| 199 |
+
pipe.send(("reset", None))
|
| 200 |
+
self._state = AsyncState.WAITING_RESET
|
| 201 |
+
|
| 202 |
+
def reset_wait(self, timeout=None):
|
| 203 |
+
"""
|
| 204 |
+
Parameters
|
| 205 |
+
----------
|
| 206 |
+
timeout : int or float, optional
|
| 207 |
+
Number of seconds before the call to `reset_wait` times out. If
|
| 208 |
+
`None`, the call to `reset_wait` never times out.
|
| 209 |
+
Returns
|
| 210 |
+
-------
|
| 211 |
+
observations : sample from `observation_space`
|
| 212 |
+
A batch of observations from the vectorized environment.
|
| 213 |
+
"""
|
| 214 |
+
self._assert_is_running()
|
| 215 |
+
if self._state != AsyncState.WAITING_RESET:
|
| 216 |
+
raise NoAsyncCallError(
|
| 217 |
+
"Calling `reset_wait` without any prior " "call to `reset_async`.",
|
| 218 |
+
AsyncState.WAITING_RESET.value,
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
if not self._poll(timeout):
|
| 222 |
+
self._state = AsyncState.DEFAULT
|
| 223 |
+
raise mp.TimeoutError(
|
| 224 |
+
"The call to `reset_wait` has timed out after "
|
| 225 |
+
"{0} second{1}.".format(timeout, "s" if timeout > 1 else "")
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 229 |
+
self._raise_if_errors(successes)
|
| 230 |
+
self._state = AsyncState.DEFAULT
|
| 231 |
+
|
| 232 |
+
if not self.shared_memory:
|
| 233 |
+
self.observations = concatenate(
|
| 234 |
+
results, self.observations, self.single_observation_space
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
return deepcopy(self.observations) if self.copy else self.observations
|
| 238 |
+
|
| 239 |
+
def step_async(self, actions):
|
| 240 |
+
"""
|
| 241 |
+
Parameters
|
| 242 |
+
----------
|
| 243 |
+
actions : iterable of samples from `action_space`
|
| 244 |
+
List of actions.
|
| 245 |
+
"""
|
| 246 |
+
self._assert_is_running()
|
| 247 |
+
if self._state != AsyncState.DEFAULT:
|
| 248 |
+
raise AlreadyPendingCallError(
|
| 249 |
+
"Calling `step_async` while waiting "
|
| 250 |
+
"for a pending call to `{0}` to complete.".format(self._state.value),
|
| 251 |
+
self._state.value,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
for pipe, action in zip(self.parent_pipes, actions):
|
| 255 |
+
pipe.send(("step", action))
|
| 256 |
+
self._state = AsyncState.WAITING_STEP
|
| 257 |
+
|
| 258 |
+
def step_wait(self, timeout=None):
|
| 259 |
+
"""
|
| 260 |
+
Parameters
|
| 261 |
+
----------
|
| 262 |
+
timeout : int or float, optional
|
| 263 |
+
Number of seconds before the call to `step_wait` times out. If
|
| 264 |
+
`None`, the call to `step_wait` never times out.
|
| 265 |
+
Returns
|
| 266 |
+
-------
|
| 267 |
+
observations : sample from `observation_space`
|
| 268 |
+
A batch of observations from the vectorized environment.
|
| 269 |
+
rewards : `np.ndarray` instance (dtype `np.float_`)
|
| 270 |
+
A vector of rewards from the vectorized environment.
|
| 271 |
+
dones : `np.ndarray` instance (dtype `np.bool_`)
|
| 272 |
+
A vector whose entries indicate whether the episode has ended.
|
| 273 |
+
infos : list of dict
|
| 274 |
+
A list of auxiliary diagnostic information.
|
| 275 |
+
"""
|
| 276 |
+
self._assert_is_running()
|
| 277 |
+
if self._state != AsyncState.WAITING_STEP:
|
| 278 |
+
raise NoAsyncCallError(
|
| 279 |
+
"Calling `step_wait` without any prior call " "to `step_async`.",
|
| 280 |
+
AsyncState.WAITING_STEP.value,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
if not self._poll(timeout):
|
| 284 |
+
self._state = AsyncState.DEFAULT
|
| 285 |
+
raise mp.TimeoutError(
|
| 286 |
+
"The call to `step_wait` has timed out after "
|
| 287 |
+
"{0} second{1}.".format(timeout, "s" if timeout > 1 else "")
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 291 |
+
self._raise_if_errors(successes)
|
| 292 |
+
self._state = AsyncState.DEFAULT
|
| 293 |
+
observations_list, rewards, dones, infos = zip(*results)
|
| 294 |
+
|
| 295 |
+
if not self.shared_memory:
|
| 296 |
+
self.observations = concatenate(
|
| 297 |
+
observations_list, self.observations, self.single_observation_space
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
return (
|
| 301 |
+
deepcopy(self.observations) if self.copy else self.observations,
|
| 302 |
+
np.array(rewards),
|
| 303 |
+
np.array(dones, dtype=np.bool_),
|
| 304 |
+
infos,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
def close_extras(self, timeout=None, terminate=False):
|
| 308 |
+
"""
|
| 309 |
+
Parameters
|
| 310 |
+
----------
|
| 311 |
+
timeout : int or float, optional
|
| 312 |
+
Number of seconds before the call to `close` times out. If `None`,
|
| 313 |
+
the call to `close` never times out. If the call to `close` times
|
| 314 |
+
out, then all processes are terminated.
|
| 315 |
+
terminate : bool (default: `False`)
|
| 316 |
+
If `True`, then the `close` operation is forced and all processes
|
| 317 |
+
are terminated.
|
| 318 |
+
"""
|
| 319 |
+
timeout = 0 if terminate else timeout
|
| 320 |
+
try:
|
| 321 |
+
if self._state != AsyncState.DEFAULT:
|
| 322 |
+
logger.warn(
|
| 323 |
+
"Calling `close` while waiting for a pending "
|
| 324 |
+
"call to `{0}` to complete.".format(self._state.value)
|
| 325 |
+
)
|
| 326 |
+
function = getattr(self, "{0}_wait".format(self._state.value))
|
| 327 |
+
function(timeout)
|
| 328 |
+
except mp.TimeoutError:
|
| 329 |
+
terminate = True
|
| 330 |
+
|
| 331 |
+
if terminate:
|
| 332 |
+
for process in self.processes:
|
| 333 |
+
if process.is_alive():
|
| 334 |
+
process.terminate()
|
| 335 |
+
else:
|
| 336 |
+
for pipe in self.parent_pipes:
|
| 337 |
+
if (pipe is not None) and (not pipe.closed):
|
| 338 |
+
pipe.send(("close", None))
|
| 339 |
+
for pipe in self.parent_pipes:
|
| 340 |
+
if (pipe is not None) and (not pipe.closed):
|
| 341 |
+
pipe.recv()
|
| 342 |
+
|
| 343 |
+
for pipe in self.parent_pipes:
|
| 344 |
+
if pipe is not None:
|
| 345 |
+
pipe.close()
|
| 346 |
+
for process in self.processes:
|
| 347 |
+
process.join()
|
| 348 |
+
|
| 349 |
+
def _poll(self, timeout=None):
|
| 350 |
+
self._assert_is_running()
|
| 351 |
+
if timeout is None:
|
| 352 |
+
return True
|
| 353 |
+
end_time = time.perf_counter() + timeout
|
| 354 |
+
delta = None
|
| 355 |
+
for pipe in self.parent_pipes:
|
| 356 |
+
delta = max(end_time - time.perf_counter(), 0)
|
| 357 |
+
if pipe is None:
|
| 358 |
+
return False
|
| 359 |
+
if pipe.closed or (not pipe.poll(delta)):
|
| 360 |
+
return False
|
| 361 |
+
return True
|
| 362 |
+
|
| 363 |
+
def _check_observation_spaces(self):
|
| 364 |
+
self._assert_is_running()
|
| 365 |
+
for pipe in self.parent_pipes:
|
| 366 |
+
pipe.send(("_check_observation_space", self.single_observation_space))
|
| 367 |
+
same_spaces, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 368 |
+
self._raise_if_errors(successes)
|
| 369 |
+
if not all(same_spaces):
|
| 370 |
+
raise RuntimeError(
|
| 371 |
+
"Some environments have an observation space "
|
| 372 |
+
"different from `{0}`. In order to batch observations, the "
|
| 373 |
+
"observation spaces from all environments must be "
|
| 374 |
+
"equal.".format(self.single_observation_space)
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
def _assert_is_running(self):
|
| 378 |
+
if self.closed:
|
| 379 |
+
raise ClosedEnvironmentError(
|
| 380 |
+
"Trying to operate on `{0}`, after a "
|
| 381 |
+
"call to `close()`.".format(type(self).__name__)
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
def _raise_if_errors(self, successes):
|
| 385 |
+
if all(successes):
|
| 386 |
+
return
|
| 387 |
+
|
| 388 |
+
num_errors = self.num_envs - sum(successes)
|
| 389 |
+
assert num_errors > 0
|
| 390 |
+
for _ in range(num_errors):
|
| 391 |
+
index, exctype, value = self.error_queue.get()
|
| 392 |
+
logger.error(
|
| 393 |
+
"Received the following error from Worker-{0}: "
|
| 394 |
+
"{1}: {2}".format(index, exctype.__name__, value)
|
| 395 |
+
)
|
| 396 |
+
logger.error("Shutting down Worker-{0}.".format(index))
|
| 397 |
+
self.parent_pipes[index].close()
|
| 398 |
+
self.parent_pipes[index] = None
|
| 399 |
+
|
| 400 |
+
logger.error("Raising the last exception back to the main process.")
|
| 401 |
+
raise exctype(value)
|
| 402 |
+
|
| 403 |
+
def call_async(self, name: str, *args, **kwargs):
|
| 404 |
+
"""Calls the method with name asynchronously and apply args and kwargs to the method.
|
| 405 |
+
|
| 406 |
+
Args:
|
| 407 |
+
name: Name of the method or property to call.
|
| 408 |
+
*args: Arguments to apply to the method call.
|
| 409 |
+
**kwargs: Keyword arguments to apply to the method call.
|
| 410 |
+
|
| 411 |
+
Raises:
|
| 412 |
+
ClosedEnvironmentError: If the environment was closed (if :meth:`close` was previously called).
|
| 413 |
+
AlreadyPendingCallError: Calling `call_async` while waiting for a pending call to complete
|
| 414 |
+
"""
|
| 415 |
+
self._assert_is_running()
|
| 416 |
+
if self._state != AsyncState.DEFAULT:
|
| 417 |
+
raise AlreadyPendingCallError(
|
| 418 |
+
"Calling `call_async` while waiting "
|
| 419 |
+
f"for a pending call to `{self._state.value}` to complete.",
|
| 420 |
+
self._state.value,
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
for pipe in self.parent_pipes:
|
| 424 |
+
pipe.send(("_call", (name, args, kwargs)))
|
| 425 |
+
self._state = AsyncState.WAITING_CALL
|
| 426 |
+
|
| 427 |
+
def call_wait(self, timeout = None) -> list:
|
| 428 |
+
"""Calls all parent pipes and waits for the results.
|
| 429 |
+
|
| 430 |
+
Args:
|
| 431 |
+
timeout: Number of seconds before the call to `step_wait` times out.
|
| 432 |
+
If `None` (default), the call to `step_wait` never times out.
|
| 433 |
+
|
| 434 |
+
Returns:
|
| 435 |
+
List of the results of the individual calls to the method or property for each environment.
|
| 436 |
+
|
| 437 |
+
Raises:
|
| 438 |
+
NoAsyncCallError: Calling `call_wait` without any prior call to `call_async`.
|
| 439 |
+
TimeoutError: The call to `call_wait` has timed out after timeout second(s).
|
| 440 |
+
"""
|
| 441 |
+
self._assert_is_running()
|
| 442 |
+
if self._state != AsyncState.WAITING_CALL:
|
| 443 |
+
raise NoAsyncCallError(
|
| 444 |
+
"Calling `call_wait` without any prior call to `call_async`.",
|
| 445 |
+
AsyncState.WAITING_CALL.value,
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
if not self._poll(timeout):
|
| 449 |
+
self._state = AsyncState.DEFAULT
|
| 450 |
+
raise mp.TimeoutError(
|
| 451 |
+
f"The call to `call_wait` has timed out after {timeout} second(s)."
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 455 |
+
self._raise_if_errors(successes)
|
| 456 |
+
self._state = AsyncState.DEFAULT
|
| 457 |
+
|
| 458 |
+
return results
|
| 459 |
+
|
| 460 |
+
def call(self, name: str, *args, **kwargs):
|
| 461 |
+
"""Call a method, or get a property, from each parallel environment.
|
| 462 |
+
|
| 463 |
+
Args:
|
| 464 |
+
name (str): Name of the method or property to call.
|
| 465 |
+
*args: Arguments to apply to the method call.
|
| 466 |
+
**kwargs: Keyword arguments to apply to the method call.
|
| 467 |
+
|
| 468 |
+
Returns:
|
| 469 |
+
List of the results of the individual calls to the method or property for each environment.
|
| 470 |
+
"""
|
| 471 |
+
self.call_async(name, *args, **kwargs)
|
| 472 |
+
return self.call_wait()
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def call_each(self, name: str,
|
| 476 |
+
args_list: list=None,
|
| 477 |
+
kwargs_list: list=None,
|
| 478 |
+
timeout = None):
|
| 479 |
+
n_envs = len(self.parent_pipes)
|
| 480 |
+
if args_list is None:
|
| 481 |
+
args_list = [[]] * n_envs
|
| 482 |
+
assert len(args_list) == n_envs
|
| 483 |
+
|
| 484 |
+
if kwargs_list is None:
|
| 485 |
+
kwargs_list = [dict()] * n_envs
|
| 486 |
+
assert len(kwargs_list) == n_envs
|
| 487 |
+
|
| 488 |
+
# send
|
| 489 |
+
self._assert_is_running()
|
| 490 |
+
if self._state != AsyncState.DEFAULT:
|
| 491 |
+
raise AlreadyPendingCallError(
|
| 492 |
+
"Calling `call_async` while waiting "
|
| 493 |
+
f"for a pending call to `{self._state.value}` to complete.",
|
| 494 |
+
self._state.value,
|
| 495 |
+
)
|
| 496 |
+
|
| 497 |
+
for i, pipe in enumerate(self.parent_pipes):
|
| 498 |
+
pipe.send(("_call", (name, args_list[i], kwargs_list[i])))
|
| 499 |
+
self._state = AsyncState.WAITING_CALL
|
| 500 |
+
|
| 501 |
+
# receive
|
| 502 |
+
self._assert_is_running()
|
| 503 |
+
if self._state != AsyncState.WAITING_CALL:
|
| 504 |
+
raise NoAsyncCallError(
|
| 505 |
+
"Calling `call_wait` without any prior call to `call_async`.",
|
| 506 |
+
AsyncState.WAITING_CALL.value,
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
if not self._poll(timeout):
|
| 510 |
+
self._state = AsyncState.DEFAULT
|
| 511 |
+
raise mp.TimeoutError(
|
| 512 |
+
f"The call to `call_wait` has timed out after {timeout} second(s)."
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 516 |
+
self._raise_if_errors(successes)
|
| 517 |
+
self._state = AsyncState.DEFAULT
|
| 518 |
+
|
| 519 |
+
return results
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def set_attr(self, name: str, values):
|
| 523 |
+
"""Sets an attribute of the sub-environments.
|
| 524 |
+
|
| 525 |
+
Args:
|
| 526 |
+
name: Name of the property to be set in each individual environment.
|
| 527 |
+
values: Values of the property to be set to. If ``values`` is a list or
|
| 528 |
+
tuple, then it corresponds to the values for each individual
|
| 529 |
+
environment, otherwise a single value is set for all environments.
|
| 530 |
+
|
| 531 |
+
Raises:
|
| 532 |
+
ValueError: Values must be a list or tuple with length equal to the number of environments.
|
| 533 |
+
AlreadyPendingCallError: Calling `set_attr` while waiting for a pending call to complete.
|
| 534 |
+
"""
|
| 535 |
+
self._assert_is_running()
|
| 536 |
+
if not isinstance(values, (list, tuple)):
|
| 537 |
+
values = [values for _ in range(self.num_envs)]
|
| 538 |
+
if len(values) != self.num_envs:
|
| 539 |
+
raise ValueError(
|
| 540 |
+
"Values must be a list or tuple with length equal to the "
|
| 541 |
+
f"number of environments. Got `{len(values)}` values for "
|
| 542 |
+
f"{self.num_envs} environments."
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
if self._state != AsyncState.DEFAULT:
|
| 546 |
+
raise AlreadyPendingCallError(
|
| 547 |
+
"Calling `set_attr` while waiting "
|
| 548 |
+
f"for a pending call to `{self._state.value}` to complete.",
|
| 549 |
+
self._state.value,
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
for pipe, value in zip(self.parent_pipes, values):
|
| 553 |
+
pipe.send(("_setattr", (name, value)))
|
| 554 |
+
_, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
|
| 555 |
+
self._raise_if_errors(successes)
|
| 556 |
+
|
| 557 |
+
def render(self, *args, **kwargs):
|
| 558 |
+
return self.call('render', *args, **kwargs)
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def _worker(index, env_fn, pipe, parent_pipe, shared_memory, error_queue):
|
| 563 |
+
assert shared_memory is None
|
| 564 |
+
env = env_fn()
|
| 565 |
+
parent_pipe.close()
|
| 566 |
+
try:
|
| 567 |
+
while True:
|
| 568 |
+
command, data = pipe.recv()
|
| 569 |
+
if command == "reset":
|
| 570 |
+
observation = env.reset()
|
| 571 |
+
pipe.send((observation, True))
|
| 572 |
+
elif command == "step":
|
| 573 |
+
observation, reward, done, info = env.step(data)
|
| 574 |
+
# if done:
|
| 575 |
+
# observation = env.reset()
|
| 576 |
+
pipe.send(((observation, reward, done, info), True))
|
| 577 |
+
elif command == "seed":
|
| 578 |
+
env.seed(data)
|
| 579 |
+
pipe.send((None, True))
|
| 580 |
+
elif command == "close":
|
| 581 |
+
pipe.send((None, True))
|
| 582 |
+
break
|
| 583 |
+
elif command == "_call":
|
| 584 |
+
name, args, kwargs = data
|
| 585 |
+
if name in ["reset", "step", "seed", "close"]:
|
| 586 |
+
raise ValueError(
|
| 587 |
+
f"Trying to call function `{name}` with "
|
| 588 |
+
f"`_call`. Use `{name}` directly instead."
|
| 589 |
+
)
|
| 590 |
+
function = getattr(env, name)
|
| 591 |
+
if callable(function):
|
| 592 |
+
pipe.send((function(*args, **kwargs), True))
|
| 593 |
+
else:
|
| 594 |
+
pipe.send((function, True))
|
| 595 |
+
elif command == "_setattr":
|
| 596 |
+
name, value = data
|
| 597 |
+
setattr(env, name, value)
|
| 598 |
+
pipe.send((None, True))
|
| 599 |
+
|
| 600 |
+
elif command == "_check_observation_space":
|
| 601 |
+
pipe.send((data == env.observation_space, True))
|
| 602 |
+
else:
|
| 603 |
+
raise RuntimeError(
|
| 604 |
+
"Received unknown command `{0}`. Must "
|
| 605 |
+
"be one of {`reset`, `step`, `seed`, `close`, "
|
| 606 |
+
"`_check_observation_space`}.".format(command)
|
| 607 |
+
)
|
| 608 |
+
except (KeyboardInterrupt, Exception):
|
| 609 |
+
error_queue.put((index,) + sys.exc_info()[:2])
|
| 610 |
+
pipe.send((None, False))
|
| 611 |
+
finally:
|
| 612 |
+
env.close()
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
def _worker_shared_memory(index, env_fn, pipe, parent_pipe, shared_memory, error_queue):
|
| 616 |
+
assert shared_memory is not None
|
| 617 |
+
env = env_fn()
|
| 618 |
+
observation_space = env.observation_space
|
| 619 |
+
parent_pipe.close()
|
| 620 |
+
try:
|
| 621 |
+
while True:
|
| 622 |
+
command, data = pipe.recv()
|
| 623 |
+
if command == "reset":
|
| 624 |
+
observation = env.reset()
|
| 625 |
+
write_to_shared_memory(
|
| 626 |
+
index, observation, shared_memory, observation_space
|
| 627 |
+
)
|
| 628 |
+
pipe.send((None, True))
|
| 629 |
+
elif command == "step":
|
| 630 |
+
observation, reward, done, info = env.step(data)
|
| 631 |
+
# if done:
|
| 632 |
+
# observation = env.reset()
|
| 633 |
+
write_to_shared_memory(
|
| 634 |
+
index, observation, shared_memory, observation_space
|
| 635 |
+
)
|
| 636 |
+
pipe.send(((None, reward, done, info), True))
|
| 637 |
+
elif command == "seed":
|
| 638 |
+
env.seed(data)
|
| 639 |
+
pipe.send((None, True))
|
| 640 |
+
elif command == "close":
|
| 641 |
+
pipe.send((None, True))
|
| 642 |
+
break
|
| 643 |
+
elif command == "_call":
|
| 644 |
+
name, args, kwargs = data
|
| 645 |
+
if name in ["reset", "step", "seed", "close"]:
|
| 646 |
+
raise ValueError(
|
| 647 |
+
f"Trying to call function `{name}` with "
|
| 648 |
+
f"`_call`. Use `{name}` directly instead."
|
| 649 |
+
)
|
| 650 |
+
function = getattr(env, name)
|
| 651 |
+
if callable(function):
|
| 652 |
+
pipe.send((function(*args, **kwargs), True))
|
| 653 |
+
else:
|
| 654 |
+
pipe.send((function, True))
|
| 655 |
+
elif command == "_setattr":
|
| 656 |
+
name, value = data
|
| 657 |
+
setattr(env, name, value)
|
| 658 |
+
pipe.send((None, True))
|
| 659 |
+
elif command == "_check_observation_space":
|
| 660 |
+
pipe.send((data == observation_space, True))
|
| 661 |
+
else:
|
| 662 |
+
raise RuntimeError(
|
| 663 |
+
"Received unknown command `{0}`. Must "
|
| 664 |
+
"be one of {`reset`, `step`, `seed`, `close`, "
|
| 665 |
+
"`_check_observation_space`}.".format(command)
|
| 666 |
+
)
|
| 667 |
+
except (KeyboardInterrupt, Exception):
|
| 668 |
+
error_queue.put((index,) + sys.exc_info()[:2])
|
| 669 |
+
pipe.send((None, False))
|
| 670 |
+
finally:
|
| 671 |
+
env.close()
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from gym.envs.registration import register
|
| 2 |
+
|
| 3 |
+
register(
|
| 4 |
+
id='block-push-multi-modal-v0',
|
| 5 |
+
entry_point='cleandiffuser.env.block_pushing.block_pushing_multimodal:BlockPushMultimodal',
|
| 6 |
+
max_episode_steps=350,
|
| 7 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing.py
ADDED
|
@@ -0,0 +1,1092 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Simple block environments for the XArm."""
|
| 17 |
+
|
| 18 |
+
import collections
|
| 19 |
+
import enum
|
| 20 |
+
import math
|
| 21 |
+
import time
|
| 22 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 23 |
+
|
| 24 |
+
import gym
|
| 25 |
+
from gym import spaces
|
| 26 |
+
from gym.envs import registration
|
| 27 |
+
from cleandiffuser.env.block_pushing.utils import utils_pybullet
|
| 28 |
+
from cleandiffuser.env.block_pushing.utils import xarm_sim_robot
|
| 29 |
+
from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
|
| 30 |
+
from cleandiffuser.env.block_pushing.utils.utils_pybullet import ObjState
|
| 31 |
+
from cleandiffuser.env.block_pushing.utils.utils_pybullet import XarmState
|
| 32 |
+
import numpy as np
|
| 33 |
+
from scipy.spatial import transform
|
| 34 |
+
import pybullet
|
| 35 |
+
import pybullet_utils.bullet_client as bullet_client
|
| 36 |
+
|
| 37 |
+
import matplotlib.pyplot as plt
|
| 38 |
+
|
| 39 |
+
BLOCK_URDF_PATH = "third_party/py/envs/assets/block.urdf"
|
| 40 |
+
PLANE_URDF_PATH = "third_party/bullet/examples/pybullet/gym/pybullet_data/" "plane.urdf"
|
| 41 |
+
WORKSPACE_URDF_PATH = "third_party/py/envs/assets/workspace.urdf"
|
| 42 |
+
ZONE_URDF_PATH = "third_party/py/envs/assets/zone.urdf"
|
| 43 |
+
INSERT_URDF_PATH = "third_party/py/envs/assets/insert.urdf"
|
| 44 |
+
|
| 45 |
+
EFFECTOR_HEIGHT = 0.06
|
| 46 |
+
EFFECTOR_DOWN_ROTATION = transform.Rotation.from_rotvec([0, math.pi, 0])
|
| 47 |
+
|
| 48 |
+
WORKSPACE_BOUNDS = np.array(((0.15, -0.5), (0.7, 0.5)))
|
| 49 |
+
|
| 50 |
+
# Min/max bounds calculated from oracle data using:
|
| 51 |
+
# ibc/environments/board2d_dataset_statistics.ipynb
|
| 52 |
+
# to calculate [mean - 3 * std, mean + 3 * std] using the oracle data.
|
| 53 |
+
# pylint: disable=line-too-long
|
| 54 |
+
ACTION_MIN = np.array([-0.02547718, -0.02090043], np.float32)
|
| 55 |
+
ACTION_MAX = np.array([0.02869084, 0.04272365], np.float32)
|
| 56 |
+
EFFECTOR_TARGET_TRANSLATION_MIN = np.array(
|
| 57 |
+
[0.1774151772260666, -0.6287994794547558], np.float32
|
| 58 |
+
)
|
| 59 |
+
EFFECTOR_TARGET_TRANSLATION_MAX = np.array(
|
| 60 |
+
[0.5654461532831192, 0.5441607423126698], np.float32
|
| 61 |
+
)
|
| 62 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN = np.array(
|
| 63 |
+
[-0.07369826920330524, -0.11395704373717308], np.float32
|
| 64 |
+
)
|
| 65 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX = np.array(
|
| 66 |
+
[0.10131562314927578, 0.19391131028532982], np.float32
|
| 67 |
+
)
|
| 68 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN = np.array(
|
| 69 |
+
[-0.17813862301409245, -0.3309651017189026], np.float32
|
| 70 |
+
)
|
| 71 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX = np.array(
|
| 72 |
+
[0.23726161383092403, 0.8404090404510498], np.float32
|
| 73 |
+
)
|
| 74 |
+
BLOCK_ORIENTATION_COS_SIN_MIN = np.array(
|
| 75 |
+
[-2.0649861991405487, -0.6154364347457886], np.float32
|
| 76 |
+
)
|
| 77 |
+
BLOCK_ORIENTATION_COS_SIN_MAX = np.array(
|
| 78 |
+
[1.6590178310871124, 1.8811014890670776], np.float32
|
| 79 |
+
)
|
| 80 |
+
TARGET_ORIENTATION_COS_SIN_MIN = np.array(
|
| 81 |
+
[-1.0761439241468906, -0.8846937336493284], np.float32
|
| 82 |
+
)
|
| 83 |
+
TARGET_ORIENTATION_COS_SIN_MAX = np.array(
|
| 84 |
+
[-0.8344330154359341, 0.8786859593819827], np.float32
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Hardcoded Pose joints to make sure we don't have surprises from using the
|
| 88 |
+
# IK solver on reset. The joint poses correspond to the Pose with:
|
| 89 |
+
# rotation = rotation3.Rotation3.from_axis_angle([0, 1, 0], math.pi)
|
| 90 |
+
# translation = np.array([0.3, -0.4, 0.07])
|
| 91 |
+
INITIAL_JOINT_POSITIONS = np.array(
|
| 92 |
+
[
|
| 93 |
+
-0.9254632489674508,
|
| 94 |
+
0.6990770671568564,
|
| 95 |
+
-1.106629064060494,
|
| 96 |
+
0.0006653351931553931,
|
| 97 |
+
0.3987969742311386,
|
| 98 |
+
-4.063402065624296,
|
| 99 |
+
]
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
DEFAULT_CAMERA_POSE = (1.0, 0, 0.75)
|
| 103 |
+
DEFAULT_CAMERA_ORIENTATION = (np.pi / 4, np.pi, -np.pi / 2)
|
| 104 |
+
IMAGE_WIDTH = 320
|
| 105 |
+
IMAGE_HEIGHT = 240
|
| 106 |
+
CAMERA_INTRINSICS = (
|
| 107 |
+
0.803 * IMAGE_WIDTH, # fx
|
| 108 |
+
0,
|
| 109 |
+
IMAGE_WIDTH / 2.0, # cx
|
| 110 |
+
0,
|
| 111 |
+
0.803 * IMAGE_WIDTH, # fy
|
| 112 |
+
IMAGE_HEIGHT / 2.0, # cy
|
| 113 |
+
0,
|
| 114 |
+
0,
|
| 115 |
+
1,
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# "Realistic" visuals.
|
| 119 |
+
X_MIN_REAL = 0.15
|
| 120 |
+
X_MAX_REAL = 0.6
|
| 121 |
+
Y_MIN_REAL = -0.3048
|
| 122 |
+
Y_MAX_REAL = 0.3048
|
| 123 |
+
WORKSPACE_BOUNDS_REAL = np.array(((X_MIN_REAL, Y_MIN_REAL), (X_MAX_REAL, Y_MAX_REAL)))
|
| 124 |
+
WORKSPACE_URDF_PATH_REAL = "third_party/py/ibc/environments/assets/workspace_real.urdf"
|
| 125 |
+
CAMERA_POSE_REAL = (0.75, 0, 0.5)
|
| 126 |
+
CAMERA_ORIENTATION_REAL = (np.pi / 5, np.pi, -np.pi / 2)
|
| 127 |
+
|
| 128 |
+
IMAGE_WIDTH_REAL = 320
|
| 129 |
+
IMAGE_HEIGHT_REAL = 180
|
| 130 |
+
CAMERA_INTRINSICS_REAL = (
|
| 131 |
+
0.803 * IMAGE_WIDTH_REAL, # fx
|
| 132 |
+
0,
|
| 133 |
+
IMAGE_WIDTH_REAL / 2.0, # cx
|
| 134 |
+
0,
|
| 135 |
+
0.803 * IMAGE_WIDTH_REAL, # fy
|
| 136 |
+
IMAGE_HEIGHT_REAL / 2.0, # cy
|
| 137 |
+
0,
|
| 138 |
+
0,
|
| 139 |
+
1,
|
| 140 |
+
)
|
| 141 |
+
# pylint: enable=line-too-long
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def build_env_name(task, shared_memory, use_image_obs, use_normalized_env=False):
|
| 145 |
+
"""Construct the env name from parameters."""
|
| 146 |
+
if isinstance(task, str):
|
| 147 |
+
task = BlockTaskVariant[task]
|
| 148 |
+
env_name = "Block" + task.value
|
| 149 |
+
|
| 150 |
+
if use_image_obs:
|
| 151 |
+
env_name = env_name + "Rgb"
|
| 152 |
+
if use_normalized_env:
|
| 153 |
+
env_name = env_name + "Normalized"
|
| 154 |
+
if shared_memory:
|
| 155 |
+
env_name = "Shared" + env_name
|
| 156 |
+
|
| 157 |
+
env_name = env_name + "-v0"
|
| 158 |
+
|
| 159 |
+
return env_name
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class BlockTaskVariant(enum.Enum):
|
| 163 |
+
REACH = "Reach"
|
| 164 |
+
REACH_NORMALIZED = "ReachNormalized"
|
| 165 |
+
PUSH = "Push"
|
| 166 |
+
PUSH_NORMALIZED = "PushNormalized"
|
| 167 |
+
INSERT = "Insert"
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def sleep_spin(sleep_time_sec):
|
| 171 |
+
"""Spin wait sleep. Avoids time.sleep accuracy issues on Windows."""
|
| 172 |
+
if sleep_time_sec <= 0:
|
| 173 |
+
return
|
| 174 |
+
t0 = time.perf_counter()
|
| 175 |
+
while time.perf_counter() - t0 < sleep_time_sec:
|
| 176 |
+
pass
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class BlockPush(gym.Env):
|
| 180 |
+
"""Simple XArm environment for block pushing."""
|
| 181 |
+
|
| 182 |
+
def __init__(
|
| 183 |
+
self,
|
| 184 |
+
control_frequency=10.0,
|
| 185 |
+
task=BlockTaskVariant.PUSH,
|
| 186 |
+
image_size=None,
|
| 187 |
+
shared_memory=False,
|
| 188 |
+
seed=None,
|
| 189 |
+
goal_dist_tolerance=0.01,
|
| 190 |
+
effector_height=None,
|
| 191 |
+
visuals_mode="default",
|
| 192 |
+
abs_action=False
|
| 193 |
+
):
|
| 194 |
+
"""Creates an env instance.
|
| 195 |
+
|
| 196 |
+
Args:
|
| 197 |
+
control_frequency: Control frequency for the arm. Each env step will
|
| 198 |
+
advance the simulation by 1/control_frequency seconds.
|
| 199 |
+
task: enum for which task, see BlockTaskVariant enum.
|
| 200 |
+
image_size: Optional image size (height, width). If None, no image
|
| 201 |
+
observations will be used.
|
| 202 |
+
shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
|
| 203 |
+
pybullet. Useful to debug.
|
| 204 |
+
seed: Optional seed for the environment.
|
| 205 |
+
goal_dist_tolerance: float, how far away from the goal to terminate.
|
| 206 |
+
effector_height: float, custom height for end effector.
|
| 207 |
+
visuals_mode: 'default' or 'real'.
|
| 208 |
+
"""
|
| 209 |
+
# pybullet.connect(pybullet.GUI)
|
| 210 |
+
# pybullet.resetDebugVisualizerCamera(
|
| 211 |
+
# cameraDistance=1.5,
|
| 212 |
+
# cameraYaw=0,
|
| 213 |
+
# cameraPitch=-40,
|
| 214 |
+
# cameraTargetPosition=[0.55, -0.35, 0.2],
|
| 215 |
+
# )
|
| 216 |
+
if visuals_mode != "default" and visuals_mode != "real":
|
| 217 |
+
raise ValueError("visuals_mode must be `real` or `default`.")
|
| 218 |
+
self._task = task
|
| 219 |
+
self._connection_mode = pybullet.DIRECT
|
| 220 |
+
if shared_memory:
|
| 221 |
+
self._connection_mode = pybullet.SHARED_MEMORY
|
| 222 |
+
|
| 223 |
+
self.goal_dist_tolerance = goal_dist_tolerance
|
| 224 |
+
|
| 225 |
+
self.effector_height = effector_height or EFFECTOR_HEIGHT
|
| 226 |
+
|
| 227 |
+
self._visuals_mode = visuals_mode
|
| 228 |
+
if visuals_mode == "default":
|
| 229 |
+
self._camera_pose = DEFAULT_CAMERA_POSE
|
| 230 |
+
self._camera_orientation = DEFAULT_CAMERA_ORIENTATION
|
| 231 |
+
self.workspace_bounds = WORKSPACE_BOUNDS
|
| 232 |
+
self._image_size = image_size
|
| 233 |
+
self._camera_instrinsics = CAMERA_INTRINSICS
|
| 234 |
+
self._workspace_urdf_path = WORKSPACE_URDF_PATH
|
| 235 |
+
else:
|
| 236 |
+
self._camera_pose = CAMERA_POSE_REAL
|
| 237 |
+
self._camera_orientation = CAMERA_ORIENTATION_REAL
|
| 238 |
+
self.workspace_bounds = WORKSPACE_BOUNDS_REAL
|
| 239 |
+
self._image_size = image_size
|
| 240 |
+
self._camera_instrinsics = CAMERA_INTRINSICS_REAL
|
| 241 |
+
self._workspace_urdf_path = WORKSPACE_URDF_PATH_REAL
|
| 242 |
+
|
| 243 |
+
self.action_space = spaces.Box(low=-0.1, high=0.1, shape=(2,)) # x, y
|
| 244 |
+
self.observation_space = self._create_observation_space(image_size)
|
| 245 |
+
|
| 246 |
+
self._rng = np.random.RandomState(seed=seed)
|
| 247 |
+
self._block_ids = None
|
| 248 |
+
self._previous_state = None
|
| 249 |
+
self._robot = None
|
| 250 |
+
self._workspace_uid = None
|
| 251 |
+
self._target_id = None
|
| 252 |
+
self._target_pose = None
|
| 253 |
+
self._target_effector_pose = None
|
| 254 |
+
self._pybullet_client = None
|
| 255 |
+
self.reach_target_translation = None
|
| 256 |
+
self._setup_pybullet_scene()
|
| 257 |
+
self._saved_state = None
|
| 258 |
+
|
| 259 |
+
assert isinstance(self._pybullet_client, bullet_client.BulletClient)
|
| 260 |
+
self._control_frequency = control_frequency
|
| 261 |
+
self._step_frequency = (
|
| 262 |
+
1 / self._pybullet_client.getPhysicsEngineParameters()["fixedTimeStep"]
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
self._last_loop_time = None
|
| 266 |
+
self._last_loop_frame_sleep_time = None
|
| 267 |
+
if self._step_frequency % self._control_frequency != 0:
|
| 268 |
+
raise ValueError(
|
| 269 |
+
"Control frequency should be a multiple of the "
|
| 270 |
+
"configured Bullet TimeStep."
|
| 271 |
+
)
|
| 272 |
+
self._sim_steps_per_step = int(self._step_frequency / self._control_frequency)
|
| 273 |
+
|
| 274 |
+
self.rendered_img = None
|
| 275 |
+
self._abs_action = abs_action
|
| 276 |
+
|
| 277 |
+
# Use saved_state and restore to make reset safe as no simulation state has
|
| 278 |
+
# been updated at this state, but the assets are now loaded.
|
| 279 |
+
self.save_state()
|
| 280 |
+
self.reset()
|
| 281 |
+
|
| 282 |
+
@property
|
| 283 |
+
def pybullet_client(self):
|
| 284 |
+
return self._pybullet_client
|
| 285 |
+
|
| 286 |
+
@property
|
| 287 |
+
def robot(self):
|
| 288 |
+
return self._robot
|
| 289 |
+
|
| 290 |
+
@property
|
| 291 |
+
def workspace_uid(self):
|
| 292 |
+
return self._workspace_uid
|
| 293 |
+
|
| 294 |
+
@property
|
| 295 |
+
def target_effector_pose(self):
|
| 296 |
+
return self._target_effector_pose
|
| 297 |
+
|
| 298 |
+
@property
|
| 299 |
+
def target_pose(self):
|
| 300 |
+
return self._target_pose
|
| 301 |
+
|
| 302 |
+
@property
|
| 303 |
+
def control_frequency(self):
|
| 304 |
+
return self._control_frequency
|
| 305 |
+
|
| 306 |
+
@property
|
| 307 |
+
def connection_mode(self):
|
| 308 |
+
return self._connection_mode
|
| 309 |
+
|
| 310 |
+
def save_state(self):
|
| 311 |
+
self._saved_state = self._pybullet_client.saveState()
|
| 312 |
+
|
| 313 |
+
def set_goal_dist_tolerance(self, val):
|
| 314 |
+
self.goal_dist_tolerance = val
|
| 315 |
+
|
| 316 |
+
def get_control_frequency(self):
|
| 317 |
+
return self._control_frequency
|
| 318 |
+
|
| 319 |
+
def compute_state(self):
|
| 320 |
+
return self._compute_state()
|
| 321 |
+
|
| 322 |
+
def get_goal_translation(self):
|
| 323 |
+
"""Return the translation component of the goal (2D)."""
|
| 324 |
+
if self._task == BlockTaskVariant.REACH:
|
| 325 |
+
return np.concatenate([self.reach_target_translation, [0]])
|
| 326 |
+
else:
|
| 327 |
+
return self._target_pose.translation if self._target_pose else None
|
| 328 |
+
|
| 329 |
+
def get_obj_ids(self):
|
| 330 |
+
return self._block_ids
|
| 331 |
+
|
| 332 |
+
def _setup_workspace_and_robot(self, end_effector="suction"):
|
| 333 |
+
self._pybullet_client.resetSimulation()
|
| 334 |
+
self._pybullet_client.configureDebugVisualizer(pybullet.COV_ENABLE_GUI, 0)
|
| 335 |
+
self._pybullet_client.setPhysicsEngineParameter(enableFileCaching=0)
|
| 336 |
+
self._pybullet_client.setGravity(0, 0, -9.8)
|
| 337 |
+
|
| 338 |
+
utils_pybullet.load_urdf(
|
| 339 |
+
self._pybullet_client, PLANE_URDF_PATH, basePosition=[0, 0, -0.001]
|
| 340 |
+
)
|
| 341 |
+
self._workspace_uid = utils_pybullet.load_urdf(
|
| 342 |
+
self._pybullet_client,
|
| 343 |
+
self._workspace_urdf_path,
|
| 344 |
+
basePosition=[0.35, 0, 0.0],
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
self._robot = xarm_sim_robot.XArmSimRobot(
|
| 348 |
+
self._pybullet_client,
|
| 349 |
+
initial_joint_positions=INITIAL_JOINT_POSITIONS,
|
| 350 |
+
end_effector=end_effector,
|
| 351 |
+
color="white" if self._visuals_mode == "real" else "default",
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
def _setup_pybullet_scene(self):
|
| 355 |
+
self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
|
| 356 |
+
|
| 357 |
+
# Temporarily disable rendering to speed up loading URDFs.
|
| 358 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
|
| 359 |
+
|
| 360 |
+
self._setup_workspace_and_robot()
|
| 361 |
+
|
| 362 |
+
if self._task == BlockTaskVariant.INSERT:
|
| 363 |
+
target_urdf_path = INSERT_URDF_PATH
|
| 364 |
+
else:
|
| 365 |
+
target_urdf_path = ZONE_URDF_PATH
|
| 366 |
+
|
| 367 |
+
self._target_id = utils_pybullet.load_urdf(
|
| 368 |
+
self._pybullet_client, target_urdf_path, useFixedBase=True
|
| 369 |
+
)
|
| 370 |
+
self._block_ids = [
|
| 371 |
+
utils_pybullet.load_urdf(
|
| 372 |
+
self._pybullet_client, BLOCK_URDF_PATH, useFixedBase=False
|
| 373 |
+
)
|
| 374 |
+
]
|
| 375 |
+
|
| 376 |
+
# Re-enable rendering.
|
| 377 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
|
| 378 |
+
|
| 379 |
+
self.step_simulation_to_stabilize()
|
| 380 |
+
|
| 381 |
+
def step_simulation_to_stabilize(self, nsteps=100):
|
| 382 |
+
for _ in range(nsteps):
|
| 383 |
+
self._pybullet_client.stepSimulation()
|
| 384 |
+
|
| 385 |
+
def seed(self, seed=None):
|
| 386 |
+
self._rng = np.random.RandomState(seed=seed)
|
| 387 |
+
|
| 388 |
+
def _set_robot_target_effector_pose(self, pose):
|
| 389 |
+
self._target_effector_pose = pose
|
| 390 |
+
self._robot.set_target_effector_pose(pose)
|
| 391 |
+
|
| 392 |
+
def reset(self, reset_poses=True):
|
| 393 |
+
workspace_center_x = 0.4
|
| 394 |
+
|
| 395 |
+
if reset_poses:
|
| 396 |
+
self._pybullet_client.restoreState(self._saved_state)
|
| 397 |
+
|
| 398 |
+
rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
|
| 399 |
+
translation = np.array([0.3, -0.4, self.effector_height])
|
| 400 |
+
starting_pose = Pose3d(rotation=rotation, translation=translation)
|
| 401 |
+
self._set_robot_target_effector_pose(starting_pose)
|
| 402 |
+
|
| 403 |
+
# Reset block pose.
|
| 404 |
+
block_x = workspace_center_x + self._rng.uniform(low=-0.1, high=0.1)
|
| 405 |
+
block_y = -0.2 + self._rng.uniform(low=-0.15, high=0.15)
|
| 406 |
+
block_translation = np.array([block_x, block_y, 0])
|
| 407 |
+
block_sampled_angle = self._rng.uniform(math.pi)
|
| 408 |
+
block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
|
| 409 |
+
|
| 410 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 411 |
+
self._block_ids[0],
|
| 412 |
+
block_translation.tolist(),
|
| 413 |
+
block_rotation.as_quat().tolist(),
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
# Reset target pose.
|
| 417 |
+
target_x = workspace_center_x + self._rng.uniform(low=-0.10, high=0.10)
|
| 418 |
+
target_y = 0.2 + self._rng.uniform(low=-0.15, high=0.15)
|
| 419 |
+
target_translation = np.array([target_x, target_y, 0.020])
|
| 420 |
+
|
| 421 |
+
target_sampled_angle = math.pi + self._rng.uniform(
|
| 422 |
+
low=-math.pi / 6, high=math.pi / 6
|
| 423 |
+
)
|
| 424 |
+
target_rotation = transform.Rotation.from_rotvec(
|
| 425 |
+
[0, 0, target_sampled_angle]
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 429 |
+
self._target_id,
|
| 430 |
+
target_translation.tolist(),
|
| 431 |
+
target_rotation.as_quat().tolist(),
|
| 432 |
+
)
|
| 433 |
+
else:
|
| 434 |
+
(
|
| 435 |
+
target_translation,
|
| 436 |
+
target_orientation_quat,
|
| 437 |
+
) = self._pybullet_client.getBasePositionAndOrientation(self._target_id)
|
| 438 |
+
target_rotation = transform.Rotation.from_quat(target_orientation_quat)
|
| 439 |
+
target_translation = np.array(target_translation)
|
| 440 |
+
|
| 441 |
+
self._target_pose = Pose3d(
|
| 442 |
+
rotation=target_rotation, translation=target_translation
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
if reset_poses:
|
| 446 |
+
self.step_simulation_to_stabilize()
|
| 447 |
+
|
| 448 |
+
state = self._compute_state()
|
| 449 |
+
self._previous_state = state
|
| 450 |
+
|
| 451 |
+
if self._task == BlockTaskVariant.REACH:
|
| 452 |
+
self._compute_reach_target(state)
|
| 453 |
+
|
| 454 |
+
self._init_goal_distance = self._compute_goal_distance(state)
|
| 455 |
+
init_goal_eps = 1e-7
|
| 456 |
+
assert self._init_goal_distance > init_goal_eps
|
| 457 |
+
self.best_fraction_reduced_goal_dist = 0.0
|
| 458 |
+
|
| 459 |
+
return state
|
| 460 |
+
|
| 461 |
+
def _compute_goal_distance(self, state):
|
| 462 |
+
goal_translation = self.get_goal_translation()
|
| 463 |
+
if self._task != BlockTaskVariant.REACH:
|
| 464 |
+
goal_distance = np.linalg.norm(
|
| 465 |
+
state["block_translation"] - goal_translation[0:2]
|
| 466 |
+
)
|
| 467 |
+
else:
|
| 468 |
+
goal_distance = np.linalg.norm(
|
| 469 |
+
state["effector_translation"] - goal_translation[0:2]
|
| 470 |
+
)
|
| 471 |
+
return goal_distance
|
| 472 |
+
|
| 473 |
+
def _compute_reach_target(self, state):
|
| 474 |
+
xy_block = state["block_translation"]
|
| 475 |
+
xy_target = state["target_translation"]
|
| 476 |
+
|
| 477 |
+
xy_block_to_target = xy_target - xy_block
|
| 478 |
+
xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
|
| 479 |
+
xy_block_to_target
|
| 480 |
+
)
|
| 481 |
+
self.reach_target_translation = xy_block + -1 * xy_dir_block_to_target * 0.05
|
| 482 |
+
|
| 483 |
+
def _compute_state(self):
|
| 484 |
+
effector_pose = self._robot.forward_kinematics()
|
| 485 |
+
block_position_and_orientation = (
|
| 486 |
+
self._pybullet_client.getBasePositionAndOrientation(self._block_ids[0])
|
| 487 |
+
)
|
| 488 |
+
block_pose = Pose3d(
|
| 489 |
+
rotation=transform.Rotation.from_quat(block_position_and_orientation[1]),
|
| 490 |
+
translation=block_position_and_orientation[0],
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
def _yaw_from_pose(pose):
|
| 494 |
+
return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1]])
|
| 495 |
+
|
| 496 |
+
obs = collections.OrderedDict(
|
| 497 |
+
block_translation=block_pose.translation[0:2],
|
| 498 |
+
block_orientation=_yaw_from_pose(block_pose),
|
| 499 |
+
effector_translation=effector_pose.translation[0:2],
|
| 500 |
+
effector_target_translation=self._target_effector_pose.translation[0:2],
|
| 501 |
+
target_translation=self._target_pose.translation[0:2],
|
| 502 |
+
target_orientation=_yaw_from_pose(self._target_pose),
|
| 503 |
+
)
|
| 504 |
+
if self._image_size is not None:
|
| 505 |
+
obs["rgb"] = self._render_camera(self._image_size)
|
| 506 |
+
return obs
|
| 507 |
+
|
| 508 |
+
def _step_robot_and_sim(self, action):
|
| 509 |
+
"""Steps the robot and pybullet sim."""
|
| 510 |
+
# Compute target_effector_pose by shifting the effector's pose by the
|
| 511 |
+
# action.
|
| 512 |
+
if self._abs_action:
|
| 513 |
+
target_effector_translation = np.array([action[0], action[1], 0])
|
| 514 |
+
else:
|
| 515 |
+
target_effector_translation = np.array(
|
| 516 |
+
self._target_effector_pose.translation
|
| 517 |
+
) + np.array([action[0], action[1], 0])
|
| 518 |
+
|
| 519 |
+
target_effector_translation[0:2] = np.clip(
|
| 520 |
+
target_effector_translation[0:2],
|
| 521 |
+
self.workspace_bounds[0],
|
| 522 |
+
self.workspace_bounds[1],
|
| 523 |
+
)
|
| 524 |
+
target_effector_translation[-1] = self.effector_height
|
| 525 |
+
target_effector_pose = Pose3d(
|
| 526 |
+
rotation=EFFECTOR_DOWN_ROTATION, translation=target_effector_translation
|
| 527 |
+
)
|
| 528 |
+
|
| 529 |
+
self._set_robot_target_effector_pose(target_effector_pose)
|
| 530 |
+
|
| 531 |
+
# Update sleep time dynamically to stay near real-time.
|
| 532 |
+
frame_sleep_time = 0
|
| 533 |
+
if self._connection_mode == pybullet.SHARED_MEMORY:
|
| 534 |
+
cur_time = time.time()
|
| 535 |
+
if self._last_loop_time is not None:
|
| 536 |
+
# Calculate the total, non-sleeping time from the previous frame, this
|
| 537 |
+
# includes the actual step as well as any compute that happens in the
|
| 538 |
+
# caller thread (model inference, etc).
|
| 539 |
+
compute_time = (
|
| 540 |
+
cur_time
|
| 541 |
+
- self._last_loop_time
|
| 542 |
+
- self._last_loop_frame_sleep_time * self._sim_steps_per_step
|
| 543 |
+
)
|
| 544 |
+
# Use this to calculate the current frame's total sleep time to ensure
|
| 545 |
+
# that env.step runs at policy rate. This is an estimate since the
|
| 546 |
+
# previous frame's compute time may not match the current frame.
|
| 547 |
+
total_sleep_time = max((1 / self._control_frequency) - compute_time, 0)
|
| 548 |
+
# Now spread this out over the inner sim steps. This doesn't change
|
| 549 |
+
# control in any way, but makes the animation appear smooth.
|
| 550 |
+
frame_sleep_time = total_sleep_time / self._sim_steps_per_step
|
| 551 |
+
else:
|
| 552 |
+
# No estimate of the previous frame's compute, assume it is zero.
|
| 553 |
+
frame_sleep_time = 1 / self._step_frequency
|
| 554 |
+
|
| 555 |
+
# Cache end of this loop time, to compute sleep time on next iteration.
|
| 556 |
+
self._last_loop_time = cur_time
|
| 557 |
+
self._last_loop_frame_sleep_time = frame_sleep_time
|
| 558 |
+
|
| 559 |
+
for _ in range(self._sim_steps_per_step):
|
| 560 |
+
if self._connection_mode == pybullet.SHARED_MEMORY:
|
| 561 |
+
sleep_spin(frame_sleep_time)
|
| 562 |
+
self._pybullet_client.stepSimulation()
|
| 563 |
+
|
| 564 |
+
def step(self, action):
|
| 565 |
+
self._step_robot_and_sim(action)
|
| 566 |
+
|
| 567 |
+
state = self._compute_state()
|
| 568 |
+
|
| 569 |
+
goal_distance = self._compute_goal_distance(state)
|
| 570 |
+
fraction_reduced_goal_distance = 1.0 - (
|
| 571 |
+
goal_distance / self._init_goal_distance
|
| 572 |
+
)
|
| 573 |
+
if fraction_reduced_goal_distance > self.best_fraction_reduced_goal_dist:
|
| 574 |
+
self.best_fraction_reduced_goal_dist = fraction_reduced_goal_distance
|
| 575 |
+
|
| 576 |
+
done = False
|
| 577 |
+
reward = self.best_fraction_reduced_goal_dist
|
| 578 |
+
|
| 579 |
+
# Terminate the episode if the block is close enough to the target.
|
| 580 |
+
if goal_distance < self.goal_dist_tolerance:
|
| 581 |
+
reward = 1.0
|
| 582 |
+
done = True
|
| 583 |
+
|
| 584 |
+
return state, reward, done, {}
|
| 585 |
+
|
| 586 |
+
@property
|
| 587 |
+
def succeeded(self):
|
| 588 |
+
state = self._compute_state()
|
| 589 |
+
goal_distance = self._compute_goal_distance(state)
|
| 590 |
+
if goal_distance < self.goal_dist_tolerance:
|
| 591 |
+
return True
|
| 592 |
+
return False
|
| 593 |
+
|
| 594 |
+
@property
|
| 595 |
+
def goal_distance(self):
|
| 596 |
+
state = self._compute_state()
|
| 597 |
+
return self._compute_goal_distance(state)
|
| 598 |
+
|
| 599 |
+
def render(self, mode="rgb_array"):
|
| 600 |
+
if self._image_size is not None:
|
| 601 |
+
image_size = self._image_size
|
| 602 |
+
else:
|
| 603 |
+
# This allows rendering even for state-only obs,
|
| 604 |
+
# for visualization.
|
| 605 |
+
image_size = (IMAGE_HEIGHT, IMAGE_WIDTH)
|
| 606 |
+
|
| 607 |
+
data = self._render_camera(image_size=(image_size[0], image_size[1]))
|
| 608 |
+
if mode == "human":
|
| 609 |
+
if self.rendered_img is None:
|
| 610 |
+
self.rendered_img = plt.imshow(
|
| 611 |
+
np.zeros((image_size[0], image_size[1], 4))
|
| 612 |
+
)
|
| 613 |
+
else:
|
| 614 |
+
self.rendered_img.set_data(data)
|
| 615 |
+
plt.draw()
|
| 616 |
+
plt.pause(0.00001)
|
| 617 |
+
return data
|
| 618 |
+
|
| 619 |
+
def close(self):
|
| 620 |
+
self._pybullet_client.disconnect()
|
| 621 |
+
|
| 622 |
+
def calc_camera_params(self, image_size):
|
| 623 |
+
# Mimic RealSense D415 camera parameters.
|
| 624 |
+
intrinsics = self._camera_instrinsics
|
| 625 |
+
|
| 626 |
+
# Set default camera poses.
|
| 627 |
+
front_position = self._camera_pose
|
| 628 |
+
front_rotation = self._camera_orientation
|
| 629 |
+
front_rotation = self._pybullet_client.getQuaternionFromEuler(front_rotation)
|
| 630 |
+
# Default camera configs.
|
| 631 |
+
zrange = (0.01, 10.0)
|
| 632 |
+
|
| 633 |
+
# OpenGL camera settings.
|
| 634 |
+
lookdir = np.float32([0, 0, 1]).reshape(3, 1)
|
| 635 |
+
updir = np.float32([0, -1, 0]).reshape(3, 1)
|
| 636 |
+
rotation = self._pybullet_client.getMatrixFromQuaternion(front_rotation)
|
| 637 |
+
rotm = np.float32(rotation).reshape(3, 3)
|
| 638 |
+
lookdir = (rotm @ lookdir).reshape(-1)
|
| 639 |
+
updir = (rotm @ updir).reshape(-1)
|
| 640 |
+
lookat = front_position + lookdir
|
| 641 |
+
focal_len = intrinsics[0]
|
| 642 |
+
znear, zfar = zrange
|
| 643 |
+
viewm = self._pybullet_client.computeViewMatrix(front_position, lookat, updir)
|
| 644 |
+
fovh = (image_size[0] / 2) / focal_len
|
| 645 |
+
fovh = 180 * np.arctan(fovh) * 2 / np.pi
|
| 646 |
+
|
| 647 |
+
# Notes: 1) FOV is vertical FOV 2) aspect must be float
|
| 648 |
+
aspect_ratio = image_size[1] / image_size[0]
|
| 649 |
+
projm = self._pybullet_client.computeProjectionMatrixFOV(
|
| 650 |
+
fovh, aspect_ratio, znear, zfar
|
| 651 |
+
)
|
| 652 |
+
|
| 653 |
+
return viewm, projm, front_position, lookat, updir
|
| 654 |
+
|
| 655 |
+
def _render_camera(self, image_size):
|
| 656 |
+
"""Render RGB image with RealSense configuration."""
|
| 657 |
+
viewm, projm, _, _, _ = self.calc_camera_params(image_size)
|
| 658 |
+
|
| 659 |
+
# Render with OpenGL camera settings.
|
| 660 |
+
_, _, color, _, _ = self._pybullet_client.getCameraImage(
|
| 661 |
+
width=image_size[1],
|
| 662 |
+
height=image_size[0],
|
| 663 |
+
viewMatrix=viewm,
|
| 664 |
+
projectionMatrix=projm,
|
| 665 |
+
flags=pybullet.ER_SEGMENTATION_MASK_OBJECT_AND_LINKINDEX,
|
| 666 |
+
renderer=pybullet.ER_BULLET_HARDWARE_OPENGL,
|
| 667 |
+
)
|
| 668 |
+
|
| 669 |
+
# Get color image.
|
| 670 |
+
color_image_size = (image_size[0], image_size[1], 4)
|
| 671 |
+
color = np.array(color, dtype=np.uint8).reshape(color_image_size)
|
| 672 |
+
color = color[:, :, :3] # remove alpha channel
|
| 673 |
+
|
| 674 |
+
return color.astype(np.uint8)
|
| 675 |
+
|
| 676 |
+
def _create_observation_space(self, image_size):
|
| 677 |
+
pi2 = math.pi * 2
|
| 678 |
+
|
| 679 |
+
obs_dict = collections.OrderedDict(
|
| 680 |
+
block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 681 |
+
block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
|
| 682 |
+
effector_translation=spaces.Box(
|
| 683 |
+
low=self.workspace_bounds[0] - 0.1, # Small buffer for to IK noise.
|
| 684 |
+
high=self.workspace_bounds[1] + 0.1,
|
| 685 |
+
), # x,y
|
| 686 |
+
effector_target_translation=spaces.Box(
|
| 687 |
+
low=self.workspace_bounds[0] - 0.1, # Small buffer for to IK noise.
|
| 688 |
+
high=self.workspace_bounds[1] + 0.1,
|
| 689 |
+
), # x,y
|
| 690 |
+
target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 691 |
+
target_orientation=spaces.Box(
|
| 692 |
+
low=-pi2,
|
| 693 |
+
high=pi2,
|
| 694 |
+
shape=(1,),
|
| 695 |
+
), # theta
|
| 696 |
+
)
|
| 697 |
+
if image_size is not None:
|
| 698 |
+
obs_dict["rgb"] = spaces.Box(
|
| 699 |
+
low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
|
| 700 |
+
)
|
| 701 |
+
return spaces.Dict(obs_dict)
|
| 702 |
+
|
| 703 |
+
def get_pybullet_state(self):
|
| 704 |
+
"""Save pybullet state of the scene.
|
| 705 |
+
|
| 706 |
+
Returns:
|
| 707 |
+
dict containing 'robots', 'robot_end_effectors', 'targets', 'objects',
|
| 708 |
+
each containing a list of ObjState.
|
| 709 |
+
"""
|
| 710 |
+
state: Dict[str, List[ObjState]] = {}
|
| 711 |
+
|
| 712 |
+
state["robots"] = [
|
| 713 |
+
XarmState.get_bullet_state(
|
| 714 |
+
self._pybullet_client,
|
| 715 |
+
self.robot.xarm,
|
| 716 |
+
target_effector_pose=self._target_effector_pose,
|
| 717 |
+
goal_translation=self.get_goal_translation(),
|
| 718 |
+
)
|
| 719 |
+
]
|
| 720 |
+
|
| 721 |
+
state["robot_end_effectors"] = []
|
| 722 |
+
if self.robot.end_effector:
|
| 723 |
+
state["robot_end_effectors"].append(
|
| 724 |
+
ObjState.get_bullet_state(
|
| 725 |
+
self._pybullet_client, self.robot.end_effector
|
| 726 |
+
)
|
| 727 |
+
)
|
| 728 |
+
|
| 729 |
+
state["targets"] = []
|
| 730 |
+
if self._target_id:
|
| 731 |
+
state["targets"].append(
|
| 732 |
+
ObjState.get_bullet_state(self._pybullet_client, self._target_id)
|
| 733 |
+
)
|
| 734 |
+
|
| 735 |
+
state["objects"] = []
|
| 736 |
+
for obj_id in self.get_obj_ids():
|
| 737 |
+
state["objects"].append(
|
| 738 |
+
ObjState.get_bullet_state(self._pybullet_client, obj_id)
|
| 739 |
+
)
|
| 740 |
+
|
| 741 |
+
return state
|
| 742 |
+
|
| 743 |
+
def set_pybullet_state(self, state):
|
| 744 |
+
"""Restore pyullet state.
|
| 745 |
+
|
| 746 |
+
WARNING: py_environment wrapper assumes environments aren't reset in their
|
| 747 |
+
constructor and will often reset the environment unintentionally. It is
|
| 748 |
+
always recommended that you call env.reset on the tfagents wrapper before
|
| 749 |
+
playback (replaying pybullet_state).
|
| 750 |
+
|
| 751 |
+
Args:
|
| 752 |
+
state: dict containing 'robots', 'robot_end_effectors', 'targets',
|
| 753 |
+
'objects', each containing a list of ObjState.
|
| 754 |
+
"""
|
| 755 |
+
|
| 756 |
+
assert isinstance(state["robots"][0], XarmState)
|
| 757 |
+
xarm_state: XarmState = state["robots"][0]
|
| 758 |
+
xarm_state.set_bullet_state(self._pybullet_client, self.robot.xarm)
|
| 759 |
+
self._set_robot_target_effector_pose(xarm_state.target_effector_pose)
|
| 760 |
+
|
| 761 |
+
def _set_state_safe(obj_state, obj_id):
|
| 762 |
+
if obj_state is not None:
|
| 763 |
+
assert obj_id is not None, "Cannot set state for missing object."
|
| 764 |
+
obj_state.set_bullet_state(self._pybullet_client, obj_id)
|
| 765 |
+
else:
|
| 766 |
+
assert obj_id is None, f"No state found for obj_id {obj_id}"
|
| 767 |
+
|
| 768 |
+
robot_end_effectors = state["robot_end_effectors"]
|
| 769 |
+
_set_state_safe(
|
| 770 |
+
None if not robot_end_effectors else robot_end_effectors[0],
|
| 771 |
+
self.robot.end_effector,
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
targets = state["targets"]
|
| 775 |
+
_set_state_safe(None if not targets else targets[0], self._target_id)
|
| 776 |
+
|
| 777 |
+
obj_ids = self.get_obj_ids()
|
| 778 |
+
assert len(state["objects"]) == len(obj_ids), "State length mismatch"
|
| 779 |
+
for obj_state, obj_id in zip(state["objects"], obj_ids):
|
| 780 |
+
_set_state_safe(obj_state, obj_id)
|
| 781 |
+
|
| 782 |
+
self.reset(reset_poses=False)
|
| 783 |
+
|
| 784 |
+
|
| 785 |
+
class BlockPushNormalized(gym.Env):
|
| 786 |
+
"""Simple XArm environment for block pushing, normalized state and actions."""
|
| 787 |
+
|
| 788 |
+
def __init__(
|
| 789 |
+
self,
|
| 790 |
+
control_frequency=10.0,
|
| 791 |
+
task=BlockTaskVariant.PUSH_NORMALIZED,
|
| 792 |
+
image_size=None,
|
| 793 |
+
shared_memory=False,
|
| 794 |
+
seed=None,
|
| 795 |
+
):
|
| 796 |
+
"""Creates an env instance.
|
| 797 |
+
|
| 798 |
+
Args:
|
| 799 |
+
control_frequency: Control frequency for the arm. Each env step will
|
| 800 |
+
advance the simulation by 1/control_frequency seconds.
|
| 801 |
+
task: enum for which task, see BlockTaskVariant enum.
|
| 802 |
+
image_size: Optional image size (height, width). If None, no image
|
| 803 |
+
observations will be used.
|
| 804 |
+
shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
|
| 805 |
+
pybullet. Useful to debug.
|
| 806 |
+
seed: Optional seed for the environment.
|
| 807 |
+
"""
|
| 808 |
+
# Map normalized task to unnormalized task.
|
| 809 |
+
if task == BlockTaskVariant.PUSH_NORMALIZED:
|
| 810 |
+
env_task = BlockTaskVariant.PUSH
|
| 811 |
+
elif task == BlockTaskVariant.REACH_NORMALIZED:
|
| 812 |
+
env_task = BlockTaskVariant.REACH
|
| 813 |
+
else:
|
| 814 |
+
raise ValueError("Unsupported task %s" % str(task))
|
| 815 |
+
self._env = BlockPush(
|
| 816 |
+
control_frequency, env_task, image_size, shared_memory, seed
|
| 817 |
+
)
|
| 818 |
+
self.action_space = spaces.Box(low=-1, high=1, shape=(2,))
|
| 819 |
+
self.observation_space = spaces.Dict(
|
| 820 |
+
collections.OrderedDict(
|
| 821 |
+
effector_target_translation=spaces.Box(low=-1, high=1, shape=(2,)),
|
| 822 |
+
effector_target_to_block_translation=spaces.Box(
|
| 823 |
+
low=-1, high=1, shape=(2,)
|
| 824 |
+
),
|
| 825 |
+
block_orientation_cos_sin=spaces.Box(low=-1, high=1, shape=(2,)),
|
| 826 |
+
effector_target_to_target_translation=spaces.Box(
|
| 827 |
+
low=-1, high=1, shape=(2,)
|
| 828 |
+
),
|
| 829 |
+
target_orientation_cos_sin=spaces.Box(low=-1, high=1, shape=(2,)),
|
| 830 |
+
)
|
| 831 |
+
)
|
| 832 |
+
self.reset()
|
| 833 |
+
|
| 834 |
+
def get_control_frequency(self):
|
| 835 |
+
return self._env.get_control_frequency()
|
| 836 |
+
|
| 837 |
+
@property
|
| 838 |
+
def reach_target_translation(self):
|
| 839 |
+
return self._env.reach_target_translation
|
| 840 |
+
|
| 841 |
+
def seed(self, seed=None):
|
| 842 |
+
self._env.seed(seed)
|
| 843 |
+
|
| 844 |
+
def reset(self):
|
| 845 |
+
state = self._env.reset()
|
| 846 |
+
return self.calc_normalized_state(state)
|
| 847 |
+
|
| 848 |
+
def step(self, action):
|
| 849 |
+
# The environment is normalized [mean-3*std, mean+3*std] -> [-1, 1].
|
| 850 |
+
action = np.clip(action, a_min=-1.0, a_max=1.0)
|
| 851 |
+
state, reward, done, info = self._env.step(
|
| 852 |
+
self.calc_unnormalized_action(action)
|
| 853 |
+
)
|
| 854 |
+
state = self.calc_normalized_state(state)
|
| 855 |
+
reward = reward * 100 # Keep returns in [0, 100]
|
| 856 |
+
return state, reward, done, info
|
| 857 |
+
|
| 858 |
+
def render(self, mode="rgb_array"):
|
| 859 |
+
return self._env.render(mode)
|
| 860 |
+
|
| 861 |
+
def close(self):
|
| 862 |
+
self._env.close()
|
| 863 |
+
|
| 864 |
+
@staticmethod
|
| 865 |
+
def _normalize(values, values_min, values_max):
|
| 866 |
+
offset = (values_max + values_min) * 0.5
|
| 867 |
+
scale = (values_max - values_min) * 0.5
|
| 868 |
+
return (values - offset) / scale # [min, max] -> [-1, 1]
|
| 869 |
+
|
| 870 |
+
@staticmethod
|
| 871 |
+
def _unnormalize(values, values_min, values_max):
|
| 872 |
+
offset = (values_max + values_min) * 0.5
|
| 873 |
+
scale = (values_max - values_min) * 0.5
|
| 874 |
+
return values * scale + offset # [-1, 1] -> [min, max]
|
| 875 |
+
|
| 876 |
+
@classmethod
|
| 877 |
+
def calc_normalized_action(cls, action):
|
| 878 |
+
return cls._normalize(action, ACTION_MIN, ACTION_MAX)
|
| 879 |
+
|
| 880 |
+
@classmethod
|
| 881 |
+
def calc_unnormalized_action(cls, norm_action):
|
| 882 |
+
return cls._unnormalize(norm_action, ACTION_MIN, ACTION_MAX)
|
| 883 |
+
|
| 884 |
+
@classmethod
|
| 885 |
+
def calc_normalized_state(cls, state):
|
| 886 |
+
|
| 887 |
+
effector_target_translation = cls._normalize(
|
| 888 |
+
state["effector_target_translation"],
|
| 889 |
+
EFFECTOR_TARGET_TRANSLATION_MIN,
|
| 890 |
+
EFFECTOR_TARGET_TRANSLATION_MAX,
|
| 891 |
+
)
|
| 892 |
+
|
| 893 |
+
effector_target_to_block_translation = cls._normalize(
|
| 894 |
+
state["block_translation"] - state["effector_target_translation"],
|
| 895 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN,
|
| 896 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX,
|
| 897 |
+
)
|
| 898 |
+
ori = state["block_orientation"][0]
|
| 899 |
+
block_orientation_cos_sin = cls._normalize(
|
| 900 |
+
np.array([math.cos(ori), math.sin(ori)], np.float32),
|
| 901 |
+
BLOCK_ORIENTATION_COS_SIN_MIN,
|
| 902 |
+
BLOCK_ORIENTATION_COS_SIN_MAX,
|
| 903 |
+
)
|
| 904 |
+
|
| 905 |
+
effector_target_to_target_translation = cls._normalize(
|
| 906 |
+
state["target_translation"] - state["effector_target_translation"],
|
| 907 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN,
|
| 908 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX,
|
| 909 |
+
)
|
| 910 |
+
ori = state["target_orientation"][0]
|
| 911 |
+
target_orientation_cos_sin = cls._normalize(
|
| 912 |
+
np.array([math.cos(ori), math.sin(ori)], np.float32),
|
| 913 |
+
TARGET_ORIENTATION_COS_SIN_MIN,
|
| 914 |
+
TARGET_ORIENTATION_COS_SIN_MAX,
|
| 915 |
+
)
|
| 916 |
+
|
| 917 |
+
# Note: We do not include effector_translation in the normalized state.
|
| 918 |
+
# This means the unnormalized -> normalized mapping is not invertable.
|
| 919 |
+
return collections.OrderedDict(
|
| 920 |
+
effector_target_translation=effector_target_translation,
|
| 921 |
+
effector_target_to_block_translation=effector_target_to_block_translation,
|
| 922 |
+
block_orientation_cos_sin=block_orientation_cos_sin,
|
| 923 |
+
effector_target_to_target_translation=effector_target_to_target_translation,
|
| 924 |
+
target_orientation_cos_sin=target_orientation_cos_sin,
|
| 925 |
+
)
|
| 926 |
+
|
| 927 |
+
@classmethod
|
| 928 |
+
def calc_unnormalized_state(cls, norm_state):
|
| 929 |
+
|
| 930 |
+
effector_target_translation = cls._unnormalize(
|
| 931 |
+
norm_state["effector_target_translation"],
|
| 932 |
+
EFFECTOR_TARGET_TRANSLATION_MIN,
|
| 933 |
+
EFFECTOR_TARGET_TRANSLATION_MAX,
|
| 934 |
+
)
|
| 935 |
+
# Note: normalized state does not include effector_translation state, this
|
| 936 |
+
# means this component will be missing (and is marked nan).
|
| 937 |
+
effector_translation = np.array([np.nan, np.nan], np.float32)
|
| 938 |
+
|
| 939 |
+
effector_target_to_block_translation = cls._unnormalize(
|
| 940 |
+
norm_state["effector_target_to_block_translation"],
|
| 941 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN,
|
| 942 |
+
EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX,
|
| 943 |
+
)
|
| 944 |
+
block_translation = (
|
| 945 |
+
effector_target_to_block_translation + effector_target_translation
|
| 946 |
+
)
|
| 947 |
+
ori_cos_sin = cls._unnormalize(
|
| 948 |
+
norm_state["block_orientation_cos_sin"],
|
| 949 |
+
BLOCK_ORIENTATION_COS_SIN_MIN,
|
| 950 |
+
BLOCK_ORIENTATION_COS_SIN_MAX,
|
| 951 |
+
)
|
| 952 |
+
block_orientation = np.array(
|
| 953 |
+
[math.atan2(ori_cos_sin[1], ori_cos_sin[0])], np.float32
|
| 954 |
+
)
|
| 955 |
+
|
| 956 |
+
effector_target_to_target_translation = cls._unnormalize(
|
| 957 |
+
norm_state["effector_target_to_target_translation"],
|
| 958 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN,
|
| 959 |
+
EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX,
|
| 960 |
+
)
|
| 961 |
+
target_translation = (
|
| 962 |
+
effector_target_to_target_translation + effector_target_translation
|
| 963 |
+
)
|
| 964 |
+
ori_cos_sin = cls._unnormalize(
|
| 965 |
+
norm_state["target_orientation_cos_sin"],
|
| 966 |
+
TARGET_ORIENTATION_COS_SIN_MIN,
|
| 967 |
+
TARGET_ORIENTATION_COS_SIN_MAX,
|
| 968 |
+
)
|
| 969 |
+
target_orientation = np.array(
|
| 970 |
+
[math.atan2(ori_cos_sin[1], ori_cos_sin[0])], np.float32
|
| 971 |
+
)
|
| 972 |
+
|
| 973 |
+
return collections.OrderedDict(
|
| 974 |
+
block_translation=block_translation,
|
| 975 |
+
block_orientation=block_orientation,
|
| 976 |
+
effector_translation=effector_translation,
|
| 977 |
+
effector_target_translation=effector_target_translation,
|
| 978 |
+
target_translation=target_translation,
|
| 979 |
+
target_orientation=target_orientation,
|
| 980 |
+
)
|
| 981 |
+
|
| 982 |
+
def get_pybullet_state(self):
|
| 983 |
+
return self._env.get_pybullet_state()
|
| 984 |
+
|
| 985 |
+
def set_pybullet_state(self, state):
|
| 986 |
+
return self._env.set_pybullet_state(state)
|
| 987 |
+
|
| 988 |
+
@property
|
| 989 |
+
def pybullet_client(self):
|
| 990 |
+
return self._env.pybullet_client
|
| 991 |
+
|
| 992 |
+
def calc_camera_params(self, image_size):
|
| 993 |
+
return self._env.calc_camera_params(image_size)
|
| 994 |
+
|
| 995 |
+
def _compute_state(self):
|
| 996 |
+
return self.calc_normalized_state(
|
| 997 |
+
self._env._compute_state()
|
| 998 |
+
) # pylint: disable=protected-access
|
| 999 |
+
|
| 1000 |
+
|
| 1001 |
+
# Make sure we only register once to allow us to reload the module in colab for
|
| 1002 |
+
# debugging.
|
| 1003 |
+
if "BlockPush-v0" in registration.registry.env_specs:
|
| 1004 |
+
del registration.registry.env_specs["BlockInsert-v0"]
|
| 1005 |
+
del registration.registry.env_specs["BlockPush-v0"]
|
| 1006 |
+
del registration.registry.env_specs["BlockPushNormalized-v0"]
|
| 1007 |
+
del registration.registry.env_specs["BlockPushRgbNormalized-v0"]
|
| 1008 |
+
del registration.registry.env_specs["BlockReach-v0"]
|
| 1009 |
+
del registration.registry.env_specs["BlockReachNormalized-v0"]
|
| 1010 |
+
del registration.registry.env_specs["BlockReachRgbNormalized-v0"]
|
| 1011 |
+
del registration.registry.env_specs["SharedBlockInsert-v0"]
|
| 1012 |
+
del registration.registry.env_specs["SharedBlockPush-v0"]
|
| 1013 |
+
del registration.registry.env_specs["SharedBlockReach-v0"]
|
| 1014 |
+
|
| 1015 |
+
registration.register(
|
| 1016 |
+
id="BlockInsert-v0",
|
| 1017 |
+
entry_point=BlockPush,
|
| 1018 |
+
kwargs=dict(task=BlockTaskVariant.INSERT),
|
| 1019 |
+
max_episode_steps=50,
|
| 1020 |
+
)
|
| 1021 |
+
registration.register(id="BlockPush-v0", entry_point=BlockPush, max_episode_steps=100)
|
| 1022 |
+
registration.register(
|
| 1023 |
+
id="BlockPushNormalized-v0",
|
| 1024 |
+
entry_point=BlockPushNormalized,
|
| 1025 |
+
kwargs=dict(task=BlockTaskVariant.PUSH_NORMALIZED),
|
| 1026 |
+
max_episode_steps=100,
|
| 1027 |
+
)
|
| 1028 |
+
registration.register(
|
| 1029 |
+
id="BlockPushRgb-v0",
|
| 1030 |
+
entry_point=BlockPush,
|
| 1031 |
+
max_episode_steps=100,
|
| 1032 |
+
kwargs=dict(image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)),
|
| 1033 |
+
)
|
| 1034 |
+
registration.register(
|
| 1035 |
+
id="BlockPushRgbNormalized-v0",
|
| 1036 |
+
entry_point=BlockPushNormalized,
|
| 1037 |
+
kwargs=dict(
|
| 1038 |
+
task=BlockTaskVariant.PUSH_NORMALIZED, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)
|
| 1039 |
+
),
|
| 1040 |
+
max_episode_steps=100,
|
| 1041 |
+
)
|
| 1042 |
+
registration.register(
|
| 1043 |
+
id="BlockReach-v0",
|
| 1044 |
+
entry_point=BlockPush,
|
| 1045 |
+
kwargs=dict(task=BlockTaskVariant.REACH),
|
| 1046 |
+
max_episode_steps=50,
|
| 1047 |
+
)
|
| 1048 |
+
registration.register(
|
| 1049 |
+
id="BlockReachRgb-v0",
|
| 1050 |
+
entry_point=BlockPush,
|
| 1051 |
+
max_episode_steps=100,
|
| 1052 |
+
kwargs=dict(task=BlockTaskVariant.REACH, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)),
|
| 1053 |
+
)
|
| 1054 |
+
registration.register(
|
| 1055 |
+
id="BlockReachNormalized-v0",
|
| 1056 |
+
entry_point=BlockPushNormalized,
|
| 1057 |
+
kwargs=dict(task=BlockTaskVariant.REACH_NORMALIZED),
|
| 1058 |
+
max_episode_steps=50,
|
| 1059 |
+
)
|
| 1060 |
+
registration.register(
|
| 1061 |
+
id="BlockReachRgbNormalized-v0",
|
| 1062 |
+
entry_point=BlockPushNormalized,
|
| 1063 |
+
kwargs=dict(
|
| 1064 |
+
task=BlockTaskVariant.REACH_NORMALIZED, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)
|
| 1065 |
+
),
|
| 1066 |
+
max_episode_steps=50,
|
| 1067 |
+
)
|
| 1068 |
+
|
| 1069 |
+
registration.register(
|
| 1070 |
+
id="SharedBlockInsert-v0",
|
| 1071 |
+
entry_point=BlockPush,
|
| 1072 |
+
kwargs=dict(task=BlockTaskVariant.INSERT, shared_memory=True),
|
| 1073 |
+
max_episode_steps=50,
|
| 1074 |
+
)
|
| 1075 |
+
registration.register(
|
| 1076 |
+
id="SharedBlockPush-v0",
|
| 1077 |
+
entry_point=BlockPush,
|
| 1078 |
+
kwargs=dict(shared_memory=True),
|
| 1079 |
+
max_episode_steps=100,
|
| 1080 |
+
)
|
| 1081 |
+
registration.register(
|
| 1082 |
+
id="SharedBlockPushNormalized-v0",
|
| 1083 |
+
entry_point=BlockPushNormalized,
|
| 1084 |
+
kwargs=dict(task=BlockTaskVariant.PUSH_NORMALIZED, shared_memory=True),
|
| 1085 |
+
max_episode_steps=100,
|
| 1086 |
+
)
|
| 1087 |
+
registration.register(
|
| 1088 |
+
id="SharedBlockReach-v0",
|
| 1089 |
+
entry_point=BlockPush,
|
| 1090 |
+
kwargs=dict(task=BlockTaskVariant.REACH, shared_memory=True),
|
| 1091 |
+
max_episode_steps=50,
|
| 1092 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_discontinuous.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Discontinuous block pushing."""
|
| 17 |
+
import collections
|
| 18 |
+
import enum
|
| 19 |
+
import math
|
| 20 |
+
from typing import List, Optional
|
| 21 |
+
|
| 22 |
+
from gym import spaces
|
| 23 |
+
from gym.envs import registration
|
| 24 |
+
from cleandiffuser.env.block_pushing import block_pushing
|
| 25 |
+
from cleandiffuser.env.block_pushing.utils import utils_pybullet
|
| 26 |
+
from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
|
| 27 |
+
import numpy as np
|
| 28 |
+
from scipy.spatial import transform
|
| 29 |
+
import pybullet
|
| 30 |
+
import pybullet_utils.bullet_client as bullet_client
|
| 31 |
+
|
| 32 |
+
ZONE2_URDF_PATH = "third_party/py/envs/assets/zone2.urdf"
|
| 33 |
+
|
| 34 |
+
MIN_TARGET_DIST = 0.15
|
| 35 |
+
NUM_RESET_ATTEMPTS = 1000
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def build_env_name(task, shared_memory, use_image_obs):
|
| 39 |
+
"""Construct the env name from parameters."""
|
| 40 |
+
del task
|
| 41 |
+
env_name = "BlockPushDiscontinuous"
|
| 42 |
+
|
| 43 |
+
if use_image_obs:
|
| 44 |
+
env_name = env_name + "Rgb"
|
| 45 |
+
|
| 46 |
+
if shared_memory:
|
| 47 |
+
env_name = "Shared" + env_name
|
| 48 |
+
|
| 49 |
+
env_name = env_name + "-v0"
|
| 50 |
+
|
| 51 |
+
return env_name
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class BlockTaskVariant(enum.Enum):
|
| 55 |
+
REACH = "Reach"
|
| 56 |
+
REACH_NORMALIZED = "ReachNormalized"
|
| 57 |
+
PUSH = "Push"
|
| 58 |
+
PUSH_NORMALIZED = "PushNormalized"
|
| 59 |
+
INSERT = "Insert"
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# pytype: skip-file
|
| 63 |
+
class BlockPushDiscontinuous(block_pushing.BlockPush):
|
| 64 |
+
"""Discontinuous block pushing."""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
control_frequency=10.0,
|
| 69 |
+
task=BlockTaskVariant.PUSH,
|
| 70 |
+
image_size=None,
|
| 71 |
+
shared_memory=False,
|
| 72 |
+
seed=None,
|
| 73 |
+
goal_dist_tolerance=0.04,
|
| 74 |
+
):
|
| 75 |
+
super(BlockPushDiscontinuous, self).__init__(
|
| 76 |
+
control_frequency=control_frequency,
|
| 77 |
+
task=task,
|
| 78 |
+
image_size=image_size,
|
| 79 |
+
shared_memory=shared_memory,
|
| 80 |
+
seed=seed,
|
| 81 |
+
goal_dist_tolerance=goal_dist_tolerance,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def target_poses(self):
|
| 86 |
+
return self._target_poses
|
| 87 |
+
|
| 88 |
+
def get_goal_translation(self):
|
| 89 |
+
"""Return the translation component of the goal (2D)."""
|
| 90 |
+
if self._target_poses:
|
| 91 |
+
return [i.translation for i in self._target_poses]
|
| 92 |
+
else:
|
| 93 |
+
return None
|
| 94 |
+
|
| 95 |
+
def _setup_pybullet_scene(self):
|
| 96 |
+
self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
|
| 97 |
+
|
| 98 |
+
# Temporarily disable rendering to speed up loading URDFs.
|
| 99 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
|
| 100 |
+
|
| 101 |
+
self._setup_workspace_and_robot()
|
| 102 |
+
target_urdf_path = block_pushing.ZONE_URDF_PATH
|
| 103 |
+
|
| 104 |
+
self._target_ids = []
|
| 105 |
+
for _ in [block_pushing.ZONE_URDF_PATH, ZONE2_URDF_PATH]:
|
| 106 |
+
self._target_ids.append(
|
| 107 |
+
utils_pybullet.load_urdf(
|
| 108 |
+
self._pybullet_client, target_urdf_path, useFixedBase=True
|
| 109 |
+
)
|
| 110 |
+
)
|
| 111 |
+
self._block_ids = [
|
| 112 |
+
utils_pybullet.load_urdf(
|
| 113 |
+
self._pybullet_client, block_pushing.BLOCK_URDF_PATH, useFixedBase=False
|
| 114 |
+
)
|
| 115 |
+
]
|
| 116 |
+
|
| 117 |
+
# Re-enable rendering.
|
| 118 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
|
| 119 |
+
|
| 120 |
+
self.step_simulation_to_stabilize()
|
| 121 |
+
|
| 122 |
+
def _reset_target_poses(self, workspace_center_x):
|
| 123 |
+
"""Resets target poses."""
|
| 124 |
+
self._target_poses = [None for _ in range(len(self._target_ids))]
|
| 125 |
+
|
| 126 |
+
def _reset_target_pose(idx, avoid=None):
|
| 127 |
+
def _get_random_translation():
|
| 128 |
+
# Choose x,y randomly.
|
| 129 |
+
target_x = workspace_center_x + self._rng.uniform(low=-0.10, high=0.10)
|
| 130 |
+
# Fix ys for this environment.
|
| 131 |
+
if idx == 0:
|
| 132 |
+
target_y = 0
|
| 133 |
+
else:
|
| 134 |
+
target_y = 0.4
|
| 135 |
+
target_translation = np.array([target_x, target_y, 0.020])
|
| 136 |
+
return target_translation
|
| 137 |
+
|
| 138 |
+
if avoid is None:
|
| 139 |
+
target_translation = _get_random_translation()
|
| 140 |
+
else:
|
| 141 |
+
# Reject targets too close to `avoid`.
|
| 142 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 143 |
+
target_translation = _get_random_translation()
|
| 144 |
+
dist = np.linalg.norm(target_translation[0] - avoid[0])
|
| 145 |
+
if dist > MIN_TARGET_DIST:
|
| 146 |
+
break
|
| 147 |
+
target_sampled_angle = math.pi + self._rng.uniform(
|
| 148 |
+
low=-math.pi / 6, high=math.pi / 6
|
| 149 |
+
)
|
| 150 |
+
target_rotation = transform.Rotation.from_rotvec(
|
| 151 |
+
[0, 0, target_sampled_angle]
|
| 152 |
+
)
|
| 153 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 154 |
+
self._target_ids[idx],
|
| 155 |
+
target_translation.tolist(),
|
| 156 |
+
target_rotation.as_quat().tolist(),
|
| 157 |
+
)
|
| 158 |
+
self._target_poses[idx] = Pose3d(
|
| 159 |
+
rotation=target_rotation, translation=target_translation
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
try_idx = 0
|
| 163 |
+
while True:
|
| 164 |
+
# Choose the first target.
|
| 165 |
+
_reset_target_pose(0)
|
| 166 |
+
# Choose the second target, avoiding the first.
|
| 167 |
+
_reset_target_pose(1, avoid=self._target_poses[0].translation)
|
| 168 |
+
dist = np.linalg.norm(
|
| 169 |
+
self._target_poses[0].translation[0]
|
| 170 |
+
- self._target_poses[1].translation[0]
|
| 171 |
+
)
|
| 172 |
+
if dist > MIN_TARGET_DIST:
|
| 173 |
+
break
|
| 174 |
+
try_idx += 1
|
| 175 |
+
if try_idx >= NUM_RESET_ATTEMPTS:
|
| 176 |
+
raise ValueError("could not find matching target")
|
| 177 |
+
assert dist > MIN_TARGET_DIST
|
| 178 |
+
|
| 179 |
+
def reset(self):
|
| 180 |
+
self._pybullet_client.restoreState(self._saved_state)
|
| 181 |
+
|
| 182 |
+
rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
|
| 183 |
+
translation = np.array([0.3, -0.4, block_pushing.EFFECTOR_HEIGHT])
|
| 184 |
+
starting_pose = Pose3d(rotation=rotation, translation=translation)
|
| 185 |
+
self._set_robot_target_effector_pose(starting_pose)
|
| 186 |
+
|
| 187 |
+
workspace_center_x = 0.4
|
| 188 |
+
|
| 189 |
+
# Reset block pose.
|
| 190 |
+
block_x = workspace_center_x + self._rng.uniform(low=-0.1, high=0.1)
|
| 191 |
+
block_y = -0.2 + self._rng.uniform(low=-0.15, high=0.15)
|
| 192 |
+
block_translation = np.array([block_x, block_y, 0])
|
| 193 |
+
block_sampled_angle = self._rng.uniform(math.pi)
|
| 194 |
+
block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
|
| 195 |
+
|
| 196 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 197 |
+
self._block_ids[0],
|
| 198 |
+
block_translation.tolist(),
|
| 199 |
+
block_rotation.as_quat().tolist(),
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
# Reset target pose.
|
| 203 |
+
self._reset_target_poses(workspace_center_x)
|
| 204 |
+
|
| 205 |
+
self.step_simulation_to_stabilize()
|
| 206 |
+
state = self._compute_state()
|
| 207 |
+
self._previous_state = state
|
| 208 |
+
self.min_dist_to_first_goal = np.inf
|
| 209 |
+
self.min_dist_to_second_goal = np.inf
|
| 210 |
+
self.steps = 0
|
| 211 |
+
return state
|
| 212 |
+
|
| 213 |
+
def _compute_goal_distance(self, state):
|
| 214 |
+
# Reward is 1. blocks is inside any target.
|
| 215 |
+
return np.mean([self.min_dist_to_first_goal, self.min_dist_to_second_goal])
|
| 216 |
+
|
| 217 |
+
def _compute_state(self):
|
| 218 |
+
effector_pose = self._robot.forward_kinematics()
|
| 219 |
+
block_position_and_orientation = (
|
| 220 |
+
self._pybullet_client.getBasePositionAndOrientation(self._block_ids[0])
|
| 221 |
+
)
|
| 222 |
+
block_pose = Pose3d(
|
| 223 |
+
rotation=transform.Rotation.from_quat(block_position_and_orientation[1]),
|
| 224 |
+
translation=block_position_and_orientation[0],
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def _yaw_from_pose(pose):
|
| 228 |
+
return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1]])
|
| 229 |
+
|
| 230 |
+
obs = collections.OrderedDict(
|
| 231 |
+
block_translation=block_pose.translation[0:2],
|
| 232 |
+
block_orientation=_yaw_from_pose(block_pose),
|
| 233 |
+
effector_translation=effector_pose.translation[0:2],
|
| 234 |
+
effector_target_translation=self._target_effector_pose.translation[0:2],
|
| 235 |
+
target_translation=self._target_poses[0].translation[0:2],
|
| 236 |
+
target_orientation=_yaw_from_pose(self._target_poses[0]),
|
| 237 |
+
target2_translation=self._target_poses[1].translation[0:2],
|
| 238 |
+
target2_orientation=_yaw_from_pose(self._target_poses[1]),
|
| 239 |
+
)
|
| 240 |
+
if self._image_size is not None:
|
| 241 |
+
obs["rgb"] = self._render_camera(self._image_size)
|
| 242 |
+
return obs
|
| 243 |
+
|
| 244 |
+
def step(self, action):
|
| 245 |
+
self._step_robot_and_sim(action)
|
| 246 |
+
state = self._compute_state()
|
| 247 |
+
reward = self._get_reward(state)
|
| 248 |
+
done = False
|
| 249 |
+
if reward > 0.0:
|
| 250 |
+
done = True
|
| 251 |
+
# Cache so we can compute success.
|
| 252 |
+
self.state = state
|
| 253 |
+
return state, reward, done, {}
|
| 254 |
+
|
| 255 |
+
def dist(self, state, target):
|
| 256 |
+
# Reward is 1. blocks is inside any target.
|
| 257 |
+
return np.linalg.norm(
|
| 258 |
+
state["block_translation"] - state["%s_translation" % target]
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
def _get_reward(self, state):
|
| 262 |
+
"""Reward is 1.0 if agent hits both goals and stays at second."""
|
| 263 |
+
# This also statefully updates these values.
|
| 264 |
+
self.min_dist_to_first_goal = min(
|
| 265 |
+
self.dist(state, "target"), self.min_dist_to_first_goal
|
| 266 |
+
)
|
| 267 |
+
self.min_dist_to_second_goal = min(
|
| 268 |
+
self.dist(state, "target2"), self.min_dist_to_second_goal
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
def _reward(thresh):
|
| 272 |
+
reward_first = True if self.min_dist_to_first_goal < thresh else False
|
| 273 |
+
reward_second = True if self.min_dist_to_second_goal < thresh else False
|
| 274 |
+
return 1.0 if (reward_first and reward_second) else 0.0
|
| 275 |
+
|
| 276 |
+
reward = _reward(self.goal_dist_tolerance)
|
| 277 |
+
return reward
|
| 278 |
+
|
| 279 |
+
@property
|
| 280 |
+
def succeeded(self):
|
| 281 |
+
thresh = self.goal_dist_tolerance
|
| 282 |
+
hit_first = True if self.min_dist_to_first_goal < thresh else False
|
| 283 |
+
hit_second = True if self.min_dist_to_first_goal < thresh else False
|
| 284 |
+
current_distance_to_second = self.dist(self.state, "target2")
|
| 285 |
+
still_at_second = True if current_distance_to_second < thresh else False
|
| 286 |
+
return hit_first and hit_second and still_at_second
|
| 287 |
+
|
| 288 |
+
def _create_observation_space(self, image_size):
|
| 289 |
+
pi2 = math.pi * 2
|
| 290 |
+
|
| 291 |
+
obs_dict = collections.OrderedDict(
|
| 292 |
+
block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 293 |
+
block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
|
| 294 |
+
effector_translation=spaces.Box(
|
| 295 |
+
# Small buffer for to IK noise.
|
| 296 |
+
low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
|
| 297 |
+
high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
|
| 298 |
+
), # x,y
|
| 299 |
+
effector_target_translation=spaces.Box(
|
| 300 |
+
# Small buffer for to IK noise.
|
| 301 |
+
low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
|
| 302 |
+
high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
|
| 303 |
+
), # x,y
|
| 304 |
+
target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 305 |
+
target_orientation=spaces.Box(
|
| 306 |
+
low=-pi2,
|
| 307 |
+
high=pi2,
|
| 308 |
+
shape=(1,),
|
| 309 |
+
), # theta
|
| 310 |
+
target2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 311 |
+
target2_orientation=spaces.Box(
|
| 312 |
+
low=-pi2,
|
| 313 |
+
high=pi2,
|
| 314 |
+
shape=(1,),
|
| 315 |
+
), # theta
|
| 316 |
+
)
|
| 317 |
+
if image_size is not None:
|
| 318 |
+
obs_dict["rgb"] = spaces.Box(
|
| 319 |
+
low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
|
| 320 |
+
)
|
| 321 |
+
return spaces.Dict(obs_dict)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
if "BlockPushDiscontinuous-v0" in registration.registry.env_specs:
|
| 325 |
+
del registration.registry.env_specs["BlockPushDiscontinuous-v0"]
|
| 326 |
+
|
| 327 |
+
registration.register(
|
| 328 |
+
id="BlockPushDiscontinuous-v0",
|
| 329 |
+
entry_point=BlockPushDiscontinuous,
|
| 330 |
+
max_episode_steps=200,
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
registration.register(
|
| 334 |
+
id="BlockPushDiscontinuousRgb-v0",
|
| 335 |
+
entry_point=BlockPushDiscontinuous,
|
| 336 |
+
max_episode_steps=200,
|
| 337 |
+
kwargs=dict(image_size=(block_pushing.IMAGE_HEIGHT, block_pushing.IMAGE_WIDTH)),
|
| 338 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_multimodal.py
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Multimodal block environments for the XArm."""
|
| 17 |
+
|
| 18 |
+
import collections
|
| 19 |
+
import logging
|
| 20 |
+
import math
|
| 21 |
+
from typing import Dict, List, Optional, Union
|
| 22 |
+
import copy
|
| 23 |
+
import time
|
| 24 |
+
|
| 25 |
+
from gym import spaces
|
| 26 |
+
from gym.envs import registration
|
| 27 |
+
from cleandiffuser.env.block_pushing import block_pushing
|
| 28 |
+
from cleandiffuser.env.block_pushing.utils import utils_pybullet
|
| 29 |
+
from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
|
| 30 |
+
from cleandiffuser.env.block_pushing.utils.utils_pybullet import ObjState
|
| 31 |
+
from cleandiffuser.env.block_pushing.utils.utils_pybullet import XarmState
|
| 32 |
+
import numpy as np
|
| 33 |
+
from scipy.spatial import transform
|
| 34 |
+
import pybullet
|
| 35 |
+
import pybullet_utils.bullet_client as bullet_client
|
| 36 |
+
|
| 37 |
+
# pytype: skip-file
|
| 38 |
+
BLOCK2_URDF_PATH = "third_party/py/envs/assets/block2.urdf"
|
| 39 |
+
ZONE2_URDF_PATH = "third_party/py/envs/assets/zone2.urdf"
|
| 40 |
+
|
| 41 |
+
# When resetting multiple targets, they should all be this far apart.
|
| 42 |
+
MIN_BLOCK_DIST = 0.1
|
| 43 |
+
MIN_TARGET_DIST = 0.12
|
| 44 |
+
# pylint: enable=line-too-long
|
| 45 |
+
NUM_RESET_ATTEMPTS = 1000
|
| 46 |
+
|
| 47 |
+
# Random movement of blocks
|
| 48 |
+
RANDOM_X_SHIFT = 0.1
|
| 49 |
+
RANDOM_Y_SHIFT = 0.15
|
| 50 |
+
|
| 51 |
+
logging.basicConfig(
|
| 52 |
+
level="INFO",
|
| 53 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 54 |
+
filemode="w",
|
| 55 |
+
)
|
| 56 |
+
logger = logging.getLogger()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def build_env_name(task, shared_memory, use_image_obs):
|
| 60 |
+
"""Construct the env name from parameters."""
|
| 61 |
+
del task
|
| 62 |
+
env_name = "BlockPushMultimodal"
|
| 63 |
+
|
| 64 |
+
if use_image_obs:
|
| 65 |
+
env_name = env_name + "Rgb"
|
| 66 |
+
|
| 67 |
+
if shared_memory:
|
| 68 |
+
env_name = "Shared" + env_name
|
| 69 |
+
|
| 70 |
+
env_name = env_name + "-v0"
|
| 71 |
+
|
| 72 |
+
return env_name
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class BlockPushEventManager:
|
| 76 |
+
def __init__(self):
|
| 77 |
+
self.event_steps = {
|
| 78 |
+
'REACH_0': -1,
|
| 79 |
+
'REACH_1': -1,
|
| 80 |
+
'TARGET_0_0': -1,
|
| 81 |
+
'TARGET_0_1': -1,
|
| 82 |
+
'TARGET_1_0': -1,
|
| 83 |
+
'TARGET_1_1': -1
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
def reach(self, step, block_id):
|
| 87 |
+
key = f'REACH_{block_id}'
|
| 88 |
+
if self.event_steps[key] < 0:
|
| 89 |
+
self.event_steps[key] = step
|
| 90 |
+
|
| 91 |
+
def target(self, step, block_id, target_id):
|
| 92 |
+
key = f'TARGET_{block_id}_{target_id}'
|
| 93 |
+
if self.event_steps[key] < 0:
|
| 94 |
+
self.event_steps[key] = step
|
| 95 |
+
|
| 96 |
+
def reset(self):
|
| 97 |
+
for key in list(self.event_steps):
|
| 98 |
+
self.event_steps[key] = -1
|
| 99 |
+
|
| 100 |
+
def get_info(self):
|
| 101 |
+
return copy.deepcopy(self.event_steps)
|
| 102 |
+
|
| 103 |
+
class BlockPushMultimodal(block_pushing.BlockPush):
|
| 104 |
+
"""2 blocks, 2 targets."""
|
| 105 |
+
|
| 106 |
+
def __init__(
|
| 107 |
+
self,
|
| 108 |
+
control_frequency=10.0,
|
| 109 |
+
task=block_pushing.BlockTaskVariant.PUSH,
|
| 110 |
+
image_size=None,
|
| 111 |
+
shared_memory=False,
|
| 112 |
+
seed=None,
|
| 113 |
+
goal_dist_tolerance=0.05,
|
| 114 |
+
abs_action=False
|
| 115 |
+
):
|
| 116 |
+
"""Creates an env instance.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
control_frequency: Control frequency for the arm. Each env step will
|
| 120 |
+
advance the simulation by 1/control_frequency seconds.
|
| 121 |
+
task: enum for which task, see BlockTaskVariant enum.
|
| 122 |
+
image_size: Optional image size (height, width). If None, no image
|
| 123 |
+
observations will be used.
|
| 124 |
+
shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
|
| 125 |
+
pybullet. Useful to debug.
|
| 126 |
+
seed: Optional seed for the environment.
|
| 127 |
+
goal_dist_tolerance: float, how far away from the goal to terminate.
|
| 128 |
+
"""
|
| 129 |
+
self._target_ids = None
|
| 130 |
+
self._target_poses = None
|
| 131 |
+
self._event_manager = BlockPushEventManager()
|
| 132 |
+
super(BlockPushMultimodal, self).__init__(
|
| 133 |
+
control_frequency=control_frequency,
|
| 134 |
+
task=task,
|
| 135 |
+
image_size=image_size,
|
| 136 |
+
shared_memory=shared_memory,
|
| 137 |
+
seed=seed,
|
| 138 |
+
goal_dist_tolerance=goal_dist_tolerance,
|
| 139 |
+
)
|
| 140 |
+
self._init_distance = [-1.0, -1.0]
|
| 141 |
+
self._in_target = [[-1.0, -1.0], [-1.0, -1.0]]
|
| 142 |
+
self._first_move = [-1, -1]
|
| 143 |
+
self._step_num = 0
|
| 144 |
+
self._abs_action = abs_action
|
| 145 |
+
|
| 146 |
+
@property
|
| 147 |
+
def target_poses(self):
|
| 148 |
+
return self._target_poses
|
| 149 |
+
|
| 150 |
+
def get_goal_translation(self):
|
| 151 |
+
"""Return the translation component of the goal (2D)."""
|
| 152 |
+
if self._target_poses:
|
| 153 |
+
return [i.translation for i in self._target_poses]
|
| 154 |
+
else:
|
| 155 |
+
return None
|
| 156 |
+
|
| 157 |
+
def _setup_pybullet_scene(self):
|
| 158 |
+
self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
|
| 159 |
+
|
| 160 |
+
# Temporarily disable rendering to speed up loading URDFs.
|
| 161 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
|
| 162 |
+
|
| 163 |
+
self._setup_workspace_and_robot()
|
| 164 |
+
|
| 165 |
+
self._target_ids = [
|
| 166 |
+
utils_pybullet.load_urdf(self._pybullet_client, i, useFixedBase=True)
|
| 167 |
+
for i in [block_pushing.ZONE_URDF_PATH, ZONE2_URDF_PATH]
|
| 168 |
+
]
|
| 169 |
+
self._block_ids = []
|
| 170 |
+
for i in [block_pushing.BLOCK_URDF_PATH, BLOCK2_URDF_PATH]:
|
| 171 |
+
self._block_ids.append(
|
| 172 |
+
utils_pybullet.load_urdf(self._pybullet_client, i, useFixedBase=False)
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
# Re-enable rendering.
|
| 176 |
+
pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
|
| 177 |
+
|
| 178 |
+
self.step_simulation_to_stabilize()
|
| 179 |
+
|
| 180 |
+
def _reset_block_poses(self, workspace_center_x):
|
| 181 |
+
"""Resets block poses."""
|
| 182 |
+
|
| 183 |
+
# Helper for choosing random block position.
|
| 184 |
+
def _reset_block_pose(idx, add=0.0, avoid=None):
|
| 185 |
+
def _get_random_translation():
|
| 186 |
+
block_x = (
|
| 187 |
+
workspace_center_x
|
| 188 |
+
+ add
|
| 189 |
+
+ self._rng.uniform(low=-RANDOM_X_SHIFT, high=RANDOM_X_SHIFT)
|
| 190 |
+
)
|
| 191 |
+
block_y = -0.2 + self._rng.uniform(
|
| 192 |
+
low=-RANDOM_Y_SHIFT, high=RANDOM_Y_SHIFT
|
| 193 |
+
)
|
| 194 |
+
block_translation = np.array([block_x, block_y, 0])
|
| 195 |
+
return block_translation
|
| 196 |
+
|
| 197 |
+
if avoid is None:
|
| 198 |
+
block_translation = _get_random_translation()
|
| 199 |
+
else:
|
| 200 |
+
# Reject targets too close to `avoid`.
|
| 201 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 202 |
+
block_translation = _get_random_translation()
|
| 203 |
+
dist = np.linalg.norm(block_translation[0] - avoid[0])
|
| 204 |
+
# print('block inner try_idx %d, dist %.3f' % (try_idx, dist))
|
| 205 |
+
if dist > MIN_BLOCK_DIST:
|
| 206 |
+
break
|
| 207 |
+
block_sampled_angle = self._rng.uniform(math.pi)
|
| 208 |
+
block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
|
| 209 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 210 |
+
self._block_ids[idx],
|
| 211 |
+
block_translation.tolist(),
|
| 212 |
+
block_rotation.as_quat().tolist(),
|
| 213 |
+
)
|
| 214 |
+
return block_translation
|
| 215 |
+
|
| 216 |
+
# Reject targets too close to `avoid`.
|
| 217 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 218 |
+
# Reset first block.
|
| 219 |
+
b0_translation = _reset_block_pose(0)
|
| 220 |
+
# Reset second block away from first block.
|
| 221 |
+
b1_translation = _reset_block_pose(1, avoid=b0_translation)
|
| 222 |
+
dist = np.linalg.norm(b0_translation[0] - b1_translation[0])
|
| 223 |
+
if dist > MIN_BLOCK_DIST:
|
| 224 |
+
break
|
| 225 |
+
else:
|
| 226 |
+
raise ValueError("could not find matching block")
|
| 227 |
+
assert dist > MIN_BLOCK_DIST
|
| 228 |
+
|
| 229 |
+
def _reset_target_poses(self, workspace_center_x):
|
| 230 |
+
"""Resets target poses."""
|
| 231 |
+
|
| 232 |
+
def _reset_target_pose(idx, add=0.0, avoid=None):
|
| 233 |
+
def _get_random_translation():
|
| 234 |
+
# Choose x,y randomly.
|
| 235 |
+
target_x = (
|
| 236 |
+
workspace_center_x
|
| 237 |
+
+ add
|
| 238 |
+
+ self._rng.uniform(
|
| 239 |
+
low=-0.05 * RANDOM_X_SHIFT, high=0.05 * RANDOM_X_SHIFT
|
| 240 |
+
)
|
| 241 |
+
)
|
| 242 |
+
target_y = 0.2 + self._rng.uniform(
|
| 243 |
+
low=-0.05 * RANDOM_Y_SHIFT, high=0.05 * RANDOM_Y_SHIFT
|
| 244 |
+
)
|
| 245 |
+
target_translation = np.array([target_x, target_y, 0.020])
|
| 246 |
+
return target_translation
|
| 247 |
+
|
| 248 |
+
if avoid is None:
|
| 249 |
+
target_translation = _get_random_translation()
|
| 250 |
+
else:
|
| 251 |
+
# Reject targets too close to `avoid`.
|
| 252 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 253 |
+
target_translation = _get_random_translation()
|
| 254 |
+
dist = np.linalg.norm(target_translation[0] - avoid[0])
|
| 255 |
+
# print('target inner try_idx %d, dist %.3f' % (try_idx, dist))
|
| 256 |
+
if dist > MIN_TARGET_DIST:
|
| 257 |
+
break
|
| 258 |
+
target_sampled_angle = math.pi + self._rng.uniform(
|
| 259 |
+
low=-math.pi / 30, high=math.pi / 30
|
| 260 |
+
)
|
| 261 |
+
target_rotation = transform.Rotation.from_rotvec(
|
| 262 |
+
[0, 0, target_sampled_angle]
|
| 263 |
+
)
|
| 264 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 265 |
+
self._target_ids[idx],
|
| 266 |
+
target_translation.tolist(),
|
| 267 |
+
target_rotation.as_quat().tolist(),
|
| 268 |
+
)
|
| 269 |
+
self._target_poses[idx] = Pose3d(
|
| 270 |
+
rotation=target_rotation, translation=target_translation
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
if self._target_poses is None:
|
| 274 |
+
self._target_poses = [None for _ in range(len(self._target_ids))]
|
| 275 |
+
|
| 276 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 277 |
+
# Choose the first target.
|
| 278 |
+
add = 0.12 * self._rng.choice([-1, 1])
|
| 279 |
+
# Randomly flip the location of the targets.
|
| 280 |
+
_reset_target_pose(0, add=add)
|
| 281 |
+
_reset_target_pose(1, add=-add, avoid=self._target_poses[0].translation)
|
| 282 |
+
dist = np.linalg.norm(
|
| 283 |
+
self._target_poses[0].translation[0]
|
| 284 |
+
- self._target_poses[1].translation[0]
|
| 285 |
+
)
|
| 286 |
+
if dist > MIN_TARGET_DIST:
|
| 287 |
+
break
|
| 288 |
+
else:
|
| 289 |
+
raise ValueError("could not find matching target")
|
| 290 |
+
assert dist > MIN_TARGET_DIST
|
| 291 |
+
|
| 292 |
+
def _reset_object_poses(self, workspace_center_x, workspace_center_y):
|
| 293 |
+
# Reset block poses.
|
| 294 |
+
self._reset_block_poses(workspace_center_x)
|
| 295 |
+
|
| 296 |
+
# Reset target poses.
|
| 297 |
+
self._reset_target_poses(workspace_center_x)
|
| 298 |
+
|
| 299 |
+
self._init_distance = [-1.0, -1.0]
|
| 300 |
+
self._in_target = [[-1.0, -1.0], [-1.0, -1.0]]
|
| 301 |
+
self._step_num = 0
|
| 302 |
+
|
| 303 |
+
def reset(self, reset_poses=True):
|
| 304 |
+
workspace_center_x = 0.4
|
| 305 |
+
workspace_center_y = 0.0
|
| 306 |
+
|
| 307 |
+
if reset_poses:
|
| 308 |
+
self._pybullet_client.restoreState(self._saved_state)
|
| 309 |
+
|
| 310 |
+
rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
|
| 311 |
+
translation = np.array([0.3, -0.4, block_pushing.EFFECTOR_HEIGHT])
|
| 312 |
+
starting_pose = Pose3d(rotation=rotation, translation=translation)
|
| 313 |
+
self._set_robot_target_effector_pose(starting_pose)
|
| 314 |
+
self._reset_object_poses(workspace_center_x, workspace_center_y)
|
| 315 |
+
|
| 316 |
+
# else:
|
| 317 |
+
self._target_poses = [
|
| 318 |
+
self._get_target_pose(idx) for idx in self._target_ids
|
| 319 |
+
]
|
| 320 |
+
|
| 321 |
+
if reset_poses:
|
| 322 |
+
self.step_simulation_to_stabilize()
|
| 323 |
+
|
| 324 |
+
state = self._compute_state()
|
| 325 |
+
self._previous_state = state
|
| 326 |
+
self._event_manager.reset()
|
| 327 |
+
return state
|
| 328 |
+
|
| 329 |
+
def _get_target_pose(self, idx):
|
| 330 |
+
(
|
| 331 |
+
target_translation,
|
| 332 |
+
target_orientation_quat,
|
| 333 |
+
) = self._pybullet_client.getBasePositionAndOrientation(idx)
|
| 334 |
+
target_rotation = transform.Rotation.from_quat(target_orientation_quat)
|
| 335 |
+
target_translation = np.array(target_translation)
|
| 336 |
+
return Pose3d(rotation=target_rotation, translation=target_translation)
|
| 337 |
+
|
| 338 |
+
def _compute_reach_target(self, state):
|
| 339 |
+
xy_block = state["block_translation"]
|
| 340 |
+
xy_target = state["target_translation"]
|
| 341 |
+
|
| 342 |
+
xy_block_to_target = xy_target - xy_block
|
| 343 |
+
xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
|
| 344 |
+
xy_block_to_target
|
| 345 |
+
)
|
| 346 |
+
self.reach_target_translation = xy_block + -1 * xy_dir_block_to_target * 0.05
|
| 347 |
+
|
| 348 |
+
def _compute_state(self):
|
| 349 |
+
effector_pose = self._robot.forward_kinematics()
|
| 350 |
+
|
| 351 |
+
def _get_block_pose(idx):
|
| 352 |
+
block_position_and_orientation = (
|
| 353 |
+
self._pybullet_client.getBasePositionAndOrientation(
|
| 354 |
+
self._block_ids[idx]
|
| 355 |
+
)
|
| 356 |
+
)
|
| 357 |
+
block_pose = Pose3d(
|
| 358 |
+
rotation=transform.Rotation.from_quat(
|
| 359 |
+
block_position_and_orientation[1]
|
| 360 |
+
),
|
| 361 |
+
translation=block_position_and_orientation[0],
|
| 362 |
+
)
|
| 363 |
+
return block_pose
|
| 364 |
+
|
| 365 |
+
block_poses = [_get_block_pose(i) for i in range(len(self._block_ids))]
|
| 366 |
+
|
| 367 |
+
def _yaw_from_pose(pose):
|
| 368 |
+
return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1] % np.pi])
|
| 369 |
+
|
| 370 |
+
obs = collections.OrderedDict(
|
| 371 |
+
block_translation=block_poses[0].translation[0:2],
|
| 372 |
+
block_orientation=_yaw_from_pose(block_poses[0]),
|
| 373 |
+
block2_translation=block_poses[1].translation[0:2],
|
| 374 |
+
block2_orientation=_yaw_from_pose(block_poses[1]),
|
| 375 |
+
effector_translation=effector_pose.translation[0:2],
|
| 376 |
+
effector_target_translation=self._target_effector_pose.translation[0:2],
|
| 377 |
+
target_translation=self._target_poses[0].translation[0:2],
|
| 378 |
+
target_orientation=_yaw_from_pose(self._target_poses[0]),
|
| 379 |
+
target2_translation=self._target_poses[1].translation[0:2],
|
| 380 |
+
target2_orientation=_yaw_from_pose(self._target_poses[1]),
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
for i in range(2):
|
| 384 |
+
new_distance = np.linalg.norm(
|
| 385 |
+
block_poses[i].translation[0:2]
|
| 386 |
+
) # + np.linalg.norm(_yaw_from_pose(block_poses[i]))
|
| 387 |
+
if self._init_distance[i] == -1:
|
| 388 |
+
self._init_distance[i] = new_distance
|
| 389 |
+
else:
|
| 390 |
+
if self._init_distance[i] != 100:
|
| 391 |
+
if np.abs(new_distance - self._init_distance[i]) > 1e-3:
|
| 392 |
+
logger.info(f"Block {i} moved on step {self._step_num}")
|
| 393 |
+
self._event_manager.reach(step=self._step_num, block_id=i)
|
| 394 |
+
self._init_distance[i] = 100
|
| 395 |
+
|
| 396 |
+
self._step_num += 1
|
| 397 |
+
if self._image_size is not None:
|
| 398 |
+
obs["rgb"] = self._render_camera(self._image_size)
|
| 399 |
+
return obs
|
| 400 |
+
|
| 401 |
+
def step(self, action):
|
| 402 |
+
self._step_robot_and_sim(action)
|
| 403 |
+
|
| 404 |
+
state = self._compute_state()
|
| 405 |
+
done = False
|
| 406 |
+
reward = self._get_reward(state)
|
| 407 |
+
if reward >= 0.5:
|
| 408 |
+
# Terminate the episode if both blocks are close enough to the targets.
|
| 409 |
+
done = True
|
| 410 |
+
|
| 411 |
+
info = self._event_manager.get_info()
|
| 412 |
+
return state, reward, done, info
|
| 413 |
+
|
| 414 |
+
def _step_robot_and_sim(self, action):
|
| 415 |
+
"""Steps the robot and pybullet sim."""
|
| 416 |
+
# Compute target_effector_pose by shifting the effector's pose by the
|
| 417 |
+
# action.
|
| 418 |
+
if self._abs_action:
|
| 419 |
+
target_effector_translation = np.array([action[0], action[1], 0])
|
| 420 |
+
else:
|
| 421 |
+
target_effector_translation = np.array(
|
| 422 |
+
self._target_effector_pose.translation
|
| 423 |
+
) + np.array([action[0], action[1], 0])
|
| 424 |
+
|
| 425 |
+
target_effector_translation[0:2] = np.clip(
|
| 426 |
+
target_effector_translation[0:2],
|
| 427 |
+
self.workspace_bounds[0],
|
| 428 |
+
self.workspace_bounds[1],
|
| 429 |
+
)
|
| 430 |
+
target_effector_translation[-1] = self.effector_height
|
| 431 |
+
target_effector_pose = Pose3d(
|
| 432 |
+
rotation=block_pushing.EFFECTOR_DOWN_ROTATION, translation=target_effector_translation
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
self._set_robot_target_effector_pose(target_effector_pose)
|
| 436 |
+
|
| 437 |
+
# Update sleep time dynamically to stay near real-time.
|
| 438 |
+
frame_sleep_time = 0
|
| 439 |
+
if self._connection_mode == pybullet.SHARED_MEMORY:
|
| 440 |
+
cur_time = time.time()
|
| 441 |
+
if self._last_loop_time is not None:
|
| 442 |
+
# Calculate the total, non-sleeping time from the previous frame, this
|
| 443 |
+
# includes the actual step as well as any compute that happens in the
|
| 444 |
+
# caller thread (model inference, etc).
|
| 445 |
+
compute_time = (
|
| 446 |
+
cur_time
|
| 447 |
+
- self._last_loop_time
|
| 448 |
+
- self._last_loop_frame_sleep_time * self._sim_steps_per_step
|
| 449 |
+
)
|
| 450 |
+
# Use this to calculate the current frame's total sleep time to ensure
|
| 451 |
+
# that env.step runs at policy rate. This is an estimate since the
|
| 452 |
+
# previous frame's compute time may not match the current frame.
|
| 453 |
+
total_sleep_time = max((1 / self._control_frequency) - compute_time, 0)
|
| 454 |
+
# Now spread this out over the inner sim steps. This doesn't change
|
| 455 |
+
# control in any way, but makes the animation appear smooth.
|
| 456 |
+
frame_sleep_time = total_sleep_time / self._sim_steps_per_step
|
| 457 |
+
else:
|
| 458 |
+
# No estimate of the previous frame's compute, assume it is zero.
|
| 459 |
+
frame_sleep_time = 1 / self._step_frequency
|
| 460 |
+
|
| 461 |
+
# Cache end of this loop time, to compute sleep time on next iteration.
|
| 462 |
+
self._last_loop_time = cur_time
|
| 463 |
+
self._last_loop_frame_sleep_time = frame_sleep_time
|
| 464 |
+
|
| 465 |
+
for _ in range(self._sim_steps_per_step):
|
| 466 |
+
if self._connection_mode == pybullet.SHARED_MEMORY:
|
| 467 |
+
block_pushing.sleep_spin(frame_sleep_time)
|
| 468 |
+
self._pybullet_client.stepSimulation()
|
| 469 |
+
|
| 470 |
+
def _get_reward(self, state):
|
| 471 |
+
# Reward is 1. if both blocks are inside targets, but not the same target.
|
| 472 |
+
targets = ["target", "target2"]
|
| 473 |
+
|
| 474 |
+
def _block_target_dist(block, target):
|
| 475 |
+
return np.linalg.norm(
|
| 476 |
+
state["%s_translation" % block] - state["%s_translation" % target]
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
def _closest_target(block):
|
| 480 |
+
# Distances to all targets.
|
| 481 |
+
dists = [_block_target_dist(block, t) for t in targets]
|
| 482 |
+
# Which is closest.
|
| 483 |
+
closest_target = targets[np.argmin(dists)]
|
| 484 |
+
closest_dist = np.min(dists)
|
| 485 |
+
# Is it in the closest target?
|
| 486 |
+
in_target = closest_dist < self.goal_dist_tolerance
|
| 487 |
+
return closest_target, in_target
|
| 488 |
+
|
| 489 |
+
blocks = ["block", "block2"]
|
| 490 |
+
|
| 491 |
+
reward = 0.0
|
| 492 |
+
|
| 493 |
+
for t_i, t in enumerate(targets):
|
| 494 |
+
for b_i, b in enumerate(blocks):
|
| 495 |
+
if self._in_target[t_i][b_i] == -1:
|
| 496 |
+
dist = _block_target_dist(b, t)
|
| 497 |
+
if dist < self.goal_dist_tolerance:
|
| 498 |
+
self._in_target[t_i][b_i] = 0
|
| 499 |
+
logger.info(
|
| 500 |
+
f"Block {b_i} entered target {t_i} on step {self._step_num}"
|
| 501 |
+
)
|
| 502 |
+
self._event_manager.target(step=self._step_num, block_id=b_i, target_id=t_i)
|
| 503 |
+
reward += 0.49
|
| 504 |
+
|
| 505 |
+
b0_closest_target, b0_in_target = _closest_target("block")
|
| 506 |
+
b1_closest_target, b1_in_target = _closest_target("block2")
|
| 507 |
+
# reward = 0.0
|
| 508 |
+
if b0_in_target and b1_in_target and (b0_closest_target != b1_closest_target):
|
| 509 |
+
reward = 0.51
|
| 510 |
+
return reward
|
| 511 |
+
|
| 512 |
+
def _compute_goal_distance(self, state):
|
| 513 |
+
blocks = ["block", "block2"]
|
| 514 |
+
|
| 515 |
+
def _target_block_dist(target, block):
|
| 516 |
+
return np.linalg.norm(
|
| 517 |
+
state["%s_translation" % block] - state["%s_translation" % target]
|
| 518 |
+
)
|
| 519 |
+
|
| 520 |
+
def _closest_block_dist(target):
|
| 521 |
+
dists = [_target_block_dist(target, b) for b in blocks]
|
| 522 |
+
closest_dist = np.min(dists)
|
| 523 |
+
return closest_dist
|
| 524 |
+
|
| 525 |
+
t0_closest_dist = _closest_block_dist("target")
|
| 526 |
+
t1_closest_dist = _closest_block_dist("target2")
|
| 527 |
+
return np.mean([t0_closest_dist, t1_closest_dist])
|
| 528 |
+
|
| 529 |
+
@property
|
| 530 |
+
def succeeded(self):
|
| 531 |
+
state = self._compute_state()
|
| 532 |
+
reward = self._get_reward(state)
|
| 533 |
+
if reward >= 0.5:
|
| 534 |
+
return True
|
| 535 |
+
return False
|
| 536 |
+
|
| 537 |
+
def _create_observation_space(self, image_size):
|
| 538 |
+
pi2 = math.pi * 2
|
| 539 |
+
|
| 540 |
+
obs_dict = collections.OrderedDict(
|
| 541 |
+
block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 542 |
+
block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
|
| 543 |
+
block2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 544 |
+
block2_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
|
| 545 |
+
effector_translation=spaces.Box(
|
| 546 |
+
low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
|
| 547 |
+
high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
|
| 548 |
+
), # x,y
|
| 549 |
+
effector_target_translation=spaces.Box(
|
| 550 |
+
low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
|
| 551 |
+
high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
|
| 552 |
+
), # x,y
|
| 553 |
+
target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 554 |
+
target_orientation=spaces.Box(
|
| 555 |
+
low=-pi2,
|
| 556 |
+
high=pi2,
|
| 557 |
+
shape=(1,),
|
| 558 |
+
), # theta
|
| 559 |
+
target2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
|
| 560 |
+
target2_orientation=spaces.Box(
|
| 561 |
+
low=-pi2,
|
| 562 |
+
high=pi2,
|
| 563 |
+
shape=(1,),
|
| 564 |
+
), # theta
|
| 565 |
+
)
|
| 566 |
+
if image_size is not None:
|
| 567 |
+
obs_dict["rgb"] = spaces.Box(
|
| 568 |
+
low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
|
| 569 |
+
)
|
| 570 |
+
return spaces.Dict(obs_dict)
|
| 571 |
+
|
| 572 |
+
def get_pybullet_state(self):
|
| 573 |
+
"""Save pybullet state of the scene.
|
| 574 |
+
|
| 575 |
+
Returns:
|
| 576 |
+
dict containing 'robots', 'robot_end_effectors', 'targets', 'objects',
|
| 577 |
+
each containing a list of ObjState.
|
| 578 |
+
"""
|
| 579 |
+
state: Dict[str, List[ObjState]] = {}
|
| 580 |
+
|
| 581 |
+
state["robots"] = [
|
| 582 |
+
XarmState.get_bullet_state(
|
| 583 |
+
self._pybullet_client,
|
| 584 |
+
self.robot.xarm,
|
| 585 |
+
target_effector_pose=self._target_effector_pose,
|
| 586 |
+
goal_translation=None,
|
| 587 |
+
)
|
| 588 |
+
]
|
| 589 |
+
|
| 590 |
+
state["robot_end_effectors"] = []
|
| 591 |
+
if self.robot.end_effector:
|
| 592 |
+
state["robot_end_effectors"].append(
|
| 593 |
+
ObjState.get_bullet_state(
|
| 594 |
+
self._pybullet_client, self.robot.end_effector
|
| 595 |
+
)
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
state["targets"] = []
|
| 599 |
+
if self._target_ids:
|
| 600 |
+
for target_id in self._target_ids:
|
| 601 |
+
state["targets"].append(
|
| 602 |
+
ObjState.get_bullet_state(self._pybullet_client, target_id)
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
state["objects"] = []
|
| 606 |
+
for obj_id in self.get_obj_ids():
|
| 607 |
+
state["objects"].append(
|
| 608 |
+
ObjState.get_bullet_state(self._pybullet_client, obj_id)
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
return state
|
| 612 |
+
|
| 613 |
+
def set_pybullet_state(self, state):
|
| 614 |
+
"""Restore pyullet state.
|
| 615 |
+
|
| 616 |
+
WARNING: py_environment wrapper assumes environments aren't reset in their
|
| 617 |
+
constructor and will often reset the environment unintentionally. It is
|
| 618 |
+
always recommended that you call env.reset on the tfagents wrapper before
|
| 619 |
+
playback (replaying pybullet_state).
|
| 620 |
+
|
| 621 |
+
Args:
|
| 622 |
+
state: dict containing 'robots', 'robot_end_effectors', 'targets',
|
| 623 |
+
'objects', each containing a list of ObjState.
|
| 624 |
+
"""
|
| 625 |
+
|
| 626 |
+
assert isinstance(state["robots"][0], XarmState)
|
| 627 |
+
xarm_state: XarmState = state["robots"][0]
|
| 628 |
+
xarm_state.set_bullet_state(self._pybullet_client, self.robot.xarm)
|
| 629 |
+
self._set_robot_target_effector_pose(xarm_state.target_effector_pose)
|
| 630 |
+
|
| 631 |
+
def _set_state_safe(obj_state, obj_id):
|
| 632 |
+
if obj_state is not None:
|
| 633 |
+
assert obj_id is not None, "Cannot set state for missing object."
|
| 634 |
+
obj_state.set_bullet_state(self._pybullet_client, obj_id)
|
| 635 |
+
else:
|
| 636 |
+
assert obj_id is None, f"No state found for obj_id {obj_id}"
|
| 637 |
+
|
| 638 |
+
robot_end_effectors = state["robot_end_effectors"]
|
| 639 |
+
_set_state_safe(
|
| 640 |
+
None if not robot_end_effectors else robot_end_effectors[0],
|
| 641 |
+
self.robot.end_effector,
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
for target_state, target_id in zip(state["targets"], self._target_ids):
|
| 645 |
+
_set_state_safe(target_state, target_id)
|
| 646 |
+
|
| 647 |
+
obj_ids = self.get_obj_ids()
|
| 648 |
+
assert len(state["objects"]) == len(obj_ids), "State length mismatch"
|
| 649 |
+
for obj_state, obj_id in zip(state["objects"], obj_ids):
|
| 650 |
+
_set_state_safe(obj_state, obj_id)
|
| 651 |
+
|
| 652 |
+
self.reset(reset_poses=False)
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
class BlockPushHorizontalMultimodal(BlockPushMultimodal):
|
| 656 |
+
def _reset_object_poses(self, workspace_center_x, workspace_center_y):
|
| 657 |
+
# Reset block poses.
|
| 658 |
+
self._reset_block_poses(workspace_center_y)
|
| 659 |
+
|
| 660 |
+
# Reset target poses.
|
| 661 |
+
self._reset_target_poses(workspace_center_y)
|
| 662 |
+
|
| 663 |
+
def _reset_block_poses(self, workspace_center_y):
|
| 664 |
+
"""Resets block poses."""
|
| 665 |
+
|
| 666 |
+
# Helper for choosing random block position.
|
| 667 |
+
def _reset_block_pose(idx, add=0.0, avoid=None):
|
| 668 |
+
def _get_random_translation():
|
| 669 |
+
block_x = 0.35 + 0.5 * self._rng.uniform(
|
| 670 |
+
low=-RANDOM_X_SHIFT, high=RANDOM_X_SHIFT
|
| 671 |
+
)
|
| 672 |
+
block_y = (
|
| 673 |
+
workspace_center_y
|
| 674 |
+
+ add
|
| 675 |
+
+ 0.5 * self._rng.uniform(low=-RANDOM_Y_SHIFT, high=RANDOM_Y_SHIFT)
|
| 676 |
+
)
|
| 677 |
+
block_translation = np.array([block_x, block_y, 0])
|
| 678 |
+
return block_translation
|
| 679 |
+
|
| 680 |
+
if avoid is None:
|
| 681 |
+
block_translation = _get_random_translation()
|
| 682 |
+
else:
|
| 683 |
+
# Reject targets too close to `avoid`.
|
| 684 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 685 |
+
block_translation = _get_random_translation()
|
| 686 |
+
dist = np.linalg.norm(block_translation[0] - avoid[0])
|
| 687 |
+
# print('block inner try_idx %d, dist %.3f' % (try_idx, dist))
|
| 688 |
+
if dist > MIN_BLOCK_DIST:
|
| 689 |
+
break
|
| 690 |
+
block_sampled_angle = self._rng.uniform(math.pi)
|
| 691 |
+
block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
|
| 692 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 693 |
+
self._block_ids[idx],
|
| 694 |
+
block_translation.tolist(),
|
| 695 |
+
block_rotation.as_quat().tolist(),
|
| 696 |
+
)
|
| 697 |
+
return block_translation
|
| 698 |
+
|
| 699 |
+
# Reject targets too close to `avoid`.
|
| 700 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 701 |
+
# Reset first block.
|
| 702 |
+
add = 0.2 * self._rng.choice([-1, 1])
|
| 703 |
+
b0_translation = _reset_block_pose(0, add=add)
|
| 704 |
+
# Reset second block away from first block.
|
| 705 |
+
b1_translation = _reset_block_pose(1, add=-add, avoid=b0_translation)
|
| 706 |
+
dist = np.linalg.norm(b0_translation[0] - b1_translation[0])
|
| 707 |
+
if dist > MIN_BLOCK_DIST:
|
| 708 |
+
break
|
| 709 |
+
else:
|
| 710 |
+
raise ValueError("could not find matching block")
|
| 711 |
+
assert dist > MIN_BLOCK_DIST
|
| 712 |
+
|
| 713 |
+
def _reset_target_poses(self, workspace_center_y):
|
| 714 |
+
"""Resets target poses."""
|
| 715 |
+
|
| 716 |
+
def _reset_target_pose(idx, add=0.0, avoid=None):
|
| 717 |
+
def _get_random_translation():
|
| 718 |
+
# Choose x,y randomly.
|
| 719 |
+
target_x = 0.5 + self._rng.uniform(
|
| 720 |
+
low=-0.05 * RANDOM_X_SHIFT, high=0.05 * RANDOM_X_SHIFT
|
| 721 |
+
)
|
| 722 |
+
target_y = (
|
| 723 |
+
workspace_center_y
|
| 724 |
+
+ add
|
| 725 |
+
+ self._rng.uniform(
|
| 726 |
+
low=-0.05 * RANDOM_Y_SHIFT, high=0.05 * RANDOM_Y_SHIFT
|
| 727 |
+
)
|
| 728 |
+
)
|
| 729 |
+
target_translation = np.array([target_x, target_y, 0.020])
|
| 730 |
+
return target_translation
|
| 731 |
+
|
| 732 |
+
if avoid is None:
|
| 733 |
+
target_translation = _get_random_translation()
|
| 734 |
+
else:
|
| 735 |
+
# Reject targets too close to `avoid`.
|
| 736 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 737 |
+
target_translation = _get_random_translation()
|
| 738 |
+
dist = np.linalg.norm(target_translation[0] - avoid[0])
|
| 739 |
+
# print('target inner try_idx %d, dist %.3f' % (try_idx, dist))
|
| 740 |
+
if dist > MIN_TARGET_DIST:
|
| 741 |
+
break
|
| 742 |
+
target_sampled_angle = math.pi + self._rng.uniform(
|
| 743 |
+
low=-math.pi / 30, high=math.pi / 30
|
| 744 |
+
)
|
| 745 |
+
target_rotation = transform.Rotation.from_rotvec(
|
| 746 |
+
[0, 0, target_sampled_angle]
|
| 747 |
+
)
|
| 748 |
+
self._pybullet_client.resetBasePositionAndOrientation(
|
| 749 |
+
self._target_ids[idx],
|
| 750 |
+
target_translation.tolist(),
|
| 751 |
+
target_rotation.as_quat().tolist(),
|
| 752 |
+
)
|
| 753 |
+
self._target_poses[idx] = Pose3d(
|
| 754 |
+
rotation=target_rotation, translation=target_translation
|
| 755 |
+
)
|
| 756 |
+
|
| 757 |
+
if self._target_poses is None:
|
| 758 |
+
self._target_poses = [None for _ in range(len(self._target_ids))]
|
| 759 |
+
|
| 760 |
+
for _ in range(NUM_RESET_ATTEMPTS):
|
| 761 |
+
# Choose the first target.
|
| 762 |
+
add = 0.2 * self._rng.choice([-1, 1])
|
| 763 |
+
# Randomly flip the location of the targets.
|
| 764 |
+
_reset_target_pose(0, add=add)
|
| 765 |
+
_reset_target_pose(1, add=-add, avoid=self._target_poses[0].translation)
|
| 766 |
+
dist = np.linalg.norm(
|
| 767 |
+
self._target_poses[0].translation[0]
|
| 768 |
+
- self._target_poses[1].translation[0]
|
| 769 |
+
)
|
| 770 |
+
break
|
| 771 |
+
# if dist > MIN_TARGET_DIST:
|
| 772 |
+
# break
|
| 773 |
+
else:
|
| 774 |
+
raise ValueError("could not find matching target")
|
| 775 |
+
# assert dist > MIN_TARGET_DIST
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
if "BlockPushMultimodal-v0" in registration.registry.env_specs:
|
| 779 |
+
del registration.registry.env_specs["BlockPushMultimodal-v0"]
|
| 780 |
+
|
| 781 |
+
registration.register(
|
| 782 |
+
id="BlockPushMultimodal-v0", entry_point=BlockPushMultimodal, max_episode_steps=350
|
| 783 |
+
)
|
| 784 |
+
|
| 785 |
+
registration.register(
|
| 786 |
+
id="BlockPushMultimodalFlipped-v0",
|
| 787 |
+
entry_point=BlockPushHorizontalMultimodal,
|
| 788 |
+
max_episode_steps=25,
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
registration.register(
|
| 792 |
+
id="SharedBlockPushMultimodal-v0",
|
| 793 |
+
entry_point=BlockPushMultimodal,
|
| 794 |
+
kwargs=dict(shared_memory=True),
|
| 795 |
+
max_episode_steps=350,
|
| 796 |
+
)
|
| 797 |
+
registration.register(
|
| 798 |
+
id="BlockPushMultimodalRgb-v0",
|
| 799 |
+
entry_point=BlockPushMultimodal,
|
| 800 |
+
max_episode_steps=350,
|
| 801 |
+
kwargs=dict(image_size=(block_pushing.IMAGE_HEIGHT, block_pushing.IMAGE_WIDTH)),
|
| 802 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/discontinuous_push_oracle.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Pushes to first target, waits, then pushes to second target."""
|
| 17 |
+
|
| 18 |
+
import cleandiffuser.env.block_pushing.oracles.oriented_push_oracle as oriented_push_oracle_module
|
| 19 |
+
import numpy as np
|
| 20 |
+
from tf_agents.trajectories import policy_step
|
| 21 |
+
from tf_agents.trajectories import time_step as ts
|
| 22 |
+
from tf_agents.typing import types
|
| 23 |
+
|
| 24 |
+
# Only used for debug visualization.
|
| 25 |
+
import pybullet # pylint: disable=unused-import
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class DiscontinuousOrientedPushOracle(oriented_push_oracle_module.OrientedPushOracle):
|
| 29 |
+
"""Pushes to first target, waits, then pushes to second target."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, env, goal_tolerance=0.04, wait=0):
|
| 32 |
+
super(DiscontinuousOrientedPushOracle, self).__init__(env)
|
| 33 |
+
self._countdown = 0
|
| 34 |
+
self._wait = wait
|
| 35 |
+
self._goal_dist_tolerance = goal_tolerance
|
| 36 |
+
|
| 37 |
+
def reset(self):
|
| 38 |
+
self.phase = "move_to_pre_block"
|
| 39 |
+
self._countdown = 0
|
| 40 |
+
|
| 41 |
+
def _action(self, time_step, policy_state):
|
| 42 |
+
if time_step.is_first():
|
| 43 |
+
self.reset()
|
| 44 |
+
# Move to first target first.
|
| 45 |
+
self._current_target = "target"
|
| 46 |
+
self._has_switched = False
|
| 47 |
+
|
| 48 |
+
def _block_target_dist(block, target):
|
| 49 |
+
dist = np.linalg.norm(
|
| 50 |
+
time_step.observation["%s_translation" % block]
|
| 51 |
+
- time_step.observation["%s_translation" % target]
|
| 52 |
+
)
|
| 53 |
+
return dist
|
| 54 |
+
|
| 55 |
+
d1 = _block_target_dist("block", "target")
|
| 56 |
+
if d1 < self._goal_dist_tolerance and not self._has_switched:
|
| 57 |
+
self._countdown = self._wait
|
| 58 |
+
# If first block has been pushed to first target, switch to second block.
|
| 59 |
+
self._has_switched = True
|
| 60 |
+
self._current_target = "target2"
|
| 61 |
+
|
| 62 |
+
xy_delta = self._get_action_for_block_target(
|
| 63 |
+
time_step, block="block", target=self._current_target
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
if self._countdown > 0:
|
| 67 |
+
xy_delta = np.zeros_like(xy_delta)
|
| 68 |
+
self._countdown -= 1
|
| 69 |
+
|
| 70 |
+
return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/multimodal_push_oracle.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Oracle for multimodal pushing task."""
|
| 17 |
+
import cleandiffuser.env.block_pushing.oracles.oriented_push_oracle as oriented_push_oracle_module
|
| 18 |
+
import numpy as np
|
| 19 |
+
from tf_agents.trajectories import policy_step
|
| 20 |
+
from tf_agents.trajectories import time_step as ts
|
| 21 |
+
from tf_agents.typing import types
|
| 22 |
+
|
| 23 |
+
# Only used for debug visualization.
|
| 24 |
+
import pybullet # pylint: disable=unused-import
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class MultimodalOrientedPushOracle(oriented_push_oracle_module.OrientedPushOracle):
|
| 28 |
+
"""Oracle for multimodal pushing task."""
|
| 29 |
+
|
| 30 |
+
def __init__(self, env, goal_dist_tolerance=0.04, action_noise_std=0.0):
|
| 31 |
+
super(MultimodalOrientedPushOracle, self).__init__(env)
|
| 32 |
+
self._goal_dist_tolerance = goal_dist_tolerance
|
| 33 |
+
self._action_noise_std = action_noise_std
|
| 34 |
+
|
| 35 |
+
def reset(self):
|
| 36 |
+
self.origin = None
|
| 37 |
+
self.first_preblock = None
|
| 38 |
+
self.phase = "move_to_pre_block"
|
| 39 |
+
|
| 40 |
+
def _get_move_to_preblock(self, xy_pre_block, xy_ee):
|
| 41 |
+
max_step_velocity = 0.3
|
| 42 |
+
# Go 5 cm away from the block, on the line between the block and target.
|
| 43 |
+
xy_delta_to_preblock = xy_pre_block - xy_ee
|
| 44 |
+
diff = np.linalg.norm(xy_delta_to_preblock)
|
| 45 |
+
if diff < 0.001:
|
| 46 |
+
self.phase = "move_to_block"
|
| 47 |
+
if self.first_preblock is None:
|
| 48 |
+
self.first_preblock = np.copy(xy_pre_block)
|
| 49 |
+
xy_delta = xy_delta_to_preblock
|
| 50 |
+
return xy_delta, max_step_velocity
|
| 51 |
+
|
| 52 |
+
def _get_action_for_block_target(self, time_step, block="block", target="target"):
|
| 53 |
+
# Specifying this as velocity makes it independent of control frequency.
|
| 54 |
+
max_step_velocity = 0.35
|
| 55 |
+
|
| 56 |
+
info = self._get_action_info(time_step, block, target)
|
| 57 |
+
|
| 58 |
+
if self.origin is None:
|
| 59 |
+
self.origin = np.copy(info.xy_ee)
|
| 60 |
+
|
| 61 |
+
if self.phase == "move_to_pre_block":
|
| 62 |
+
xy_delta, max_step_velocity = self._get_move_to_preblock(
|
| 63 |
+
info.xy_pre_block, info.xy_ee
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
if self.phase == "return_to_first_preblock":
|
| 67 |
+
max_step_velocity = 0.3
|
| 68 |
+
if self.first_preblock is None:
|
| 69 |
+
self.first_preblock = self.origin
|
| 70 |
+
# Return to the first preblock.
|
| 71 |
+
xy_delta_to_origin = self.first_preblock - info.xy_ee
|
| 72 |
+
diff = np.linalg.norm(xy_delta_to_origin)
|
| 73 |
+
if diff < 0.001:
|
| 74 |
+
self.phase = "return_to_origin"
|
| 75 |
+
xy_delta = xy_delta_to_origin
|
| 76 |
+
|
| 77 |
+
if self.phase == "return_to_origin":
|
| 78 |
+
max_step_velocity = 0.3
|
| 79 |
+
# Go 5 cm away from the block, on the line between the block and target.
|
| 80 |
+
xy_delta_to_origin = self.origin - info.xy_ee
|
| 81 |
+
diff = np.linalg.norm(xy_delta_to_origin)
|
| 82 |
+
if diff < 0.001:
|
| 83 |
+
self.phase = "move_to_pre_block"
|
| 84 |
+
xy_delta = xy_delta_to_origin
|
| 85 |
+
|
| 86 |
+
if self.phase == "move_to_block":
|
| 87 |
+
xy_delta = self._get_move_to_block(
|
| 88 |
+
info.xy_delta_to_nexttoblock,
|
| 89 |
+
info.theta_threshold_to_orient,
|
| 90 |
+
info.theta_error,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
if self.phase == "push_block":
|
| 94 |
+
xy_delta = self._get_push_block(
|
| 95 |
+
info.theta_error,
|
| 96 |
+
info.theta_threshold_to_orient,
|
| 97 |
+
info.xy_delta_to_touchingblock,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
orient_circle_diameter = 0.025
|
| 101 |
+
|
| 102 |
+
if self.phase == "orient_block_left" or self.phase == "orient_block_right":
|
| 103 |
+
max_step_velocity = 0.15
|
| 104 |
+
|
| 105 |
+
if self.phase == "orient_block_left":
|
| 106 |
+
xy_delta = self._get_orient_block_left(
|
| 107 |
+
info.xy_dir_block_to_ee,
|
| 108 |
+
orient_circle_diameter,
|
| 109 |
+
info.xy_block,
|
| 110 |
+
info.xy_ee,
|
| 111 |
+
info.theta_error,
|
| 112 |
+
info.theta_threshold_flat_enough,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
if self.phase == "orient_block_right":
|
| 116 |
+
xy_delta = self._get_orient_block_right(
|
| 117 |
+
info.xy_dir_block_to_ee,
|
| 118 |
+
orient_circle_diameter,
|
| 119 |
+
info.xy_block,
|
| 120 |
+
info.xy_ee,
|
| 121 |
+
info.theta_error,
|
| 122 |
+
info.theta_threshold_flat_enough,
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
if self._action_noise_std != 0.0:
|
| 126 |
+
xy_delta += self._np_random_state.randn(2) * self._action_noise_std
|
| 127 |
+
|
| 128 |
+
max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
|
| 129 |
+
length = np.linalg.norm(xy_delta)
|
| 130 |
+
if length > max_step_distance:
|
| 131 |
+
xy_direction = xy_delta / length
|
| 132 |
+
xy_delta = xy_direction * max_step_distance
|
| 133 |
+
return xy_delta
|
| 134 |
+
|
| 135 |
+
def _choose_goal_order(self):
|
| 136 |
+
"""Chooses block->target order for multimodal pushing."""
|
| 137 |
+
# Define all possible ((first_block, first_target),
|
| 138 |
+
# (second_block, second_target)).
|
| 139 |
+
possible_orders = [
|
| 140 |
+
(("block", "target"), ("block2", "target2")),
|
| 141 |
+
(("block", "target2"), ("block2", "target")),
|
| 142 |
+
(("block2", "target"), ("block", "target2")),
|
| 143 |
+
(("block2", "target2"), ("block", "target")),
|
| 144 |
+
]
|
| 145 |
+
# import pdb; pdb.set_trace()
|
| 146 |
+
# result = random.choice(possible_orders)
|
| 147 |
+
result = possible_orders[self._env._rng.choice(len(possible_orders))]
|
| 148 |
+
return result
|
| 149 |
+
|
| 150 |
+
def _action(self, time_step, policy_state):
|
| 151 |
+
if time_step.is_first():
|
| 152 |
+
self.reset()
|
| 153 |
+
(
|
| 154 |
+
(self._first_block, self._first_target),
|
| 155 |
+
(self._second_block, self._second_target),
|
| 156 |
+
) = self._choose_goal_order()
|
| 157 |
+
self._current_block, self._current_target = (
|
| 158 |
+
self._first_block,
|
| 159 |
+
self._first_target,
|
| 160 |
+
)
|
| 161 |
+
self._has_switched = False
|
| 162 |
+
|
| 163 |
+
def _block_target_dist(block, target):
|
| 164 |
+
dist = np.linalg.norm(
|
| 165 |
+
time_step.observation["%s_translation" % block]
|
| 166 |
+
- time_step.observation["%s_translation" % target]
|
| 167 |
+
)
|
| 168 |
+
return dist
|
| 169 |
+
|
| 170 |
+
if (
|
| 171 |
+
_block_target_dist(self._first_block, self._first_target)
|
| 172 |
+
< self._goal_dist_tolerance
|
| 173 |
+
and not self._has_switched
|
| 174 |
+
):
|
| 175 |
+
# If first block has been pushed to first target, switch to second block.
|
| 176 |
+
self._current_block, self._current_target = (
|
| 177 |
+
self._second_block,
|
| 178 |
+
self._second_target,
|
| 179 |
+
)
|
| 180 |
+
self._has_switched = True
|
| 181 |
+
self.phase = "return_to_first_preblock"
|
| 182 |
+
|
| 183 |
+
xy_delta = self._get_action_for_block_target(
|
| 184 |
+
time_step, block=self._current_block, target=self._current_target
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/oriented_push_oracle.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Oracle for pushing task which orients the block then pushes it."""
|
| 17 |
+
|
| 18 |
+
import cleandiffuser.env.block_pushing.oracles.pushing_info as pushing_info_module
|
| 19 |
+
import numpy as np
|
| 20 |
+
from tf_agents.policies import py_policy
|
| 21 |
+
from tf_agents.trajectories import policy_step
|
| 22 |
+
from tf_agents.trajectories import time_step as ts
|
| 23 |
+
from tf_agents.typing import types
|
| 24 |
+
|
| 25 |
+
# Only used for debug visualization.
|
| 26 |
+
import pybullet # pylint: disable=unused-import
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class OrientedPushOracle(py_policy.PyPolicy):
|
| 30 |
+
"""Oracle for pushing task which orients the block then pushes it."""
|
| 31 |
+
|
| 32 |
+
def __init__(self, env, action_noise_std=0.0):
|
| 33 |
+
super(OrientedPushOracle, self).__init__(
|
| 34 |
+
env.time_step_spec(), env.action_spec()
|
| 35 |
+
)
|
| 36 |
+
self._env = env
|
| 37 |
+
self._np_random_state = np.random.RandomState(0)
|
| 38 |
+
self.phase = "move_to_pre_block"
|
| 39 |
+
self._action_noise_std = action_noise_std
|
| 40 |
+
|
| 41 |
+
def reset(self):
|
| 42 |
+
self.phase = "move_to_pre_block"
|
| 43 |
+
|
| 44 |
+
def get_theta_from_vector(self, vector):
|
| 45 |
+
return np.arctan2(vector[1], vector[0])
|
| 46 |
+
|
| 47 |
+
def theta_to_rotation2d(self, theta):
|
| 48 |
+
r = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
|
| 49 |
+
return r
|
| 50 |
+
|
| 51 |
+
def rotate(self, theta, xy_dir_block_to_ee):
|
| 52 |
+
rot_2d = self.theta_to_rotation2d(theta)
|
| 53 |
+
return rot_2d @ xy_dir_block_to_ee
|
| 54 |
+
|
| 55 |
+
def _get_action_info(self, time_step, block, target):
|
| 56 |
+
xy_block = time_step.observation["%s_translation" % block][:2]
|
| 57 |
+
theta_block = time_step.observation["%s_orientation" % block]
|
| 58 |
+
xy_target = time_step.observation["%s_translation" % target][:2]
|
| 59 |
+
xy_ee = time_step.observation["effector_target_translation"][:2]
|
| 60 |
+
|
| 61 |
+
xy_block_to_target = xy_target - xy_block
|
| 62 |
+
xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
|
| 63 |
+
xy_block_to_target
|
| 64 |
+
)
|
| 65 |
+
theta_to_target = self.get_theta_from_vector(xy_dir_block_to_target)
|
| 66 |
+
|
| 67 |
+
theta_error = theta_to_target - theta_block
|
| 68 |
+
# Block has 4-way symmetry.
|
| 69 |
+
while theta_error > np.pi / 4:
|
| 70 |
+
theta_error -= np.pi / 2.0
|
| 71 |
+
while theta_error < -np.pi / 4:
|
| 72 |
+
theta_error += np.pi / 2.0
|
| 73 |
+
|
| 74 |
+
xy_pre_block = xy_block + -xy_dir_block_to_target * 0.05
|
| 75 |
+
xy_nexttoblock = xy_block + -xy_dir_block_to_target * 0.03
|
| 76 |
+
xy_touchingblock = xy_block + -xy_dir_block_to_target * 0.01
|
| 77 |
+
xy_delta_to_nexttoblock = xy_nexttoblock - xy_ee
|
| 78 |
+
xy_delta_to_touchingblock = xy_touchingblock - xy_ee
|
| 79 |
+
|
| 80 |
+
xy_block_to_ee = xy_ee - xy_block
|
| 81 |
+
xy_dir_block_to_ee = xy_block_to_ee / np.linalg.norm(xy_block_to_ee)
|
| 82 |
+
|
| 83 |
+
theta_threshold_to_orient = 0.2
|
| 84 |
+
theta_threshold_flat_enough = 0.03
|
| 85 |
+
return pushing_info_module.PushingInfo(
|
| 86 |
+
xy_block=xy_block,
|
| 87 |
+
xy_ee=xy_ee,
|
| 88 |
+
xy_pre_block=xy_pre_block,
|
| 89 |
+
xy_delta_to_nexttoblock=xy_delta_to_nexttoblock,
|
| 90 |
+
xy_delta_to_touchingblock=xy_delta_to_touchingblock,
|
| 91 |
+
xy_dir_block_to_ee=xy_dir_block_to_ee,
|
| 92 |
+
theta_threshold_to_orient=theta_threshold_to_orient,
|
| 93 |
+
theta_threshold_flat_enough=theta_threshold_flat_enough,
|
| 94 |
+
theta_error=theta_error,
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
def _get_move_to_preblock(self, xy_pre_block, xy_ee):
|
| 98 |
+
max_step_velocity = 0.3
|
| 99 |
+
# Go 5 cm away from the block, on the line between the block and target.
|
| 100 |
+
xy_delta_to_preblock = xy_pre_block - xy_ee
|
| 101 |
+
diff = np.linalg.norm(xy_delta_to_preblock)
|
| 102 |
+
if diff < 0.001:
|
| 103 |
+
self.phase = "move_to_block"
|
| 104 |
+
xy_delta = xy_delta_to_preblock
|
| 105 |
+
return xy_delta, max_step_velocity
|
| 106 |
+
|
| 107 |
+
def _get_move_to_block(
|
| 108 |
+
self, xy_delta_to_nexttoblock, theta_threshold_to_orient, theta_error
|
| 109 |
+
):
|
| 110 |
+
diff = np.linalg.norm(xy_delta_to_nexttoblock)
|
| 111 |
+
if diff < 0.001:
|
| 112 |
+
self.phase = "push_block"
|
| 113 |
+
# If need to re-oorient, then re-orient.
|
| 114 |
+
if theta_error > theta_threshold_to_orient:
|
| 115 |
+
self.phase = "orient_block_left"
|
| 116 |
+
if theta_error < -theta_threshold_to_orient:
|
| 117 |
+
self.phase = "orient_block_right"
|
| 118 |
+
# Otherwise, push into the block.
|
| 119 |
+
xy_delta = xy_delta_to_nexttoblock
|
| 120 |
+
return xy_delta
|
| 121 |
+
|
| 122 |
+
def _get_push_block(
|
| 123 |
+
self, theta_error, theta_threshold_to_orient, xy_delta_to_touchingblock
|
| 124 |
+
):
|
| 125 |
+
# If need to reorient, go back to move_to_pre_block, move_to_block first.
|
| 126 |
+
if theta_error > theta_threshold_to_orient:
|
| 127 |
+
self.phase = "move_to_pre_block"
|
| 128 |
+
if theta_error < -theta_threshold_to_orient:
|
| 129 |
+
self.phase = "move_to_pre_block"
|
| 130 |
+
xy_delta = xy_delta_to_touchingblock
|
| 131 |
+
return xy_delta
|
| 132 |
+
|
| 133 |
+
def _get_orient_block_left(
|
| 134 |
+
self,
|
| 135 |
+
xy_dir_block_to_ee,
|
| 136 |
+
orient_circle_diameter,
|
| 137 |
+
xy_block,
|
| 138 |
+
xy_ee,
|
| 139 |
+
theta_error,
|
| 140 |
+
theta_threshold_flat_enough,
|
| 141 |
+
):
|
| 142 |
+
xy_dir_block_to_ee = self.rotate(0.2, xy_dir_block_to_ee)
|
| 143 |
+
xy_block_to_ee = xy_dir_block_to_ee * orient_circle_diameter
|
| 144 |
+
xy_push_left_spot = xy_block + xy_block_to_ee
|
| 145 |
+
xy_delta = xy_push_left_spot - xy_ee
|
| 146 |
+
if theta_error < theta_threshold_flat_enough:
|
| 147 |
+
self.phase = "move_to_pre_block"
|
| 148 |
+
return xy_delta
|
| 149 |
+
|
| 150 |
+
def _get_orient_block_right(
|
| 151 |
+
self,
|
| 152 |
+
xy_dir_block_to_ee,
|
| 153 |
+
orient_circle_diameter,
|
| 154 |
+
xy_block,
|
| 155 |
+
xy_ee,
|
| 156 |
+
theta_error,
|
| 157 |
+
theta_threshold_flat_enough,
|
| 158 |
+
):
|
| 159 |
+
xy_dir_block_to_ee = self.rotate(-0.2, xy_dir_block_to_ee)
|
| 160 |
+
xy_block_to_ee = xy_dir_block_to_ee * orient_circle_diameter
|
| 161 |
+
xy_push_left_spot = xy_block + xy_block_to_ee
|
| 162 |
+
xy_delta = xy_push_left_spot - xy_ee
|
| 163 |
+
if theta_error > -theta_threshold_flat_enough:
|
| 164 |
+
self.phase = "move_to_pre_block"
|
| 165 |
+
return xy_delta
|
| 166 |
+
|
| 167 |
+
def _get_action_for_block_target(self, time_step, block="block", target="target"):
|
| 168 |
+
# Specifying this as velocity makes it independent of control frequency.
|
| 169 |
+
max_step_velocity = 0.35
|
| 170 |
+
info = self._get_action_info(time_step, block, target)
|
| 171 |
+
|
| 172 |
+
if self.phase == "move_to_pre_block":
|
| 173 |
+
xy_delta, max_step_velocity = self._get_move_to_preblock(
|
| 174 |
+
info.xy_pre_block, info.xy_ee
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
if self.phase == "move_to_block":
|
| 178 |
+
xy_delta = self._get_move_to_block(
|
| 179 |
+
info.xy_delta_to_nexttoblock,
|
| 180 |
+
info.theta_threshold_to_orient,
|
| 181 |
+
info.theta_error,
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
if self.phase == "push_block":
|
| 185 |
+
xy_delta = self._get_push_block(
|
| 186 |
+
info.theta_error,
|
| 187 |
+
info.theta_threshold_to_orient,
|
| 188 |
+
info.xy_delta_to_touchingblock,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
orient_circle_diameter = 0.025
|
| 192 |
+
|
| 193 |
+
if self.phase == "orient_block_left" or self.phase == "orient_block_right":
|
| 194 |
+
max_step_velocity = 0.15
|
| 195 |
+
|
| 196 |
+
if self.phase == "orient_block_left":
|
| 197 |
+
xy_delta = self._get_orient_block_left(
|
| 198 |
+
info.xy_dir_block_to_ee,
|
| 199 |
+
orient_circle_diameter,
|
| 200 |
+
info.xy_block,
|
| 201 |
+
info.xy_ee,
|
| 202 |
+
info.theta_error,
|
| 203 |
+
info.theta_threshold_flat_enough,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
if self.phase == "orient_block_right":
|
| 207 |
+
xy_delta = self._get_orient_block_right(
|
| 208 |
+
info.xy_dir_block_to_ee,
|
| 209 |
+
orient_circle_diameter,
|
| 210 |
+
info.xy_block,
|
| 211 |
+
info.xy_ee,
|
| 212 |
+
info.theta_error,
|
| 213 |
+
info.theta_threshold_flat_enough,
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
if self._action_noise_std != 0.0:
|
| 217 |
+
xy_delta += self._np_random_state.randn(2) * self._action_noise_std
|
| 218 |
+
|
| 219 |
+
max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
|
| 220 |
+
length = np.linalg.norm(xy_delta)
|
| 221 |
+
if length > max_step_distance:
|
| 222 |
+
xy_direction = xy_delta / length
|
| 223 |
+
xy_delta = xy_direction * max_step_distance
|
| 224 |
+
return xy_delta
|
| 225 |
+
|
| 226 |
+
def _action(self, time_step, policy_state):
|
| 227 |
+
if time_step.is_first():
|
| 228 |
+
self.reset()
|
| 229 |
+
xy_delta = self._get_action_for_block_target(
|
| 230 |
+
time_step, block="block", target="target"
|
| 231 |
+
)
|
| 232 |
+
return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class OrientedPushNormalizedOracle(py_policy.PyPolicy):
|
| 236 |
+
"""Oracle for pushing task which orients the block then pushes it."""
|
| 237 |
+
|
| 238 |
+
def __init__(self, env):
|
| 239 |
+
super(OrientedPushNormalizedOracle, self).__init__(
|
| 240 |
+
env.time_step_spec(), env.action_spec()
|
| 241 |
+
)
|
| 242 |
+
self._oracle = OrientedPushOracle(env)
|
| 243 |
+
self._env = env
|
| 244 |
+
|
| 245 |
+
def reset(self):
|
| 246 |
+
self._oracle.reset()
|
| 247 |
+
|
| 248 |
+
def _action(self, time_step, policy_state):
|
| 249 |
+
time_step = time_step._asdict()
|
| 250 |
+
time_step["observation"] = self._env.calc_unnormalized_state(
|
| 251 |
+
time_step["observation"]
|
| 252 |
+
)
|
| 253 |
+
step = self._oracle._action(
|
| 254 |
+
ts.TimeStep(**time_step), policy_state
|
| 255 |
+
) # pylint: disable=protected-access
|
| 256 |
+
return policy_step.PolicyStep(
|
| 257 |
+
action=self._env.calc_normalized_action(step.action)
|
| 258 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/pushing_info.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Dataclass holding info needed for pushing oracles."""
|
| 17 |
+
import dataclasses
|
| 18 |
+
from typing import Any
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclasses.dataclass
|
| 22 |
+
class PushingInfo:
|
| 23 |
+
"""Holds onto info necessary for pushing state machine."""
|
| 24 |
+
|
| 25 |
+
xy_block: Any = None
|
| 26 |
+
xy_ee: Any = None
|
| 27 |
+
xy_pre_block: Any = None
|
| 28 |
+
xy_delta_to_nexttoblock: Any = None
|
| 29 |
+
xy_delta_to_touchingblock: Any = None
|
| 30 |
+
xy_dir_block_to_ee: Any = None
|
| 31 |
+
theta_threshold_to_orient: Any = None
|
| 32 |
+
theta_threshold_flat_enough: Any = None
|
| 33 |
+
theta_error: Any = None
|
| 34 |
+
obstacle_poses: Any = None
|
| 35 |
+
distance_to_target: Any = None
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/reach_oracle.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Reach oracle."""
|
| 17 |
+
import numpy as np
|
| 18 |
+
from tf_agents.policies import py_policy
|
| 19 |
+
from tf_agents.trajectories import policy_step
|
| 20 |
+
from tf_agents.trajectories import time_step as ts
|
| 21 |
+
from tf_agents.typing import types
|
| 22 |
+
|
| 23 |
+
# Only used for debug visualization.
|
| 24 |
+
import pybullet # pylint: disable=unused-import
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ReachOracle(py_policy.PyPolicy):
|
| 28 |
+
"""Oracle for moving to a specific spot relative to the block and target."""
|
| 29 |
+
|
| 30 |
+
def __init__(self, env, block_pushing_oracles_action_std=0.0):
|
| 31 |
+
super(ReachOracle, self).__init__(env.time_step_spec(), env.action_spec())
|
| 32 |
+
self._env = env
|
| 33 |
+
self._np_random_state = np.random.RandomState(0)
|
| 34 |
+
self._block_pushing_oracles_action_std = block_pushing_oracles_action_std
|
| 35 |
+
|
| 36 |
+
def _action(self, time_step, policy_state):
|
| 37 |
+
|
| 38 |
+
# Specifying this as velocity makes it independent of control frequency.
|
| 39 |
+
max_step_velocity = 0.2
|
| 40 |
+
|
| 41 |
+
xy_ee = time_step.observation["effector_target_translation"]
|
| 42 |
+
|
| 43 |
+
# This should be observable from block and target translation,
|
| 44 |
+
# but re-using the computation from the env so that it's only done once, and
|
| 45 |
+
# used for reward / completion computation.
|
| 46 |
+
xy_pre_block = self._env.reach_target_translation
|
| 47 |
+
|
| 48 |
+
xy_delta = xy_pre_block - xy_ee
|
| 49 |
+
|
| 50 |
+
if self._block_pushing_oracles_action_std != 0.0:
|
| 51 |
+
xy_delta += (
|
| 52 |
+
self._np_random_state.randn(2) * self._block_pushing_oracles_action_std
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
|
| 56 |
+
length = np.linalg.norm(xy_delta)
|
| 57 |
+
if length > max_step_distance:
|
| 58 |
+
xy_direction = xy_delta / length
|
| 59 |
+
xy_delta = xy_direction * max_step_distance
|
| 60 |
+
|
| 61 |
+
return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/pose3d.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""A simple 6DOF pose container.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import dataclasses
|
| 20 |
+
import numpy as np
|
| 21 |
+
from scipy.spatial import transform
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class NoCopyAsDict(object):
|
| 25 |
+
"""Base class for dataclasses. Avoids a copy in the asdict() call."""
|
| 26 |
+
|
| 27 |
+
def asdict(self):
|
| 28 |
+
"""Replacement for dataclasses.asdict.
|
| 29 |
+
|
| 30 |
+
TF Dataset does not handle dataclasses.asdict, which uses copy.deepcopy when
|
| 31 |
+
setting values in the output dict. This causes issues with tf.Dataset.
|
| 32 |
+
Instead, shallow copy contents.
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
dict containing contents of dataclass.
|
| 36 |
+
"""
|
| 37 |
+
return {k.name: getattr(self, k.name) for k in dataclasses.fields(self)}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclasses.dataclass
|
| 41 |
+
class Pose3d(NoCopyAsDict):
|
| 42 |
+
"""Simple container for translation and rotation."""
|
| 43 |
+
|
| 44 |
+
rotation: transform.Rotation
|
| 45 |
+
translation: np.ndarray
|
| 46 |
+
|
| 47 |
+
@property
|
| 48 |
+
def vec7(self):
|
| 49 |
+
return np.concatenate([self.translation, self.rotation.as_quat()])
|
| 50 |
+
|
| 51 |
+
def serialize(self):
|
| 52 |
+
return {
|
| 53 |
+
"rotation": self.rotation.as_quat().tolist(),
|
| 54 |
+
"translation": self.translation.tolist(),
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
@staticmethod
|
| 58 |
+
def deserialize(data):
|
| 59 |
+
return Pose3d(
|
| 60 |
+
rotation=transform.Rotation.from_quat(data["rotation"]),
|
| 61 |
+
translation=np.array(data["translation"]),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
def __eq__(self, other):
|
| 65 |
+
return np.array_equal(
|
| 66 |
+
self.rotation.as_quat(), other.rotation.as_quat()
|
| 67 |
+
) and np.array_equal(self.translation, other.translation)
|
| 68 |
+
|
| 69 |
+
def __ne__(self, other):
|
| 70 |
+
return not self.__eq__(other)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/utils_pybullet.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""Assortment of utilities to interact with bullet within g3."""
|
| 17 |
+
import dataclasses
|
| 18 |
+
import datetime
|
| 19 |
+
import getpass
|
| 20 |
+
import gzip
|
| 21 |
+
import json
|
| 22 |
+
import os
|
| 23 |
+
import time
|
| 24 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 25 |
+
|
| 26 |
+
from absl import logging
|
| 27 |
+
from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
|
| 28 |
+
import numpy as np
|
| 29 |
+
from scipy.spatial import transform
|
| 30 |
+
import six
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
import pybullet
|
| 34 |
+
import pybullet_data
|
| 35 |
+
import pybullet_utils.bullet_client as bullet_client
|
| 36 |
+
|
| 37 |
+
Vec3 = Tuple[float, float, float]
|
| 38 |
+
Vec4 = Tuple[float, float, float, float]
|
| 39 |
+
PYBULLET_STATE_VERSION = 2 # Basic versioning of serialized pybullet state.
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# Note about rotation_to_matrix and matrix_to_rotation below:
|
| 43 |
+
# The abstractions below allow us to use older versions of scipy.
|
| 44 |
+
def rotation_to_matrix(rotation):
|
| 45 |
+
if hasattr(rotation, "as_dcm"):
|
| 46 |
+
return rotation.as_dcm()
|
| 47 |
+
else:
|
| 48 |
+
assert hasattr(rotation, "as_matrix")
|
| 49 |
+
return rotation.as_matrix()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def matrix_to_rotation(matrix):
|
| 53 |
+
if hasattr(transform.Rotation, "from_dcm"):
|
| 54 |
+
return transform.Rotation.from_dcm(matrix)
|
| 55 |
+
else:
|
| 56 |
+
assert hasattr(transform.Rotation, "from_matrix")
|
| 57 |
+
return transform.Rotation.from_matrix(matrix)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def load_urdf(pybullet_client, file_path, *args, **kwargs):
|
| 61 |
+
"""Loads the given URDF filepath."""
|
| 62 |
+
|
| 63 |
+
# Handles most general file open case.
|
| 64 |
+
try:
|
| 65 |
+
if os.path.exists(file_path):
|
| 66 |
+
return pybullet_client.loadURDF(file_path, *args, **kwargs)
|
| 67 |
+
except pybullet_client.error:
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
import pathlib
|
| 72 |
+
asset_path = str(pathlib.Path(__file__).parent.parent.joinpath('assets'))
|
| 73 |
+
if file_path.startswith("third_party/py/envs/assets/"):
|
| 74 |
+
pybullet_client.setAdditionalSearchPath(asset_path)
|
| 75 |
+
file_path = file_path[len("third_party/py/envs/assets/") :]
|
| 76 |
+
if file_path.startswith(
|
| 77 |
+
"third_party/bullet/examples/pybullet/gym/pybullet_data/"
|
| 78 |
+
):
|
| 79 |
+
pybullet_client.setAdditionalSearchPath(pybullet_data.getDataPath())
|
| 80 |
+
file_path = file_path[55:]
|
| 81 |
+
# logging.info("Loading URDF %s", file_path)
|
| 82 |
+
return pybullet_client.loadURDF(file_path, *args, **kwargs)
|
| 83 |
+
except pybullet.error:
|
| 84 |
+
raise FileNotFoundError("Cannot load the URDF file {}".format(file_path))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def add_visual_sphere(client, center=(0, 0, 0), radius=0.1, rgba=(0.5, 0.5, 0.5, 0.5)):
|
| 88 |
+
"""Add a sphere to bullet scene (visual only, no physics).
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
client: pybullet client (or pybullet library handle).
|
| 92 |
+
center: Center of sphere.
|
| 93 |
+
radius: Sphere radius.
|
| 94 |
+
rgba: rgba color of sphere.
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
Unique integer bullet id of constructed object.
|
| 98 |
+
"""
|
| 99 |
+
vis_obj_id = client.createVisualShape(
|
| 100 |
+
client.GEOM_SPHERE, radius=radius, rgbaColor=rgba
|
| 101 |
+
)
|
| 102 |
+
obj_id = client.createMultiBody(
|
| 103 |
+
baseCollisionShapeIndex=-1, baseVisualShapeIndex=vis_obj_id, basePosition=center
|
| 104 |
+
)
|
| 105 |
+
return obj_id
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def pybullet_mat_to_numpy_4x4(pybullet_matrix):
|
| 109 |
+
assert len(pybullet_matrix) == 16, "pybullet matrix should be len 16"
|
| 110 |
+
return np.transpose(np.reshape(np.array(pybullet_matrix, dtype=np.float64), (4, 4)))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def decompose_view_matrix(pybullet_view_matrix):
|
| 114 |
+
"""Decompose view matrix into pos + quat format (assumes mat is rigid!)."""
|
| 115 |
+
# It would be MUCH better to use something from bullet, however pybullet does
|
| 116 |
+
# not expose all of the linear algebra library.
|
| 117 |
+
mat = pybullet_mat_to_numpy_4x4(pybullet_view_matrix)
|
| 118 |
+
|
| 119 |
+
# View matrix is now:
|
| 120 |
+
# | R_11 R_12 R_13 t_1 |
|
| 121 |
+
# | R_21 R_22 R_23 t_2 |
|
| 122 |
+
# | R_31 R_32 R_33 t_3 |
|
| 123 |
+
# | 0 0 0 1 |
|
| 124 |
+
|
| 125 |
+
# R is the inverse eye to target at orientation, and t is R * eye.
|
| 126 |
+
mat_view_to_world = np.linalg.inv(mat)
|
| 127 |
+
|
| 128 |
+
# mat_view_to_world is the view to world transform, therefore the translation
|
| 129 |
+
# component of this matrix is simply the world space position (since mat *
|
| 130 |
+
# (0, 0, 0, 1)) is just copying the right column.
|
| 131 |
+
world_xyz_view = np.copy(mat_view_to_world[0:3, 3])
|
| 132 |
+
|
| 133 |
+
mat_view_to_world[0:3, 3] = 0 # Zero out the position change.
|
| 134 |
+
world_quat_view = matrix_to_rotation(mat_view_to_world).as_quat()
|
| 135 |
+
|
| 136 |
+
return world_xyz_view, world_quat_view
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def world_obj_to_view(world_xyz_obj, world_quat_obj, camera_view, client):
|
| 140 |
+
"""Transform object into view space."""
|
| 141 |
+
world_xyz_view, world_quat_view = decompose_view_matrix(camera_view)
|
| 142 |
+
view_xyz_world, view_quat_world = client.invertTransform(
|
| 143 |
+
world_xyz_view, world_quat_view
|
| 144 |
+
)
|
| 145 |
+
view_xyz_obj, view_quat_obj = client.multiplyTransforms(
|
| 146 |
+
view_xyz_world, view_quat_world, world_xyz_obj, world_quat_obj
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
return view_xyz_obj, view_quat_obj
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def image_xy_to_view_ray(xy, cam_width, cam_height, proj_mat_inv):
|
| 153 |
+
"""Calculate view-space ray from pixel location."""
|
| 154 |
+
# Recall (from http://www.songho.ca/opengl/gl_projectionmatrix.html):
|
| 155 |
+
# xyzw_clip = M_proj * xyzw_eye, and
|
| 156 |
+
# xyz_ndc = xyzw_clip[0:3] / xwzw_clip[3].
|
| 157 |
+
xyz_ndc = np.array(
|
| 158 |
+
[2.0 * xy[0] / cam_width - 1.0, -(2.0 * xy[1] / cam_height - 1.0), 0]
|
| 159 |
+
) # in [-1, 1]
|
| 160 |
+
xyzw_clip = np.concatenate([xyz_ndc, [1]])
|
| 161 |
+
xyzw_eye = proj_mat_inv @ xyzw_clip
|
| 162 |
+
origin = np.zeros(3)
|
| 163 |
+
vec = xyzw_eye[:3] / max(np.linalg.norm(xyzw_eye[:3]), 1e-6)
|
| 164 |
+
return origin, vec
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def view_ray_to_world_ray(origin, vec, view_mat_inv):
|
| 168 |
+
"""Transform view-space ray into world space."""
|
| 169 |
+
origin = view_mat_inv @ np.concatenate([origin, [1]])
|
| 170 |
+
vec = view_mat_inv @ np.concatenate([vec, [0]])
|
| 171 |
+
|
| 172 |
+
return origin[:3], vec[:3]
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def ray_to_plane_test(ray_origin, ray_vec, plane_origin, plane_normal):
|
| 176 |
+
"""Perform a ray-plane intersection test."""
|
| 177 |
+
ln = np.dot(plane_normal, ray_vec)
|
| 178 |
+
if abs(ln) < np.finfo(np.float32).eps:
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
# Solve for the intersection fraction t.
|
| 182 |
+
t = np.dot(plane_normal, plane_origin - ray_origin) / ln
|
| 183 |
+
if t >= 0:
|
| 184 |
+
return ray_origin + ray_vec * t
|
| 185 |
+
else:
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def get_workspace(env):
|
| 190 |
+
(
|
| 191 |
+
workspace_origin,
|
| 192 |
+
workspace_quat,
|
| 193 |
+
) = env.pybullet_client.getBasePositionAndOrientation(env.workspace_uid)
|
| 194 |
+
workspace_normal = rotation_to_matrix(transform.Rotation.from_quat(workspace_quat))[
|
| 195 |
+
2, 0:3
|
| 196 |
+
]
|
| 197 |
+
|
| 198 |
+
return workspace_origin, workspace_normal
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def reset_camera_pose(env, view_type):
|
| 202 |
+
"""Reset camera pose to canonical frame."""
|
| 203 |
+
p = env.pybullet_client
|
| 204 |
+
|
| 205 |
+
if view_type == "POLICY":
|
| 206 |
+
camera_info = p.getDebugVisualizerCamera()
|
| 207 |
+
image_size = (camera_info[0], camera_info[1])
|
| 208 |
+
|
| 209 |
+
viewm, _, front_position, lookat, _ = env.calc_camera_params(image_size)
|
| 210 |
+
|
| 211 |
+
euler = matrix_to_rotation(pybullet_mat_to_numpy_4x4(viewm)[0:3, 0:3]).as_euler(
|
| 212 |
+
"xyz", degrees=False
|
| 213 |
+
)
|
| 214 |
+
pitch = euler[1]
|
| 215 |
+
yaw = -euler[2]
|
| 216 |
+
# The distance is a bit far away (the GL view has higher FOV).
|
| 217 |
+
distance = np.linalg.norm(front_position - lookat) * 0.6
|
| 218 |
+
elif view_type == "TOP_DOWN":
|
| 219 |
+
workspace_origin, _ = get_workspace(env)
|
| 220 |
+
distance = 0.5
|
| 221 |
+
lookat = workspace_origin
|
| 222 |
+
yaw = np.pi / 2
|
| 223 |
+
# Note: pi/2 pitch results in gimble lock and pybullet doesn't support it.
|
| 224 |
+
pitch = -(np.pi / 2 - 1e-5)
|
| 225 |
+
else:
|
| 226 |
+
raise ValueError("unsupported view_type %s" % view_type)
|
| 227 |
+
p.resetDebugVisualizerCamera(
|
| 228 |
+
cameraDistance=distance,
|
| 229 |
+
cameraYaw=360 * yaw / (2.0 * np.pi),
|
| 230 |
+
cameraPitch=360 * pitch / (2.0 * np.pi),
|
| 231 |
+
cameraTargetPosition=lookat,
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def _lists_to_tuple(obj):
|
| 236 |
+
if isinstance(obj, list):
|
| 237 |
+
return tuple([_lists_to_tuple(v) for v in obj])
|
| 238 |
+
else:
|
| 239 |
+
return obj
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
@dataclasses.dataclass
|
| 243 |
+
class ObjState:
|
| 244 |
+
"""A container for storing pybullet object state."""
|
| 245 |
+
|
| 246 |
+
obj_id: int
|
| 247 |
+
|
| 248 |
+
# base_pose: (xyz, quat).
|
| 249 |
+
base_pose: Tuple[Vec3, Vec4]
|
| 250 |
+
# base_vel: (vel, ang_vel).
|
| 251 |
+
base_vel: Tuple[Vec3, Vec3]
|
| 252 |
+
joint_info: Any
|
| 253 |
+
joint_state: Any
|
| 254 |
+
|
| 255 |
+
@staticmethod
|
| 256 |
+
def get_bullet_state(client, obj_id):
|
| 257 |
+
"""Read Pybullet internal state."""
|
| 258 |
+
base_pose = client.getBasePositionAndOrientation(obj_id)
|
| 259 |
+
base_vel = client.getBaseVelocity(obj_id)
|
| 260 |
+
|
| 261 |
+
joint_info = []
|
| 262 |
+
joint_state = []
|
| 263 |
+
for i in range(client.getNumJoints(obj_id)):
|
| 264 |
+
joint_state.append(client.getJointState(obj_id, i))
|
| 265 |
+
joint_info.append(ObjState._get_joint_info(client, obj_id, i))
|
| 266 |
+
|
| 267 |
+
return ObjState(
|
| 268 |
+
obj_id=obj_id,
|
| 269 |
+
base_pose=base_pose,
|
| 270 |
+
base_vel=base_vel,
|
| 271 |
+
joint_info=tuple(joint_info),
|
| 272 |
+
joint_state=tuple(joint_state),
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
@staticmethod
|
| 276 |
+
def _get_joint_info(client, obj_id, joint_index):
|
| 277 |
+
ji = client.getJointInfo(obj_id, joint_index)
|
| 278 |
+
return tuple([v if not isinstance(v, bytes) else v.decode("utf-8") for v in ji])
|
| 279 |
+
|
| 280 |
+
def set_bullet_state(self, client, obj_id):
|
| 281 |
+
"""Hard set the current bullet state."""
|
| 282 |
+
xyz, quat = self.base_pose
|
| 283 |
+
client.resetBasePositionAndOrientation(obj_id, xyz, quat)
|
| 284 |
+
vel, ang_vel = self.base_vel
|
| 285 |
+
client.resetBaseVelocity(obj_id, vel, ang_vel)
|
| 286 |
+
|
| 287 |
+
njoints = client.getNumJoints(obj_id)
|
| 288 |
+
if njoints != len(self.joint_info) or njoints != len(self.joint_state):
|
| 289 |
+
raise ValueError("Incorrect number of joint info state pairs.")
|
| 290 |
+
|
| 291 |
+
for i, (joint_info, joint_state) in enumerate(
|
| 292 |
+
zip(self.joint_info, self.joint_state)
|
| 293 |
+
):
|
| 294 |
+
joint_index = joint_info[0]
|
| 295 |
+
if joint_index != i:
|
| 296 |
+
raise ValueError("Joint index mismatch.")
|
| 297 |
+
|
| 298 |
+
# Check that the current joint we're trying to restore state for has the
|
| 299 |
+
# same info as the state joint.
|
| 300 |
+
cur_joint_info = ObjState._get_joint_info(client, obj_id, joint_index)
|
| 301 |
+
if cur_joint_info != joint_info:
|
| 302 |
+
raise ValueError(
|
| 303 |
+
"joint_info mismatch %s vs %s (expected)"
|
| 304 |
+
% (str(cur_joint_info), str(joint_info))
|
| 305 |
+
)
|
| 306 |
+
joint_position = joint_state[0]
|
| 307 |
+
joint_velocity = joint_state[1]
|
| 308 |
+
client.resetJointState(
|
| 309 |
+
obj_id, i, targetValue=joint_position, targetVelocity=joint_velocity
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
def serialize(self):
|
| 313 |
+
return {
|
| 314 |
+
"obj_id": self.obj_id,
|
| 315 |
+
"base_pose": self.base_pose,
|
| 316 |
+
"base_vel": self.base_vel,
|
| 317 |
+
"joint_info": self.joint_info,
|
| 318 |
+
"joint_state": self.joint_state,
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
@staticmethod
|
| 322 |
+
def deserialize(data):
|
| 323 |
+
return ObjState(
|
| 324 |
+
obj_id=_lists_to_tuple(data["obj_id"]),
|
| 325 |
+
base_pose=_lists_to_tuple(data["base_pose"]),
|
| 326 |
+
base_vel=_lists_to_tuple(data["base_vel"]),
|
| 327 |
+
joint_info=_lists_to_tuple(data["joint_info"]),
|
| 328 |
+
joint_state=_lists_to_tuple(data["joint_state"]),
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
@dataclasses.dataclass
|
| 333 |
+
class XarmState(ObjState):
|
| 334 |
+
"""A container for storing pybullet robot state."""
|
| 335 |
+
|
| 336 |
+
# The set point of the robot's controller.
|
| 337 |
+
target_effector_pose: Pose3d
|
| 338 |
+
goal_translation: Optional[Vec3]
|
| 339 |
+
|
| 340 |
+
@staticmethod
|
| 341 |
+
def get_bullet_state(client, obj_id, target_effector_pose, goal_translation):
|
| 342 |
+
if goal_translation is not None:
|
| 343 |
+
goal_translation = tuple(goal_translation.tolist())
|
| 344 |
+
return XarmState(
|
| 345 |
+
**dataclasses.asdict(ObjState.get_bullet_state(client, obj_id)),
|
| 346 |
+
target_effector_pose=target_effector_pose,
|
| 347 |
+
goal_translation=goal_translation
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
def serialize(self):
|
| 351 |
+
data = ObjState.serialize(self)
|
| 352 |
+
data["target_effector_pose"] = self.target_effector_pose.serialize()
|
| 353 |
+
if self.goal_translation is not None:
|
| 354 |
+
data["goal_translation"] = self.goal_translation
|
| 355 |
+
else:
|
| 356 |
+
data["goal_translation"] = []
|
| 357 |
+
return data
|
| 358 |
+
|
| 359 |
+
@staticmethod
|
| 360 |
+
def deserialize(data):
|
| 361 |
+
goal_translation = (
|
| 362 |
+
None
|
| 363 |
+
if not data["goal_translation"]
|
| 364 |
+
else _lists_to_tuple(data["goal_translation"])
|
| 365 |
+
)
|
| 366 |
+
return XarmState(
|
| 367 |
+
obj_id=data["obj_id"],
|
| 368 |
+
base_pose=_lists_to_tuple(data["base_pose"]),
|
| 369 |
+
base_vel=_lists_to_tuple(data["base_vel"]),
|
| 370 |
+
joint_info=_lists_to_tuple(data["joint_info"]),
|
| 371 |
+
joint_state=_lists_to_tuple(data["joint_state"]),
|
| 372 |
+
goal_translation=goal_translation,
|
| 373 |
+
target_effector_pose=Pose3d.deserialize(data["target_effector_pose"]),
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def _serialize_pybullet_state(pybullet_state):
|
| 378 |
+
"""Convert data to POD types."""
|
| 379 |
+
if isinstance(pybullet_state, list):
|
| 380 |
+
return [_serialize_pybullet_state(entry) for entry in pybullet_state]
|
| 381 |
+
elif isinstance(pybullet_state, dict):
|
| 382 |
+
assert "_serialized_obj_name" not in pybullet_state
|
| 383 |
+
return {
|
| 384 |
+
key: _serialize_pybullet_state(value)
|
| 385 |
+
for key, value in pybullet_state.items()
|
| 386 |
+
}
|
| 387 |
+
elif isinstance(pybullet_state, (XarmState, ObjState)):
|
| 388 |
+
return {
|
| 389 |
+
"_serialized_obj_name": type(pybullet_state).__name__,
|
| 390 |
+
"_serialized_data": pybullet_state.serialize(),
|
| 391 |
+
}
|
| 392 |
+
elif isinstance(pybullet_state, int):
|
| 393 |
+
return pybullet_state
|
| 394 |
+
else:
|
| 395 |
+
raise ValueError(
|
| 396 |
+
"Unhandled type for object %s, type %s"
|
| 397 |
+
% (str(pybullet_state), type(pybullet_state))
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def _deserialize_pybullet_state(state):
|
| 402 |
+
"""Parse data from POD types."""
|
| 403 |
+
if isinstance(state, list):
|
| 404 |
+
return [_deserialize_pybullet_state(item) for item in state]
|
| 405 |
+
elif isinstance(state, dict):
|
| 406 |
+
if "_serialized_obj_name" in state:
|
| 407 |
+
if state["_serialized_obj_name"] == XarmState.__name__:
|
| 408 |
+
return XarmState.deserialize(state["_serialized_data"])
|
| 409 |
+
elif state["_serialized_obj_name"] == ObjState.__name__:
|
| 410 |
+
return ObjState.deserialize(state["_serialized_data"])
|
| 411 |
+
else:
|
| 412 |
+
raise ValueError("Unsupported: %s" % state["_serialized_obj_name"])
|
| 413 |
+
else:
|
| 414 |
+
return {
|
| 415 |
+
key: _deserialize_pybullet_state(value) for key, value in state.items()
|
| 416 |
+
}
|
| 417 |
+
elif isinstance(state, int):
|
| 418 |
+
return state
|
| 419 |
+
else:
|
| 420 |
+
raise ValueError("Unhandled type for object %s" % str(state))
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def write_pybullet_state(filename, pybullet_state, task, actions=None):
|
| 424 |
+
"""Serialize pybullet state to json file."""
|
| 425 |
+
import torch
|
| 426 |
+
data = {
|
| 427 |
+
"pybullet_state": _serialize_pybullet_state(pybullet_state),
|
| 428 |
+
"state_version": PYBULLET_STATE_VERSION,
|
| 429 |
+
"ts_ms": int(time.mktime(datetime.datetime.now().timetuple())) * 1000,
|
| 430 |
+
"user": getpass.getuser(),
|
| 431 |
+
"task": task,
|
| 432 |
+
"actions": actions if actions is not None else [],
|
| 433 |
+
}
|
| 434 |
+
torch.save(data, filename)
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def read_pybullet_state(filename):
|
| 438 |
+
"""Deserialize pybullet state from json file."""
|
| 439 |
+
import torch
|
| 440 |
+
data = torch.load(filename)
|
| 441 |
+
|
| 442 |
+
assert isinstance(data, dict)
|
| 443 |
+
|
| 444 |
+
if data["state_version"] != PYBULLET_STATE_VERSION:
|
| 445 |
+
raise ValueError(
|
| 446 |
+
"incompatible state data (version %d, expected %d)"
|
| 447 |
+
% (data["state_version"], PYBULLET_STATE_VERSION)
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
data["pybullet_state"] = _deserialize_pybullet_state(data["pybullet_state"])
|
| 451 |
+
return data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/xarm_sim_robot.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2022 The Reach ML Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""XArm Robot Kinematics."""
|
| 17 |
+
from cleandiffuser.env.block_pushing.utils import utils_pybullet
|
| 18 |
+
from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
|
| 19 |
+
import numpy as np
|
| 20 |
+
from scipy.spatial import transform
|
| 21 |
+
import pybullet
|
| 22 |
+
|
| 23 |
+
XARM_URDF_PATH = (
|
| 24 |
+
"third_party/bullet/examples/pybullet/gym/pybullet_data/" "xarm/xarm6_robot.urdf"
|
| 25 |
+
)
|
| 26 |
+
SUCTION_URDF_PATH = "third_party/py/envs/assets/suction/" "suction-head-long.urdf"
|
| 27 |
+
CYLINDER_URDF_PATH = "third_party/py/envs/assets/suction/" "cylinder.urdf"
|
| 28 |
+
CYLINDER_REAL_URDF_PATH = "third_party/py/envs/assets/suction/" "cylinder_real.urdf"
|
| 29 |
+
HOME_JOINT_POSITIONS = np.deg2rad([0, -20, -80, 0, 100, -30])
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class XArmSimRobot:
|
| 33 |
+
"""A simulated PyBullet XArm robot, mostly for forward/inverse kinematics."""
|
| 34 |
+
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
pybullet_client,
|
| 38 |
+
initial_joint_positions=HOME_JOINT_POSITIONS,
|
| 39 |
+
end_effector="none",
|
| 40 |
+
color="default",
|
| 41 |
+
):
|
| 42 |
+
self._pybullet_client = pybullet_client
|
| 43 |
+
self.initial_joint_positions = initial_joint_positions
|
| 44 |
+
|
| 45 |
+
if color == "default":
|
| 46 |
+
self.xarm = utils_pybullet.load_urdf(
|
| 47 |
+
pybullet_client, XARM_URDF_PATH, [0, 0, 0]
|
| 48 |
+
)
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError("Unrecognized xarm color %s" % color)
|
| 51 |
+
|
| 52 |
+
# Get revolute joints of robot (skip fixed joints).
|
| 53 |
+
joints = []
|
| 54 |
+
joint_indices = []
|
| 55 |
+
for i in range(self._pybullet_client.getNumJoints(self.xarm)):
|
| 56 |
+
joint_info = self._pybullet_client.getJointInfo(self.xarm, i)
|
| 57 |
+
if joint_info[2] == pybullet.JOINT_REVOLUTE:
|
| 58 |
+
joints.append(joint_info[0])
|
| 59 |
+
joint_indices.append(i)
|
| 60 |
+
# Note examples in pybullet do this, but it is not clear what the
|
| 61 |
+
# benefits are.
|
| 62 |
+
self._pybullet_client.changeDynamics(
|
| 63 |
+
self.xarm, i, linearDamping=0, angularDamping=0
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
self._n_joints = len(joints)
|
| 67 |
+
self._joints = tuple(joints)
|
| 68 |
+
self._joint_indices = tuple(joint_indices)
|
| 69 |
+
|
| 70 |
+
# Move robot to home joint configuration
|
| 71 |
+
self.reset_joints(self.initial_joint_positions)
|
| 72 |
+
self.effector_link = 6
|
| 73 |
+
|
| 74 |
+
if (
|
| 75 |
+
end_effector == "suction"
|
| 76 |
+
or end_effector == "cylinder"
|
| 77 |
+
or end_effector == "cylinder_real"
|
| 78 |
+
):
|
| 79 |
+
self.end_effector = self._setup_end_effector(end_effector)
|
| 80 |
+
else:
|
| 81 |
+
if end_effector != "none":
|
| 82 |
+
raise ValueError('end_effector "%s" is not supported.' % end_effector)
|
| 83 |
+
self.end_effector = None
|
| 84 |
+
|
| 85 |
+
def _setup_end_effector(self, end_effector):
|
| 86 |
+
"""Adds a suction or cylinder end effector."""
|
| 87 |
+
pose = self.forward_kinematics()
|
| 88 |
+
if end_effector == "suction":
|
| 89 |
+
body = utils_pybullet.load_urdf(
|
| 90 |
+
self._pybullet_client,
|
| 91 |
+
SUCTION_URDF_PATH,
|
| 92 |
+
pose.translation,
|
| 93 |
+
pose.rotation.as_quat(),
|
| 94 |
+
)
|
| 95 |
+
elif end_effector == "cylinder":
|
| 96 |
+
body = utils_pybullet.load_urdf(
|
| 97 |
+
self._pybullet_client,
|
| 98 |
+
CYLINDER_URDF_PATH,
|
| 99 |
+
pose.translation,
|
| 100 |
+
pose.rotation.as_quat(),
|
| 101 |
+
)
|
| 102 |
+
elif end_effector == "cylinder_real":
|
| 103 |
+
body = utils_pybullet.load_urdf(
|
| 104 |
+
self._pybullet_client,
|
| 105 |
+
CYLINDER_REAL_URDF_PATH,
|
| 106 |
+
pose.translation,
|
| 107 |
+
pose.rotation.as_quat(),
|
| 108 |
+
)
|
| 109 |
+
else:
|
| 110 |
+
raise ValueError('end_effector "%s" is not supported.' % end_effector)
|
| 111 |
+
|
| 112 |
+
constraint_id = self._pybullet_client.createConstraint(
|
| 113 |
+
parentBodyUniqueId=self.xarm,
|
| 114 |
+
parentLinkIndex=6,
|
| 115 |
+
childBodyUniqueId=body,
|
| 116 |
+
childLinkIndex=-1,
|
| 117 |
+
jointType=pybullet.JOINT_FIXED,
|
| 118 |
+
jointAxis=(0, 0, 0),
|
| 119 |
+
parentFramePosition=(0, 0, 0),
|
| 120 |
+
childFramePosition=(0, 0, 0),
|
| 121 |
+
)
|
| 122 |
+
self._pybullet_client.changeConstraint(constraint_id, maxForce=50)
|
| 123 |
+
|
| 124 |
+
return body
|
| 125 |
+
|
| 126 |
+
def reset_joints(self, joint_values):
|
| 127 |
+
"""Sets the position of the Robot's joints.
|
| 128 |
+
|
| 129 |
+
*Note*: This should only be used at the start while not running the
|
| 130 |
+
simulation resetJointState overrides all physics simulation.
|
| 131 |
+
|
| 132 |
+
Args:
|
| 133 |
+
joint_values: Iterable with desired joint positions.
|
| 134 |
+
"""
|
| 135 |
+
for i in range(self._n_joints):
|
| 136 |
+
self._pybullet_client.resetJointState(
|
| 137 |
+
self.xarm, self._joints[i], joint_values[i]
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
def get_joints_measured(self):
|
| 141 |
+
joint_states = self._pybullet_client.getJointStates(
|
| 142 |
+
self.xarm, self._joint_indices
|
| 143 |
+
)
|
| 144 |
+
joint_positions = np.array([state[0] for state in joint_states])
|
| 145 |
+
joint_velocities = np.array([state[1] for state in joint_states])
|
| 146 |
+
joint_torques = np.array([state[3] for state in joint_states])
|
| 147 |
+
return joint_positions, joint_velocities, joint_torques
|
| 148 |
+
|
| 149 |
+
def get_joint_positions(self):
|
| 150 |
+
joint_states = self._pybullet_client.getJointStates(
|
| 151 |
+
self.xarm, self._joint_indices
|
| 152 |
+
)
|
| 153 |
+
joint_positions = np.array([state[0] for state in joint_states])
|
| 154 |
+
return joint_positions
|
| 155 |
+
|
| 156 |
+
def forward_kinematics(self):
|
| 157 |
+
"""Forward kinematics."""
|
| 158 |
+
effector_state = self._pybullet_client.getLinkState(
|
| 159 |
+
self.xarm, self.effector_link
|
| 160 |
+
)
|
| 161 |
+
return Pose3d(
|
| 162 |
+
translation=np.array(effector_state[0]),
|
| 163 |
+
rotation=transform.Rotation.from_quat(effector_state[1]),
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
def inverse_kinematics(
|
| 167 |
+
self, world_effector_pose, max_iterations=100, residual_threshold=1e-10
|
| 168 |
+
):
|
| 169 |
+
"""Inverse kinematics.
|
| 170 |
+
|
| 171 |
+
Args:
|
| 172 |
+
world_effector_pose: Target Pose3d for the robot's end effector.
|
| 173 |
+
max_iterations: Refine the IK solution until the distance between target
|
| 174 |
+
and actual end effector position is below this threshold, or the
|
| 175 |
+
maxNumIterations is reached. Default is 20 iterations.
|
| 176 |
+
residual_threshold: Refine the IK solution until the distance between
|
| 177 |
+
target and actual end effector position is below this threshold, or the
|
| 178 |
+
maxNumIterations is reached.
|
| 179 |
+
|
| 180 |
+
Returns:
|
| 181 |
+
Numpy array with required joint angles to reach the requested pose.
|
| 182 |
+
"""
|
| 183 |
+
return np.array(
|
| 184 |
+
self._pybullet_client.calculateInverseKinematics(
|
| 185 |
+
self.xarm,
|
| 186 |
+
self.effector_link,
|
| 187 |
+
world_effector_pose.translation,
|
| 188 |
+
world_effector_pose.rotation.as_quat(), # as_quat returns xyzw.
|
| 189 |
+
lowerLimits=[-17] * 6,
|
| 190 |
+
upperLimits=[17] * 6,
|
| 191 |
+
jointRanges=[17] * 6,
|
| 192 |
+
restPoses=[0, 0] + self.get_joint_positions()[2:].tolist(),
|
| 193 |
+
maxNumIterations=max_iterations,
|
| 194 |
+
residualThreshold=residual_threshold,
|
| 195 |
+
)
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
def set_target_effector_pose(self, world_effector_pose):
|
| 199 |
+
target_joint_positions = self.inverse_kinematics(world_effector_pose)
|
| 200 |
+
self.set_target_joint_positions(target_joint_positions)
|
| 201 |
+
|
| 202 |
+
def set_target_joint_velocities(self, target_joint_velocities):
|
| 203 |
+
self._pybullet_client.setJointMotorControlArray(
|
| 204 |
+
self.xarm,
|
| 205 |
+
self._joint_indices,
|
| 206 |
+
pybullet.VELOCITY_CONTROL,
|
| 207 |
+
targetVelocities=target_joint_velocities,
|
| 208 |
+
forces=[5 * 240.0] * 6,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
def set_target_joint_positions(self, target_joint_positions):
|
| 212 |
+
self._pybullet_client.setJointMotorControlArray(
|
| 213 |
+
self.xarm,
|
| 214 |
+
self._joint_indices,
|
| 215 |
+
pybullet.POSITION_CONTROL,
|
| 216 |
+
targetPositions=target_joint_positions,
|
| 217 |
+
forces=[5 * 240.0] * 6,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def set_alpha_transparency(self, alpha):
|
| 221 |
+
visual_shape_data = self._pybullet_client.getVisualShapeData(self.xarm)
|
| 222 |
+
|
| 223 |
+
for i in range(self._pybullet_client.getNumJoints(self.xarm)):
|
| 224 |
+
object_id, link_index, _, _, _, _, _, rgba_color = visual_shape_data[i]
|
| 225 |
+
assert object_id == self.xarm, "xarm id mismatch."
|
| 226 |
+
assert link_index == i, "Link visual data was returned out of order."
|
| 227 |
+
rgba_color = list(rgba_color[0:3]) + [alpha]
|
| 228 |
+
self._pybullet_client.changeVisualShape(
|
| 229 |
+
self.xarm, linkIndex=i, rgbaColor=rgba_color
|
| 230 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Environments using kitchen and Franka robot."""
|
| 2 |
+
from gym.envs.registration import register
|
| 3 |
+
|
| 4 |
+
register(
|
| 5 |
+
id="kitchen-microwave-kettle-light-slider-v0",
|
| 6 |
+
entry_point="cleandiffuser.env.kitchen.v0:KitchenMicrowaveKettleLightSliderV0",
|
| 7 |
+
max_episode_steps=280,
|
| 8 |
+
reward_threshold=1.0,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
register(
|
| 12 |
+
id="kitchen-microwave-kettle-burner-light-v0",
|
| 13 |
+
entry_point="cleandiffuser.env.kitchen.v0:KitchenMicrowaveKettleBottomBurnerLightV0",
|
| 14 |
+
max_episode_steps=280,
|
| 15 |
+
reward_threshold=1.0,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
register(
|
| 19 |
+
id="kitchen-kettle-microwave-light-slider-v0",
|
| 20 |
+
entry_point="cleandiffuser.env.kitchen.v0:KitchenKettleMicrowaveLightSliderV0",
|
| 21 |
+
max_episode_steps=280,
|
| 22 |
+
reward_threshold=1.0,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
register(
|
| 26 |
+
id="kitchen-all-v0",
|
| 27 |
+
entry_point="cleandiffuser.env.kitchen.v0:KitchenAllV0",
|
| 28 |
+
max_episode_steps=280,
|
| 29 |
+
reward_threshold=1.0,
|
| 30 |
+
)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/base.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
# hack to import adept envs
|
| 4 |
+
ADEPT_DIR = os.path.join(os.path.dirname(__file__), 'relay_policy_learning', 'adept_envs')
|
| 5 |
+
sys.path.append(ADEPT_DIR)
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import numpy as np
|
| 9 |
+
import adept_envs
|
| 10 |
+
from adept_envs.franka.kitchen_multitask_v0 import KitchenTaskRelaxV1
|
| 11 |
+
|
| 12 |
+
OBS_ELEMENT_INDICES = {
|
| 13 |
+
"bottom burner": np.array([11, 12]),
|
| 14 |
+
"top burner": np.array([15, 16]),
|
| 15 |
+
"light switch": np.array([17, 18]),
|
| 16 |
+
"slide cabinet": np.array([19]),
|
| 17 |
+
"hinge cabinet": np.array([20, 21]),
|
| 18 |
+
"microwave": np.array([22]),
|
| 19 |
+
"kettle": np.array([23, 24, 25, 26, 27, 28, 29]),
|
| 20 |
+
}
|
| 21 |
+
OBS_ELEMENT_GOALS = {
|
| 22 |
+
"bottom burner": np.array([-0.88, -0.01]),
|
| 23 |
+
"top burner": np.array([-0.92, -0.01]),
|
| 24 |
+
"light switch": np.array([-0.69, -0.05]),
|
| 25 |
+
"slide cabinet": np.array([0.37]),
|
| 26 |
+
"hinge cabinet": np.array([0.0, 1.45]),
|
| 27 |
+
"microwave": np.array([-0.75]),
|
| 28 |
+
"kettle": np.array([-0.23, 0.75, 1.62, 0.99, 0.0, 0.0, -0.06]),
|
| 29 |
+
}
|
| 30 |
+
BONUS_THRESH = 0.3
|
| 31 |
+
logger = logging.getLogger()
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class KitchenBase(KitchenTaskRelaxV1):
|
| 35 |
+
# A string of element names. The robot's task is then to modify each of
|
| 36 |
+
# these elements appropriately.
|
| 37 |
+
TASK_ELEMENTS = []
|
| 38 |
+
ALL_TASKS = [
|
| 39 |
+
"bottom burner",
|
| 40 |
+
"top burner",
|
| 41 |
+
"light switch",
|
| 42 |
+
"slide cabinet",
|
| 43 |
+
"hinge cabinet",
|
| 44 |
+
"microwave",
|
| 45 |
+
"kettle",
|
| 46 |
+
]
|
| 47 |
+
REMOVE_TASKS_WHEN_COMPLETE = True
|
| 48 |
+
TERMINATE_ON_TASK_COMPLETE = True
|
| 49 |
+
TERMINATE_ON_WRONG_COMPLETE = False
|
| 50 |
+
COMPLETE_IN_ANY_ORDER = (
|
| 51 |
+
True # This allows for the tasks to be completed in arbitrary order.
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def __init__(
|
| 55 |
+
self, dataset_url=None, ref_max_score=None, ref_min_score=None,
|
| 56 |
+
use_abs_action=False,
|
| 57 |
+
**kwargs
|
| 58 |
+
):
|
| 59 |
+
self.tasks_to_complete = list(self.TASK_ELEMENTS)
|
| 60 |
+
self.goal_masking = True
|
| 61 |
+
super(KitchenBase, self).__init__(use_abs_action=use_abs_action, **kwargs)
|
| 62 |
+
|
| 63 |
+
def set_goal_masking(self, goal_masking=True):
|
| 64 |
+
"""Sets goal masking for goal-conditioned approaches (like RPL)."""
|
| 65 |
+
self.goal_masking = goal_masking
|
| 66 |
+
|
| 67 |
+
def _get_task_goal(self, task=None, actually_return_goal=False):
|
| 68 |
+
if task is None:
|
| 69 |
+
task = ["microwave", "kettle", "bottom burner", "light switch"]
|
| 70 |
+
new_goal = np.zeros_like(self.goal)
|
| 71 |
+
if self.goal_masking and not actually_return_goal:
|
| 72 |
+
return new_goal
|
| 73 |
+
for element in task:
|
| 74 |
+
element_idx = OBS_ELEMENT_INDICES[element]
|
| 75 |
+
element_goal = OBS_ELEMENT_GOALS[element]
|
| 76 |
+
new_goal[element_idx] = element_goal
|
| 77 |
+
|
| 78 |
+
return new_goal
|
| 79 |
+
|
| 80 |
+
def reset_model(self):
|
| 81 |
+
self.tasks_to_complete = list(self.TASK_ELEMENTS)
|
| 82 |
+
return super(KitchenBase, self).reset_model()
|
| 83 |
+
|
| 84 |
+
def get_observation(self):
|
| 85 |
+
return self._get_obs()
|
| 86 |
+
|
| 87 |
+
def _get_reward_n_score(self, obs_dict):
|
| 88 |
+
reward_dict, score = super(KitchenBase, self)._get_reward_n_score(obs_dict)
|
| 89 |
+
reward = 0.0
|
| 90 |
+
next_q_obs = obs_dict["qp"]
|
| 91 |
+
next_obj_obs = obs_dict["obj_qp"]
|
| 92 |
+
next_goal = self._get_task_goal(
|
| 93 |
+
task=self.TASK_ELEMENTS, actually_return_goal=True
|
| 94 |
+
) # obs_dict['goal']
|
| 95 |
+
idx_offset = len(next_q_obs)
|
| 96 |
+
completions = []
|
| 97 |
+
all_completed_so_far = True
|
| 98 |
+
for element in self.tasks_to_complete:
|
| 99 |
+
element_idx = OBS_ELEMENT_INDICES[element]
|
| 100 |
+
distance = np.linalg.norm(
|
| 101 |
+
next_obj_obs[..., element_idx - idx_offset] - next_goal[element_idx]
|
| 102 |
+
)
|
| 103 |
+
complete = distance < BONUS_THRESH
|
| 104 |
+
condition = (
|
| 105 |
+
complete and all_completed_so_far
|
| 106 |
+
if not self.COMPLETE_IN_ANY_ORDER
|
| 107 |
+
else complete
|
| 108 |
+
)
|
| 109 |
+
if condition: # element == self.tasks_to_complete[0]:
|
| 110 |
+
print("Task {} completed!".format(element))
|
| 111 |
+
completions.append(element)
|
| 112 |
+
all_completed_so_far = all_completed_so_far and complete
|
| 113 |
+
if self.REMOVE_TASKS_WHEN_COMPLETE:
|
| 114 |
+
[self.tasks_to_complete.remove(element) for element in completions]
|
| 115 |
+
bonus = float(len(completions))
|
| 116 |
+
reward_dict["bonus"] = bonus
|
| 117 |
+
reward_dict["r_total"] = bonus
|
| 118 |
+
score = bonus
|
| 119 |
+
return reward_dict, score
|
| 120 |
+
|
| 121 |
+
def step(self, a, b=None):
|
| 122 |
+
obs, reward, done, env_info = super(KitchenBase, self).step(a, b=b)
|
| 123 |
+
if self.TERMINATE_ON_TASK_COMPLETE:
|
| 124 |
+
done = not self.tasks_to_complete
|
| 125 |
+
if self.TERMINATE_ON_WRONG_COMPLETE:
|
| 126 |
+
all_goal = self._get_task_goal(task=self.ALL_TASKS)
|
| 127 |
+
for wrong_task in list(set(self.ALL_TASKS) - set(self.TASK_ELEMENTS)):
|
| 128 |
+
element_idx = OBS_ELEMENT_INDICES[wrong_task]
|
| 129 |
+
distance = np.linalg.norm(obs[..., element_idx] - all_goal[element_idx])
|
| 130 |
+
complete = distance < BONUS_THRESH
|
| 131 |
+
if complete:
|
| 132 |
+
done = True
|
| 133 |
+
break
|
| 134 |
+
env_info["completed_tasks"] = set(self.TASK_ELEMENTS) - set(
|
| 135 |
+
self.tasks_to_complete
|
| 136 |
+
)
|
| 137 |
+
return obs, reward, done, env_info
|
| 138 |
+
|
| 139 |
+
def get_goal(self):
|
| 140 |
+
"""Loads goal state from dataset for goal-conditioned approaches (like RPL)."""
|
| 141 |
+
raise NotImplementedError
|
| 142 |
+
|
| 143 |
+
def _split_data_into_seqs(self, data):
|
| 144 |
+
"""Splits dataset object into list of sequence dicts."""
|
| 145 |
+
seq_end_idxs = np.where(data["terminals"])[0]
|
| 146 |
+
start = 0
|
| 147 |
+
seqs = []
|
| 148 |
+
for end_idx in seq_end_idxs:
|
| 149 |
+
seqs.append(
|
| 150 |
+
dict(
|
| 151 |
+
states=data["observations"][start : end_idx + 1],
|
| 152 |
+
actions=data["actions"][start : end_idx + 1],
|
| 153 |
+
)
|
| 154 |
+
)
|
| 155 |
+
start = end_idx + 1
|
| 156 |
+
return seqs
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_lowdim_wrapper.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Dict, Optional, Optional
|
| 2 |
+
import numpy as np
|
| 3 |
+
import gym
|
| 4 |
+
from gym.spaces import Box
|
| 5 |
+
from cleandiffuser.env.kitchen.base import KitchenBase
|
| 6 |
+
|
| 7 |
+
class KitchenLowdimWrapper(gym.Env):
|
| 8 |
+
def __init__(self,
|
| 9 |
+
env: KitchenBase,
|
| 10 |
+
init_qpos: Optional[np.ndarray]=None,
|
| 11 |
+
init_qvel: Optional[np.ndarray]=None,
|
| 12 |
+
render_hw = (240,360)
|
| 13 |
+
):
|
| 14 |
+
self.env = env
|
| 15 |
+
self.all_tasks = env.ALL_TASKS
|
| 16 |
+
self.init_qpos = init_qpos
|
| 17 |
+
self.init_qvel = init_qvel
|
| 18 |
+
self.render_hw = render_hw
|
| 19 |
+
|
| 20 |
+
@property
|
| 21 |
+
def action_space(self):
|
| 22 |
+
return self.env.action_space
|
| 23 |
+
|
| 24 |
+
@property
|
| 25 |
+
def observation_space(self):
|
| 26 |
+
return self.env.observation_space
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
def tasks_to_complete(self):
|
| 30 |
+
return self.env.tasks_to_complete
|
| 31 |
+
|
| 32 |
+
def set_init_q(self, init_qpos, init_qvel):
|
| 33 |
+
self.init_qpos = init_qpos
|
| 34 |
+
self.init_qvel = init_qvel
|
| 35 |
+
|
| 36 |
+
def seed(self, seed=None):
|
| 37 |
+
return self.env.seed(seed)
|
| 38 |
+
|
| 39 |
+
def reset(self):
|
| 40 |
+
if self.init_qpos is not None:
|
| 41 |
+
print("Load known initial conditions.")
|
| 42 |
+
# reset anyway to be safe, not very expensive
|
| 43 |
+
_ = self.env.reset()
|
| 44 |
+
# start from known state
|
| 45 |
+
self.env.set_state(self.init_qpos, self.init_qvel)
|
| 46 |
+
obs = self.env.get_observation()
|
| 47 |
+
return obs
|
| 48 |
+
else:
|
| 49 |
+
return self.env.reset()
|
| 50 |
+
|
| 51 |
+
def render(self, mode='rgb_array'):
|
| 52 |
+
h, w = self.render_hw
|
| 53 |
+
return self.env.render(mode=mode, width=w, height=h)
|
| 54 |
+
|
| 55 |
+
def step(self, a):
|
| 56 |
+
return self.env.step(a)
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_util.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import struct
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def parse_mjl_logs(read_filename, skipamount):
|
| 5 |
+
with open(read_filename, mode='rb') as file:
|
| 6 |
+
fileContent = file.read()
|
| 7 |
+
headers = struct.unpack('iiiiiii', fileContent[:28])
|
| 8 |
+
nq = headers[0]
|
| 9 |
+
nv = headers[1]
|
| 10 |
+
nu = headers[2]
|
| 11 |
+
nmocap = headers[3]
|
| 12 |
+
nsensordata = headers[4]
|
| 13 |
+
nuserdata = headers[5]
|
| 14 |
+
name_len = headers[6]
|
| 15 |
+
name = struct.unpack(str(name_len) + 's', fileContent[28:28+name_len])[0]
|
| 16 |
+
rem_size = len(fileContent[28 + name_len:])
|
| 17 |
+
num_floats = int(rem_size/4)
|
| 18 |
+
dat = np.asarray(struct.unpack(str(num_floats) + 'f', fileContent[28+name_len:]))
|
| 19 |
+
recsz = 1 + nq + nv + nu + 7*nmocap + nsensordata + nuserdata
|
| 20 |
+
if rem_size % recsz != 0:
|
| 21 |
+
print("ERROR")
|
| 22 |
+
else:
|
| 23 |
+
dat = np.reshape(dat, (int(len(dat)/recsz), recsz))
|
| 24 |
+
dat = dat.T
|
| 25 |
+
|
| 26 |
+
time = dat[0,:][::skipamount] - 0*dat[0, 0]
|
| 27 |
+
qpos = dat[1:nq + 1, :].T[::skipamount, :]
|
| 28 |
+
qvel = dat[nq+1:nq+nv+1,:].T[::skipamount, :]
|
| 29 |
+
ctrl = dat[nq+nv+1:nq+nv+nu+1,:].T[::skipamount,:]
|
| 30 |
+
mocap_pos = dat[nq+nv+nu+1:nq+nv+nu+3*nmocap+1,:].T[::skipamount, :]
|
| 31 |
+
mocap_quat = dat[nq+nv+nu+3*nmocap+1:nq+nv+nu+7*nmocap+1,:].T[::skipamount, :]
|
| 32 |
+
sensordata = dat[nq+nv+nu+7*nmocap+1:nq+nv+nu+7*nmocap+nsensordata+1,:].T[::skipamount,:]
|
| 33 |
+
userdata = dat[nq+nv+nu+7*nmocap+nsensordata+1:,:].T[::skipamount,:]
|
| 34 |
+
|
| 35 |
+
data = dict(nq=nq,
|
| 36 |
+
nv=nv,
|
| 37 |
+
nu=nu,
|
| 38 |
+
nmocap=nmocap,
|
| 39 |
+
nsensordata=nsensordata,
|
| 40 |
+
name=name,
|
| 41 |
+
time=time,
|
| 42 |
+
qpos=qpos,
|
| 43 |
+
qvel=qvel,
|
| 44 |
+
ctrl=ctrl,
|
| 45 |
+
mocap_pos=mocap_pos,
|
| 46 |
+
mocap_quat=mocap_quat,
|
| 47 |
+
sensordata=sensordata,
|
| 48 |
+
userdata=userdata,
|
| 49 |
+
logName = read_filename
|
| 50 |
+
)
|
| 51 |
+
return data
|
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/relay_policy_learning/adept_envs/adept_envs/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
#
|
| 3 |
+
# Copyright 2020 Google LLC
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
|
| 17 |
+
import adept_envs.franka
|
| 18 |
+
|
| 19 |
+
from adept_envs.utils.configurable import global_config
|