tarzanagh's picture
Add files using upload-large-folder tool
c35fc6b verified
Raw
History Blame Contribute Delete
18.8 kB
import numpy as np
import torch
from PIL import Image, ImageDraw
from typing import Optional, Union
import random
import pandas as pd
from PIL import Image
from scipy.ndimage import label, find_objects
from typing import Optional
def draw_obs(bg_img, mask):
assert isinstance(bg_img, Image.Image)
img = bg_img.copy()
draw = ImageDraw.Draw(img)
bg_width, bg_height = img.size
mask_height, mask_width = mask.shape
x_scale = bg_width / mask_width
y_scale = bg_height / mask_height
for y in range(mask_height):
for x in range(mask_width):
if mask[y, x]:
scaled_x = round(x * x_scale)
scaled_y = round(y * y_scale)
draw.rectangle([(scaled_x, scaled_y),
(scaled_x + round(x_scale), scaled_y + round(y_scale))], fill='black')
return img
def draw_obstacles_pixel(bg, obstacle_masks):
batch_size, _, _ = obstacle_masks.shape
batched_images = []
for i in range(batch_size):
background = bg.copy()
batched_images.append(draw_obs(background, obstacle_masks[i]))
return batched_images
def create_mask_from_binary_csv(csv_path, img_size, device):
try:
original_mask = pd.read_csv(csv_path, header=None).to_numpy()
except FileNotFoundError:
print(f"Error: The file {csv_path} was not found.")
return None
except Exception as e:
print(f"Error reading or converting CSV file: {e}")
return None
mask_image = Image.fromarray((original_mask.astype(np.uint8) * 255), 'L')
resized_image = mask_image.resize((img_size, img_size), Image.NEAREST)
resized_mask_np = np.array(resized_image)
final_mask = resized_mask_np / 255.0
obstacle_mask_tensor = torch.tensor(
final_mask, dtype=torch.float32, device=device
).unsqueeze(0)
return obstacle_mask_tensor
def apply_random_obstacle_variations_fix_seed(
base_obstacle_mask_tensor: torch.Tensor,
num_variations: int,
device: torch.device,
seed: Optional[int] = None,
scale_range: tuple = (0.9, 1.1),
offset_range_pixels: int = 5,
min_obstacle_area_ratio: float = 0.01,
min_dist_between_obstacles: int = 0
) -> torch.Tensor:
original_rng_state = None
if seed is not None:
original_rng_state = np.random.get_state()
np.random.seed(seed)
H, W = base_obstacle_mask_tensor.shape[1:]
base_mask_np = base_obstacle_mask_tensor.squeeze(0).cpu().numpy().astype(np.uint8)
labeled_array, num_features = label(base_mask_np)
if num_features == 0:
print("No obstacles found in the base mask. Returning original mask.")
if seed is not None:
np.random.set_state(original_rng_state)
return base_obstacle_mask_tensor.repeat(num_variations, 1, 1)
slices = find_objects(labeled_array)
variant_masks_list = [base_mask_np]
for _ in range(num_variations - 1):
current_variant_mask = np.zeros_like(base_mask_np, dtype=np.uint8)
for i, slc in enumerate(slices):
if slc is None:
continue
y_slice, x_slice = slc
obstacle_component = base_mask_np[y_slice, x_slice]
scale = np.random.uniform(scale_range[0], scale_range[1])
orig_h_comp, orig_w_comp = obstacle_component.shape
new_h_comp, new_w_comp = int(orig_h_comp * scale), int(orig_w_comp * scale)
if new_h_comp * new_w_comp < H * W * min_obstacle_area_ratio:
new_h_comp = max(1, int(np.sqrt(H * W * min_obstacle_area_ratio)))
new_w_comp = max(1, int(np.sqrt(H * W * min_obstacle_area_ratio)))
pil_comp = Image.fromarray(obstacle_component * 255, 'L')
scaled_comp_pil = pil_comp.resize((new_w_comp, new_h_comp), Image.NEAREST)
scaled_comp = np.array(scaled_comp_pil) > 0
center_y = y_slice.start + orig_h_comp // 2
center_x = x_slice.start + orig_w_comp // 2
offset_y = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
offset_x = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
new_center_y = center_y + offset_y
new_center_x = center_x + offset_x
new_y_start = new_center_y - new_h_comp // 2
new_x_start = new_center_x - new_w_comp // 2
new_y_start = max(0, min(new_y_start, H - new_h_comp))
new_x_start = max(0, min(new_x_start, W - new_w_comp))
new_y_end = new_y_start + new_h_comp
new_x_end = new_x_start + new_w_comp
current_variant_mask[new_y_start:new_y_end, new_x_start:new_x_end] = scaled_comp
variant_masks_list.append(current_variant_mask)
if seed is not None:
np.random.set_state(original_rng_state)
variant_masks_tensor = torch.tensor(
np.stack(variant_masks_list), dtype=torch.float32, device=device
)
return variant_masks_tensor
def apply_shelf_obstacle_variations_fix_seed(
base_obstacle_mask_tensor: torch.Tensor,
num_variations: int,
device: torch.device,
seed: Optional[int] = None,
scale_range: tuple = (0.9, 1.1),
offset_range_pixels: int = 5,
min_obstacle_area_ratio: float = 0.01,
num_special_variations: int = 0,
special_vertical_offset: int = 20
) -> torch.Tensor:
"""
Apply random variations to shelf obstacle mask with special variations
Args:
base_obstacle_mask_tensor: Base obstacle mask [1, H, W]
num_variations: Number of variations to generate
device: Torch device
seed: Random seed for reproducibility
scale_range: Range for scaling obstacles
offset_range_pixels: Maximum pixel offset for obstacles
min_obstacle_area_ratio: Minimum area ratio for obstacles
num_special_variations: Number of special variations with vertical offset only
special_vertical_offset: Vertical offset for special variations
Returns:
Varied obstacle masks [num_variations, 1, H, W]
"""
original_rng_state = None
if seed is not None:
original_rng_state = np.random.get_state()
np.random.seed(seed)
H, W = base_obstacle_mask_tensor.shape[1:]
base_mask_np = base_obstacle_mask_tensor.squeeze(0).cpu().numpy().astype(np.uint8)
labeled_array, num_features = label(base_mask_np)
if num_features == 0:
print("No obstacles found in the base mask. Returning original mask.")
if seed is not None:
np.random.set_state(original_rng_state)
return base_obstacle_mask_tensor.repeat(num_variations, 1, 1, 1)
slices = find_objects(labeled_array)
variant_masks_list = []
variant_masks_list.append(base_mask_np)
for i in range(num_variations - 1):
current_variation_index = i + 1
is_special = (current_variation_index >= (num_variations - num_special_variations))
current_variant_mask = np.zeros_like(base_mask_np, dtype=np.uint8)
transformed_obstacle_coords = []
for j, slc in enumerate(slices):
y_slice, x_slice = slc
obstacle_component = base_mask_np[y_slice, x_slice]
scale = np.random.uniform(scale_range[0], scale_range[1])
orig_h_comp, orig_w_comp = obstacle_component.shape
new_h_comp, new_w_comp = int(orig_h_comp * scale), int(orig_w_comp * scale)
if new_h_comp * new_w_comp < H * W * min_obstacle_area_ratio:
new_h_comp = max(1, int(np.sqrt(H*W*min_obstacle_area_ratio)))
new_w_comp = max(1, int(np.sqrt(H*W*min_obstacle_area_ratio)))
pil_comp = Image.fromarray(obstacle_component * 255, 'L')
scaled_comp_pil = pil_comp.resize((new_w_comp, new_h_comp), Image.NEAREST)
scaled_comp = np.array(scaled_comp_pil) > 0
center_y = y_slice.start + orig_h_comp // 2
center_x = x_slice.start + orig_w_comp // 2
if is_special:
offset_x = 0
offset_y = np.random.choice([-special_vertical_offset, special_vertical_offset])
else:
offset_y = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
offset_x = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
new_center_y = center_y + offset_y
new_center_x = center_x + offset_x
new_y_start = new_center_y - new_h_comp // 2
new_x_start = new_center_x - new_w_comp // 2
new_y_start = max(0, min(new_y_start, H - new_h_comp))
new_x_start = max(0, min(new_x_start, W - new_w_comp))
new_y_end = new_y_start + new_h_comp
new_x_end = new_x_start + new_w_comp
for prev_y_slice, prev_x_slice in transformed_obstacle_coords:
if (max(new_y_start, prev_y_slice.start) < min(new_y_end, prev_y_slice.stop) and
max(new_x_start, prev_x_slice.start) < min(new_x_end, prev_x_slice.stop)):
pass
current_variant_mask[new_y_start:new_y_end, new_x_start:new_x_end] |= scaled_comp
transformed_obstacle_coords.append(
(slice(new_y_start, new_y_end), slice(new_x_start, new_x_end))
)
variant_masks_list.append(current_variant_mask)
final_variant_masks = torch.tensor(
np.stack(variant_masks_list), dtype=torch.float32, device=device
).unsqueeze(1)
if seed is not None:
np.random.set_state(original_rng_state)
return final_variant_masks
def apply_room_obstacle_variations_fix_seed(
horizontal_mask_tensor: torch.Tensor,
vertical_mask_tensor: torch.Tensor,
num_variations: int,
device: torch.device,
seed: Optional[int] = None,
scale_range: tuple = (0.9, 1.1),
offset_range_pixels: int = 5,
min_obstacle_area_ratio: float = 0.01
) -> torch.Tensor:
original_rng_state = None
if seed is not None:
original_rng_state = np.random.get_state()
np.random.seed(seed)
H, W = horizontal_mask_tensor.shape[1:]
h_mask_np = horizontal_mask_tensor.squeeze(0).cpu().numpy().astype(np.uint8)
v_mask_np = vertical_mask_tensor.squeeze(0).cpu().numpy().astype(np.uint8)
variant_masks_list = []
original_plus_mask = np.logical_or(h_mask_np, v_mask_np).astype(np.uint8)
variant_masks_list.append(original_plus_mask)
for _ in range(num_variations - 1):
is_horizontal = False
if np.random.rand() < 0.5:
mask_to_transform = h_mask_np
static_mask = v_mask_np
is_horizontal = True
else:
mask_to_transform = v_mask_np
static_mask = h_mask_np
is_horizontal = False
labeled_array, num_features = label(mask_to_transform)
if num_features == 0:
variant_masks_list.append(static_mask)
continue
slices = find_objects(labeled_array)
slc = slices[0]
y_slice, x_slice = slc
obstacle_component = mask_to_transform[y_slice, x_slice]
scale = np.random.uniform(scale_range[0], scale_range[1])
orig_h_comp, orig_w_comp = obstacle_component.shape
new_h_comp, new_w_comp = int(orig_h_comp * scale), int(orig_w_comp * scale)
if new_h_comp * new_w_comp < H * W * min_obstacle_area_ratio:
new_h_comp = max(1, int(np.sqrt(H*W*min_obstacle_area_ratio)))
new_w_comp = max(1, int(np.sqrt(H*W*min_obstacle_area_ratio)))
pil_comp = Image.fromarray(obstacle_component * 255, 'L')
scaled_comp_pil = pil_comp.resize((new_w_comp, new_h_comp), Image.NEAREST)
scaled_comp = np.array(scaled_comp_pil) > 0
center_y = y_slice.start + orig_h_comp // 2
center_x = x_slice.start + orig_w_comp // 2
offset_y = 0
offset_x = 0
if is_horizontal:
offset_y = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
else:
offset_x = np.random.randint(-offset_range_pixels, offset_range_pixels + 1)
new_center_y = center_y + offset_y
new_center_x = center_x + offset_x
new_y_start = new_center_y - new_h_comp // 2
new_x_start = new_center_x - new_w_comp // 2
new_y_start = max(0, min(new_y_start, H - new_h_comp))
new_x_start = max(0, min(new_x_start, W - new_w_comp))
new_y_end = new_y_start + new_h_comp
new_x_end = new_x_start + new_w_comp
varied_bar_mask = np.zeros_like(h_mask_np, dtype=np.uint8)
varied_bar_mask[new_y_start:new_y_end, new_x_start:new_x_end] |= scaled_comp
final_mask = np.logical_or(varied_bar_mask, static_mask).astype(np.uint8)
variant_masks_list.append(final_mask)
final_variant_masks = torch.tensor(
np.stack(variant_masks_list), dtype=torch.float32, device=device
).unsqueeze(1)
if seed is not None:
np.random.set_state(original_rng_state)
return final_variant_masks
def is_valid_obstacles(mask, x_start, x_size, y_start, y_size, min_distance):
x_end, y_end = x_start + x_size, y_start + y_size
x_safe_start = max(0, x_start - min_distance)
y_safe_start = max(0, y_start - min_distance)
x_safe_end = min(mask.shape[0], x_end + min_distance)
y_safe_end = min(mask.shape[1], y_end + min_distance)
if torch.sum(mask[x_safe_start:x_safe_end, y_safe_start:y_safe_end]) > 0:
return False
return True
def randgen_obstacle_mask(batch_size, image_size, seed:Optional[int]=None, device='cuda'):
if seed is not None:
torch.manual_seed(seed)
random.seed(seed)
min_area_ratio = 0.07
max_area_ratio = 0.3
random_ratio = random.uniform(min_area_ratio, max_area_ratio)
total_obstacle_area = int(image_size**2 * random_ratio)
max_length = int(image_size*5/6)
min_length = int(image_size*2/15)
min_distance = int(image_size/15)
max_attempts = 50
masks = torch.zeros(batch_size, image_size, image_size, dtype=torch.bool, device=device)
for i in range(batch_size):
num_obstacles = torch.randint(2, 5, (1,)).item()
areas = torch.tensor(total_obstacle_area // num_obstacles, device=device).repeat(num_obstacles)
for area in areas:
attempts = 0
while attempts < max_attempts:
possible_x_sizes = [x for x in range(min_length, max_length + 1) if min_length <= (area // x) <= max_length]
if not possible_x_sizes:
area = (area * 0.8).long()
attempts += 1
continue
x_size = random.choice(possible_x_sizes)
y_size = area // x_size
x_start = torch.randint(0, image_size - x_size + 1, (1,)).item()
y_start = torch.randint(0, image_size - y_size + 1, (1,)).item()
if is_valid_obstacles(masks[i], x_start, x_size, y_start, y_size, min_distance):
masks[i, x_start:x_start + x_size, y_start:y_start + y_size] = 1
break
attempts += 1
return masks
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: str = 'cuda',
obstacle_clearance_pixels: int = 0
):
if seed is not None:
torch.manual_seed(seed)
assert len(bounds) == 4, f'Inappropriate 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 overlay_goals(img, img_size, objs, pos, goal_size):
assert pos.dim() == 3
n = len(objs)
if len(img) != pos.shape[0]:
img = img * pos.shape[0]
if img[0].height != img_size:
new_size = (img_size, img_size)
for i in range(len(img)):
img[i] = img[i].resize(new_size, Image.LANCZOS)
W, H = img[0].size
objs = [img.copy() for img in objs]
for i in range(len(objs)):
objs[i] = objs[i].resize((W // goal_size, H // goal_size), Image.LANCZOS)
pos_pix = ((1 + pos) / 2 * torch.tensor([H - 1, W - 1], device=pos.device, dtype=pos.dtype))
imgs = []
for i, ps in enumerate(pos_pix.cpu().numpy()):
bg = img[i].copy()
for j, p in enumerate(ps):
p1, p0 = round(p[1]), round(p[0])
w, h = objs[j].size
bg.paste(objs[j], (p1 - w // 2, p0 - h // 2), objs[j])
img_np = np.array(bg)[..., :3] / 255
imgs.append(img_np)
return torch.tensor(imgs, dtype=pos.dtype, device=pos.device).permute(0, 3, 1, 2)