tarzanagh's picture
Add files using upload-large-folder tool
c35fc6b verified
Raw
History Blame Contribute Delete
32.7 kB
import os
import numpy as np
import torch
from PIL import Image, ImageDraw
from typing import Optional, Union
import random
import yaml
import argparse
import imageio.v2 as imageio
from src.utils.utils import draw_obstacles_pixel, create_mask_from_binary_csv, apply_random_obstacle_variations_fix_seed, apply_shelf_obstacle_variations_fix_seed, apply_room_obstacle_variations_fix_seed
from scipy import interpolate
def make_position_seed(seed_base, obstacle_var_idx, position_var_idx):
"""Compute a reproducible per-run seed."""
if seed_base is None:
seed_base = 0
return int(seed_base + obstacle_var_idx * 1000 + position_var_idx)
def set_global_seed(seed: int):
"""Set random seed for reproducibility"""
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
def normalized_to_pixel(coords, img_size):
c = np.asarray(coords)
px = ((np.clip(c, -1, 1) + 1) * 0.5 * (img_size - 1)).round().astype(int)
return px
def generate_multi_goals_and_background(
map_name, num_goals, goal_prompts, bg_image, obstacle_tensor,
obstacle_ood, goal_bounds, img_size, eval_config, device
):
if map_name == "ood":
background_with_obstacle = draw_obstacles_pixel(bg_image.copy(), obstacle_ood.squeeze(1))
apple_indices = [i for i, prompt in enumerate(goal_prompts) if "apple" in prompt.lower()]
non_apple_indices = [i for i in range(num_goals) if i not in apple_indices]
multi_goals = torch.zeros(1, num_goals, 2, device=device, dtype=torch.float32)
if len(apple_indices) > 0:
fixed_pos = eval_config['ood']['apple_fixed_position']
multi_goals[0, apple_indices[0], 0] = fixed_pos[0]
multi_goals[0, apple_indices[0], 1] = fixed_pos[1]
random_goal_indices = non_apple_indices + apple_indices[1:]
if len(random_goal_indices) > 0:
ood_goal_dist = eval_config['evaluation']['goal_generation']['min_distance_between_goals']
ood_clearance = eval_config['ood']['goal_generation'].get('obstacle_clearance_pixels',
eval_config['evaluation']['obstacle_clearance_pixels'])
random_goals = gen_goals(
goal_bounds, (len(random_goal_indices), 1), img_size, dist=ood_goal_dist,
obstacles=obstacle_tensor, device=device, obstacle_clearance_pixels=ood_clearance
)
for i, idx in enumerate(random_goal_indices):
multi_goals[0, idx, :] = random_goals[0, i, :]
else:
background_with_obstacle = draw_obstacles_pixel(bg_image.copy(), obstacle_tensor.squeeze(1))
goal_dist = eval_config['evaluation']['goal_generation']['min_distance_between_goals']
clearance = eval_config['evaluation']['obstacle_clearance_pixels']
multi_goals = gen_goals(
goal_bounds, (num_goals, 1), img_size, dist=goal_dist,
obstacles=obstacle_tensor, device=device, obstacle_clearance_pixels=clearance
)
return multi_goals, background_with_obstacle
def get_distance(a, b):
return torch.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
def is_valid_goals(pixel_x, pixel_y, obstacles, batch_idx, clearance=0):
if obstacles.dim() == 4:
obstacles = obstacles.squeeze(1)
B, H, W = obstacles.shape
if not (0 <= pixel_x < W and 0 <= pixel_y < H):
return False
x_start = max(0, pixel_x - clearance)
x_end = min(W, pixel_x + clearance + 1)
y_start = max(0, pixel_y - clearance)
y_end = min(H, pixel_y + clearance + 1)
area_to_check = obstacles[batch_idx, x_start:x_end, y_start:y_end]
return torch.sum(area_to_check) == 0
def gen_goals(
bounds,
n:Union[tuple, int],
img_size: int,
dist:Optional[float]=0.25,
obstacles:Optional[torch.Tensor]=None,
seed: Optional[int]=None,
device='cuda',
obstacle_clearance_pixels: int = 0
):
if seed is not None:
torch.manual_seed(seed)
assert len(bounds) == 4, f'Unappropriate map bound: {bounds}'
if isinstance(n, int):
M = 1
N = n
else:
M, N = n
goals = []
for batch_idx in range(N):
batch_goals = []
while len(batch_goals) < M:
x = bounds[0] + (bounds[1] - bounds[0]) * torch.rand((1, 1), dtype=torch.float32, device=device)
y = bounds[2] + (bounds[3] - bounds[2]) * torch.rand((1, 1), dtype=torch.float32, device=device)
if obstacles is not None:
pixel_x = ((x + 1) * 0.5 * (img_size - 1)).long().item()
pixel_y = ((y + 1) * 0.5 * (img_size - 1)).long().item()
if not is_valid_goals(pixel_x, pixel_y, obstacles, batch_idx, clearance=obstacle_clearance_pixels):
continue
valid = True
if dist is not None:
for existing_goal in batch_goals:
if get_distance([x, y], [existing_goal[0, 0], existing_goal[0, 1]]) < dist:
valid = False
break
if valid:
batch_goals.append(torch.cat((x, y), dim=1))
goals.append(torch.cat(batch_goals, dim=0))
return torch.stack(goals)
def gen_agents(
bounds,
n:Union[tuple, int],
img_size: int,
dist:Optional[float]=None,
obstacles:Optional[torch.Tensor]=None,
seed: Optional[int]=None,
device='cuda',
obstacle_clearance_pixels: int = 0,
goals_to_avoid: Optional[torch.Tensor] = None,
min_goal_dist: Optional[float] = None
):
if seed is not None:
torch.manual_seed(seed)
assert len(bounds) == 4, f'Unappropriate map bound: {bounds}'
if isinstance(n, int):
M = 1
N = n
else:
M, N = n
goals = []
for batch_idx in range(N):
batch_goals = []
while len(batch_goals) < M:
x = bounds[0] + (bounds[1] - bounds[0]) * torch.rand((1, 1), dtype=torch.float32, device=device)
y = bounds[2] + (bounds[3] - bounds[2]) * torch.rand((1, 1), dtype=torch.float32, device=device)
if obstacles is not None:
pixel_x = ((x + 1) * 0.5 * (img_size - 1)).long().item()
pixel_y = ((y + 1) * 0.5 * (img_size - 1)).long().item()
if not is_valid_goals(pixel_x, pixel_y, obstacles, batch_idx, clearance=obstacle_clearance_pixels):
continue
valid = True
if dist is not None:
for existing_agent in batch_goals:
if get_distance([x, y], [existing_agent[0, 0], existing_agent[0, 1]]) < dist:
valid = False
break
if not valid:
continue
if goals_to_avoid is not None and min_goal_dist is not None:
all_goals_for_batch = goals_to_avoid[batch_idx]
for i in range(all_goals_for_batch.shape[0]):
existing_goal = all_goals_for_batch[i]
if get_distance([x, y], [existing_goal[0], existing_goal[1]]) < min_goal_dist:
valid = False
break
if not valid:
continue
batch_goals.append(torch.cat((x, y), dim=1))
goals.append(torch.cat(batch_goals, dim=0))
return torch.stack(goals)
def overlay_goals_with_prompts_add_background(img, img_size, objs, pos, goal_prompts, goal_size):
assert pos.dim() == 3
if len(img) != pos.shape[0]:
img = img * pos.shape[0]
original_backgrounds = [im.copy() for im in img]
Orig_W, Orig_H = original_backgrounds[0].size
resized_backgrounds = [im.resize((img_size, img_size), Image.LANCZOS) for im in img]
W, H = resized_backgrounds[0].size
objs_input_list = [img.copy() for img in objs]
objs_resized = []
for i in range(len(objs_input_list)):
new_w_res = W // goal_size
new_h_res = H // goal_size
objs_resized.append(objs_input_list[i].resize((new_w_res, new_h_res), Image.LANCZOS))
objs_original_sized = []
for i in range(len(objs_input_list)):
new_w_orig = Orig_W // goal_size
new_h_orig = Orig_H // goal_size
objs_original_sized.append(objs_input_list[i].resize((new_w_orig, new_h_orig), Image.LANCZOS))
pos_pix_resized = ((1 + pos) / 2 * torch.tensor([H - 1, W - 1], device=pos.device, dtype=pos.dtype)).cpu().numpy()
pos_pix_original = ((1 + pos) / 2 * torch.tensor([Orig_H - 1, Orig_W - 1], device=pos.device, dtype=pos.dtype)).cpu().numpy()
imgs_resized_np = []
imgs_original_pil = []
batch_prompts = []
for i in range(pos_pix_resized.shape[0]):
bg_resized = resized_backgrounds[i].copy()
bg_original = original_backgrounds[i].copy()
ps_resized = pos_pix_resized[i]
ps_original = pos_pix_original[i]
for j in range(ps_resized.shape[0]):
obj_to_paste_res = objs_resized[j]
w_res, h_res = obj_to_paste_res.size
p_res = ps_resized[j]
p1_res, p0_res = round(p_res[1]), round(p_res[0])
bg_resized.paste(obj_to_paste_res, (p1_res - w_res // 2, p0_res - h_res // 2), obj_to_paste_res)
obj_to_paste_orig = objs_original_sized[j]
w_orig, h_orig = obj_to_paste_orig.size
p_orig = ps_original[j]
p1_orig, p0_orig = round(p_orig[1]), round(p_orig[0])
bg_original.paste(obj_to_paste_orig, (p1_orig - w_orig // 2, p0_orig - h_orig // 2), obj_to_paste_orig)
img_np = np.array(bg_resized)[..., :3] / 255
imgs_resized_np.append(img_np)
imgs_original_pil.append(bg_original)
batch_prompts.append([f"{goal_prompts[j]}" for j in range(len(ps_resized))])
imgs_tensor = torch.tensor(imgs_resized_np, dtype=pos.dtype, device=pos.device).permute(0, 3, 1, 2)
return imgs_tensor, batch_prompts, imgs_original_pil
def draw_agents_on_map(
agent_positions,
background_image,
dot_size=15
):
mapp = background_image.copy()
draw = ImageDraw.Draw(mapp)
img_width, img_height = mapp.size
if agent_positions.dim() == 3:
points = agent_positions[0].cpu().numpy()
else:
points = agent_positions.cpu().numpy()
h_coords = (points[:, 0] + 1) / 2 * (img_height - 1)
w_coords = (points[:, 1] + 1) / 2 * (img_width - 1)
agent_colors = ["red", "green", "blue", "orange", "purple", "cyan", "magenta", "yellow", "brown"]
for i in range(points.shape[0]):
xc, yc = w_coords[i], h_coords[i]
color = agent_colors[i % len(agent_colors)]
bbox = [
(xc - dot_size / 2, yc - dot_size / 2),
(xc + dot_size / 2, yc + dot_size / 2)
]
draw.ellipse(bbox, fill=color)
return [mapp]
def load_config(path):
with open(path, 'r') as file:
config = yaml.safe_load(file)
return config
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def collision_grad(x, d_min=0.084, eps=1e-8):
diff = x.unsqueeze(2) - x.unsqueeze(1)
dist = torch.linalg.norm(diff, dim=-1) + eps
mask = dist < d_min
grad_mag = torch.where(mask, (d_min - dist) / dist, 0.)
grad = (grad_mag.unsqueeze(-1) * diff).sum(dim=2)
return grad
def compute_path_length_from_pos(trajs_pos, real_world_meters=18.0):
METERS_PER_NORMALIZED_UNIT = real_world_meters / 2.0
if trajs_pos.ndim == 4:
path_length = torch.linalg.norm(torch.diff(trajs_pos, dim=2), dim=-1).sum(2)
elif trajs_pos.ndim == 3:
path_length = torch.linalg.norm(torch.diff(trajs_pos, dim=1), dim=-1).sum(1)
else:
raise ValueError(f"Expected trajs_pos to be 3D or 4D, got {trajs_pos.ndim}D")
path_length_meters = path_length * METERS_PER_NORMALIZED_UNIT
return path_length_meters
def resample_trajectory_constant_velocity(trajs_pos, num_samples=None):
if trajs_pos.ndim == 4:
B, N, T, D = trajs_pos.shape
elif trajs_pos.ndim == 3:
trajs_pos = trajs_pos.unsqueeze(0)
B, N, T, D = trajs_pos.shape
squeeze_output = True
else:
raise ValueError(f"Expected trajs_pos to be 3D or 4D, got {trajs_pos.ndim}D")
if num_samples is None:
num_samples = T
resampled_trajs = torch.zeros(B, N, num_samples, D, device=trajs_pos.device)
for b in range(B):
for n in range(N):
traj = trajs_pos[b, n].cpu().numpy()
diffs = np.diff(traj, axis=0)
segment_lengths = np.linalg.norm(diffs, axis=1)
arc_length = np.concatenate([[0], np.cumsum(segment_lengths)])
arc_length_diffs = np.diff(arc_length)
if np.any(arc_length_diffs <= 1e-10):
indices = np.linspace(0, T - 1, num_samples)
for d in range(D):
resampled_trajs[b, n, :, d] = torch.from_numpy(
np.interp(indices, np.arange(T), traj[:, d])
).to(trajs_pos.device)
continue
if arc_length[-1] < 1e-6:
resampled_trajs[b, n] = trajs_pos[b, n, 0].unsqueeze(0).repeat(num_samples, 1)
continue
cs_x = interpolate.CubicSpline(arc_length, traj[:, 0])
cs_y = interpolate.CubicSpline(arc_length, traj[:, 1])
new_arc_length = np.linspace(0, arc_length[-1], num_samples)
resampled_x = cs_x(new_arc_length)
resampled_y = cs_y(new_arc_length)
resampled_trajs[b, n, :, 0] = torch.from_numpy(resampled_x).to(trajs_pos.device)
resampled_trajs[b, n, :, 1] = torch.from_numpy(resampled_y).to(trajs_pos.device)
if 'squeeze_output' in locals() and squeeze_output:
resampled_trajs = resampled_trajs.squeeze(0)
return resampled_trajs
def compute_energy_consumption(trajs_pos, wheel_radius=0.1, wheel_base=0.33, real_world_meters=18.0):
METERS_PER_NORMALIZED_UNIT = real_world_meters / 2.0
k1_left = 0.0203
k2_left = 0.148
k3_left = 0.0014
k4_left = 0.102
k5_left = 0.0017
P_static_left = 0.129
k1_right = 0.0203
k2_right = 0.148
k3_right = 0.0014
k4_right = 0.0949
k5_right = 0.0017
P_static_right = 0.111
if trajs_pos.ndim == 4:
B, N, T, D = trajs_pos.shape
elif trajs_pos.ndim == 3:
trajs_pos = trajs_pos.unsqueeze(0)
B, N, T, D = trajs_pos.shape
squeeze_output = True
else:
raise ValueError(f"Expected trajs_pos to be 3D or 4D, got {trajs_pos.ndim}D")
path_lengths_meters = compute_path_length_from_pos(trajs_pos, real_world_meters=real_world_meters)
delta_t = 1.0
total_time = T * delta_t
v_constant_meters = path_lengths_meters / total_time
dx = torch.diff(trajs_pos[..., 1], dim=2) * METERS_PER_NORMALIZED_UNIT
dy = torch.diff(trajs_pos[..., 0], dim=2) * METERS_PER_NORMALIZED_UNIT
ddx = torch.diff(dx, dim=2)
ddy = torch.diff(dy, dim=2)
dx_mid = (dx[:, :, :-1] + dx[:, :, 1:]) / 2
dy_mid = (dy[:, :, :-1] + dy[:, :, 1:]) / 2
v_const = v_constant_meters.unsqueeze(-1)
denom = (v_const**3).clamp(min=1e-6)
kappa = (dx_mid * ddy - dy_mid * ddx) / denom
omega = v_const.squeeze(-1).unsqueeze(-1) * kappa
omega = torch.nn.functional.pad(omega, (0, 1), value=0)
omega_dot = torch.diff(omega, dim=2) / delta_t
omega_dot = torch.nn.functional.pad(omega_dot, (0, 1), value=0)
energy = torch.zeros(B, N, device=trajs_pos.device)
for t in range(T - 1):
v = v_constant_meters
omega_t = omega[:, :, t]
alpha_t = omega_dot[:, :, t]
theta_dot_left = (2 * v + wheel_base * omega_t) / (2 * wheel_radius)
theta_dot_right = (2 * v - wheel_base * omega_t) / (2 * wheel_radius)
theta_ddot_left = wheel_base * alpha_t / (2 * wheel_radius)
theta_ddot_right = -wheel_base * alpha_t / (2 * wheel_radius)
power_left = (k1_left * theta_ddot_left**2 +
k2_left * theta_dot_left * theta_ddot_left +
k3_left * theta_ddot_left +
k4_left * theta_dot_left**2 +
k5_left * theta_dot_left +
P_static_left)
power_right = (k1_right * theta_ddot_right**2 +
k2_right * theta_dot_right * theta_ddot_right +
k3_right * theta_ddot_right +
k4_right * theta_dot_right**2 +
k5_right * theta_dot_right +
P_static_right)
energy += (power_left + power_right) * delta_t
if 'squeeze_output' in locals() and squeeze_output:
energy = energy.squeeze(0)
return energy
def compute_trajectory_smoothness(trajs_pos, real_world_meters=18.0):
METERS_PER_NORMALIZED_UNIT = real_world_meters / 2.0
if trajs_pos.ndim == 4:
B, N, T, D = trajs_pos.shape
elif trajs_pos.ndim == 3:
trajs_pos = trajs_pos.unsqueeze(0)
B, N, T, D = trajs_pos.shape
squeeze_output = True
else:
raise ValueError(f"Expected trajs_pos to be 3D or 4D, got {trajs_pos.ndim}D")
path_lengths_meters = compute_path_length_from_pos(trajs_pos, real_world_meters=real_world_meters)
v_const = path_lengths_meters / (T - 1)
dx = torch.diff(trajs_pos[..., 1], dim=2) * METERS_PER_NORMALIZED_UNIT
dy = torch.diff(trajs_pos[..., 0], dim=2) * METERS_PER_NORMALIZED_UNIT
ddx = torch.diff(dx, dim=2)
ddy = torch.diff(dy, dim=2)
dx_mid = (dx[:, :, :-1] + dx[:, :, 1:]) / 2
dy_mid = (dy[:, :, :-1] + dy[:, :, 1:]) / 2
denom = (v_const.unsqueeze(-1)**3).clamp(min=1e-6)
kappa = (dx_mid * ddy - dy_mid * ddx) / denom
kappa_diff = torch.diff(kappa, dim=2)
smoothness = torch.mean((kappa_diff**2), dim=2)
if 'squeeze_output' in locals() and squeeze_output:
smoothness = smoothness.squeeze(0)
return smoothness
def compute_safety_margins(trajs_pos, obstacle_tensor, robot_radius=0.02775, img_size=96, real_world_meters=18.0):
METERS_PER_NORMALIZED_UNIT = real_world_meters / 2.0
if trajs_pos.ndim == 4:
B, N, T, D = trajs_pos.shape
elif trajs_pos.ndim == 3:
trajs_pos = trajs_pos.unsqueeze(0)
B, N, T, D = trajs_pos.shape
else:
raise ValueError(f"Expected trajs_pos to be 3D or 4D, got {trajs_pos.ndim}D")
if obstacle_tensor.dim() == 4:
obstacle_tensor = obstacle_tensor.squeeze(1)
min_dist_to_obs = float('inf')
for b in range(B):
obstacle_pixels = torch.nonzero(obstacle_tensor[b], as_tuple=False) # [num_obs, 2] in (row, col) = (y, x)
if obstacle_pixels.numel() == 0:
continue
obs_y_normalized = (obstacle_pixels[:, 0].float() / (img_size - 1)) * 2 - 1
obs_x_normalized = (obstacle_pixels[:, 1].float() / (img_size - 1)) * 2 - 1
obs_normalized = torch.stack([obs_y_normalized, obs_x_normalized], dim=1) # [num_obs, 2] in (y, x)
for t in range(T):
for n in range(N):
robot_pos = trajs_pos[b, n, t]
distances = torch.linalg.norm(obs_normalized - robot_pos.unsqueeze(0), dim=1)
min_idx = distances.argmin()
raw_dist = distances[min_idx].item()
min_dist = raw_dist - robot_radius
min_dist_meters = min_dist * METERS_PER_NORMALIZED_UNIT
if min_dist_meters < min_dist_to_obs:
min_dist_to_obs = min_dist_meters
min_dist_to_robots = float('inf')
for t in range(T):
for b in range(B):
for i in range(N):
for j in range(i+1, N):
dist = torch.linalg.norm(trajs_pos[b, i, t] - trajs_pos[b, j, t]).item()
dist_meters = dist * METERS_PER_NORMALIZED_UNIT
if dist_meters < min_dist_to_robots:
min_dist_to_robots = dist_meters
if min_dist_to_obs == float('inf'):
min_dist_to_obs = 1.0 * METERS_PER_NORMALIZED_UNIT
if min_dist_to_robots == float('inf'):
min_dist_to_robots = 2.0 * METERS_PER_NORMALIZED_UNIT
return min_dist_to_obs, min_dist_to_robots
def get_text_embeds(texts, tokenizer, text_encoder, device):
"""Get CLIP text embeddings"""
tokens = tokenizer(texts, padding="max_length", max_length=10, return_tensors="pt")
with torch.no_grad():
outputs = text_encoder(**tokens)
return outputs.last_hidden_state.to(device)
def collision_mask(states, obstacle):
B, N, _ = states.shape
H, W = obstacle.shape[1], obstacle.shape[2]
mapped_states = ((states + 1) * 47.5).long()
y_coords = mapped_states[:, :, 0]
x_coords = mapped_states[:, :, 1]
y_coords = y_coords.clamp(0, H - 1)
x_coords = x_coords.clamp(0, W - 1)
batch_indices = torch.arange(B, device=states.device).unsqueeze(1).expand(B, N)
collision_mask_tensor = obstacle[batch_indices, y_coords, x_coords]
return collision_mask_tensor.bool()
def visualize_final_trajectories(
trajectories,
background_img,
output_path,
line_width=3,
state_dot_size=14,
start_dot_size=14,
end_dot_size=14,
agent_colors=None
):
if agent_colors is None:
agent_colors = ["red", "green", "blue", "orange", "purple", "cyan", "magenta", "yellow", "brown"]
mapp = background_img.copy()
draw = ImageDraw.Draw(mapp)
B, N, T, _ = trajectories.shape
img_height, img_width = mapp.size
agent_trajectories = trajectories[0].cpu().numpy()
for i in range(N):
color = agent_colors[i % len(agent_colors)]
path_points = agent_trajectories[i]
h_coords = (path_points[:, 0] + 1) / 2 * img_height
w_coords = (path_points[:, 1] + 1) / 2 * img_width
pixel_path = list(zip(w_coords, h_coords))
if len(pixel_path) > 1:
draw.line(pixel_path, fill=color, width=line_width)
start_x, start_y = pixel_path[0]
end_x, end_y = pixel_path[-1]
draw.ellipse(
[(start_x - start_dot_size/2, start_y - start_dot_size/2),
(start_x + start_dot_size/2, start_y + start_dot_size/2)],
fill="white", outline=color, width=2
)
draw.ellipse(
[(end_x - end_dot_size/2, end_y - end_dot_size/2),
(end_x + end_dot_size/2, end_y + end_dot_size/2)],
fill=color, outline="black", width=2
)
mapp.save(output_path)
def create_trajectory_video(
trajectory_history,
background_img,
output_path,
dot_size=10,
fps=50,
repeat_frames=3
):
frames = []
trajectory_reversed = trajectory_history
for x in trajectory_reversed:
frame_img = background_img.copy()
for agent_img in draw_agents_on_map(
agent_positions=x.cpu(),
background_image=frame_img,
dot_size=dot_size
):
frame_array = np.array(agent_img)
frames.append(frame_array)
break
slow_frames = []
for f in frames:
slow_frames.extend([f] * repeat_frames)
imageio.mimsave(output_path, slow_frames, fps=fps, codec='libx264', quality=8)
def load_obstacle_mask_from_config(map_name, img_size, device, config, project_root):
mask_paths = config['mask_paths']
obs_config = config['obstacle_variations']
if map_name == "ood":
csv_path = os.path.join(project_root, mask_paths['ood'])
base_obstacle_tensor = create_mask_from_binary_csv(
csv_path=csv_path, img_size=img_size, device=device
)
return base_obstacle_tensor.unsqueeze(1)
elif map_name == "room":
csv_h_path = os.path.join(project_root, mask_paths['room_horizontal'])
csv_v_path = os.path.join(project_root, mask_paths['room_vertical'])
h_tensor = create_mask_from_binary_csv(csv_h_path, img_size, device)
v_tensor = create_mask_from_binary_csv(csv_v_path, img_size, device)
varied_tensors = apply_room_obstacle_variations_fix_seed(
horizontal_mask_tensor=h_tensor,
vertical_mask_tensor=v_tensor,
num_variations=obs_config['num_variations'],
device=device,
seed=obs_config['seed'],
scale_range=tuple(obs_config['scale_range']),
offset_range_pixels=obs_config['offset_range_pixels']
)
elif map_name == "shelf":
csv_path = os.path.join(project_root, mask_paths['shelf'])
base_obstacle_tensor = create_mask_from_binary_csv(
csv_path=csv_path, img_size=img_size, device=device
)
cropped_obstacle_tensor = base_obstacle_tensor.clone()
shelf_config = obs_config['shelf']
for crop_region in shelf_config['crop_regions']:
y_start, y_end, x_start, x_end = crop_region
cropped_obstacle_tensor[0, y_start:y_end, x_start:x_end] = 0
varied_tensors = apply_shelf_obstacle_variations_fix_seed(
base_obstacle_mask_tensor=cropped_obstacle_tensor,
num_variations=obs_config['num_variations'],
device=device,
seed=obs_config['seed'],
scale_range=tuple(shelf_config['scale_range']),
offset_range_pixels=obs_config['offset_range_pixels'],
min_obstacle_area_ratio=obs_config['min_obstacle_area_ratio'],
num_special_variations=shelf_config['num_special_variations'],
special_vertical_offset=shelf_config['special_vertical_offset']
)
else:
csv_path = os.path.join(project_root, mask_paths[map_name])
base_tensor = create_mask_from_binary_csv(csv_path, img_size, device)
varied_tensors = apply_random_obstacle_variations_fix_seed(
base_obstacle_mask_tensor=base_tensor,
num_variations=obs_config['num_variations'],
device=device,
seed=obs_config['seed'],
scale_range=tuple(obs_config['scale_range']),
offset_range_pixels=obs_config['offset_range_pixels'],
min_obstacle_area_ratio=obs_config['min_obstacle_area_ratio']
)
return varied_tensors
def create_model_from_checkpoint(img_size, device, checkpoint_path, Unet):
class Unet2D(Unet):
def __init__(self, dim, out_dim, dim_mults=(1, 2, 4, 8)):
super().__init__(dim=dim, out_dim=out_dim, dim_mults=dim_mults)
def forward(self, obs, t, prompt):
predicted_fields = super().forward(obs, t, prompt=prompt)
return predicted_fields
model = Unet2D(
dim=img_size,
out_dim=2,
dim_mults=(1, 2, 4, 8),
).to(device)
# Load checkpoint
checkpoint = torch.load(checkpoint_path, map_location=device)
if isinstance(checkpoint, dict) and 'model' in checkpoint:
model.load_state_dict(checkpoint['model'])
else:
model.load_state_dict(checkpoint)
model.eval()
return model
def load_evaluation_config(config_path):
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
return config
def inference_combined(
model, diffusion, obs1, prompt_list, x_T, obstacle_tensor,
frozen, img_size, device, bilinear_interpolate_samples,
config, seed: int = None, use_gaussian: bool = False, step_size: float = 0.025,
use_inter_robot_collision: bool = True
):
if seed is not None:
torch.manual_seed(seed)
inf_config = config['inference']
epsilon = inf_config['epsilon']
k1 = inf_config['k1']
k2 = inf_config['k2']
beta0 = inf_config['beta0']
beta_pow = inf_config['beta_pow']
tau_coll = inf_config['tau_coll']
anneal_it = inf_config['anneal_iterations']
robot_diameter = config['evaluation']['robot_diameter']
T = diffusion.noise_steps
x = x_T.clone()
B, N, D = x.shape
prompts_N = torch.cat(prompt_list, dim=0)
prompts_BN = prompts_N.unsqueeze(0).repeat(B, 1, 1, 1)
prompts_flat = prompts_BN.view(-1, 10, 512)
obs_flat = obs1.repeat_interleave(N, 0)
use_precompute = (N == 3)
if use_precompute:
all_t_steps = torch.arange(1, T + 1, device=device)
t_full_batch = all_t_steps.unsqueeze(1).repeat(1, B*N).view(-1)
obs_full_batch = obs_flat.unsqueeze(0).repeat(T, 1, 1, 1, 1).view(-1, *obs_flat.shape[1:])
prompts_full_batch = prompts_flat.unsqueeze(0).repeat(T, 1, 1, 1).view(-1, *prompts_flat.shape[1:])
all_predicted_fields_flat = model(obs_full_batch, t_full_batch, prompts_full_batch)
field_shape = all_predicted_fields_flat.shape[1:]
all_predicted_fields = all_predicted_fields_flat.view(T, B*N, *field_shape)
trajectory_history = []
for i in reversed(range(1, T+1)):
alpha = epsilon * (diffusion.std[i-1] / diffusion.std[-1])
collision_masks = collision_mask(x, obstacle_tensor).to(device)
movable = ~frozen & ~collision_masks
active_mask = (movable).view(-1)
idx_active = active_mask.nonzero(as_tuple=False).squeeze(-1)
if use_precompute:
fields_for_t_i_all_agents = all_predicted_fields[i-1]
predicted_fields = fields_for_t_i_all_agents[idx_active]
else:
t = (torch.ones(1) * i).long().to(device)
t_flat = t.repeat_interleave(N)
obs_batch = obs_flat[idx_active]
t_batch = t_flat[idx_active]
prompts_batch = prompts_flat[idx_active]
predicted_fields = model(obs_batch, t_batch, prompts_batch)
for j in range(anneal_it):
x_prev = x.clone()
x_flat_all = x.view(-1, 1, D)
x_batch = x_flat_all[idx_active]
sampled_preds = bilinear_interpolate_samples(predicted_fields, x_batch)
score_batch = sampled_preds[..., :2]
score_bn = score_batch.view(-1, 1, D)
scores_flat = torch.zeros_like(x_flat_all)
scores_flat[idx_active, 0, :] = score_bn.view(-1, D)
scores = scores_flat.view(B, N, D)
grad_cost = collision_grad(x_prev, d_min=tau_coll)
guide_w = beta0 * (i / T) ** beta_pow
if use_inter_robot_collision:
scores_hat = scores + guide_w * grad_cost
else:
scores_hat = scores
if not use_gaussian:
drift = (img_size / (2*diffusion.std[i-1])) * scores_hat * alpha**k1
else:
drift = scores_hat * step_size
noise = torch.randn_like(x) * alpha**((k1+k2)/2)
diff_mat = x.unsqueeze(2) - x.unsqueeze(1)
dist_mat = torch.linalg.norm(diff_mat, dim=-1)
collided = (dist_mat < robot_diameter).triu(diagonal=1)
for b in range(B):
pairs = torch.nonzero(collided[b], as_tuple=False)
if pairs.numel() > 0:
agents_involved = torch.unique(pairs)
for n_idx in agents_involved.tolist():
frozen[b, n_idx] = True
movable[b, n_idx] = False
x[movable] = x_prev[movable] + drift[movable] + noise[movable]
if j == 0 or j == (anneal_it // 3) or j == (anneal_it * 2 // 3) or j == (anneal_it - 1):
trajectory_history.append(x.clone().clamp(-0.99, 0.99))
return x, ~movable, trajectory_history