English
DeepChoice / dataset_generation /batch_serialization.py
antoine.carreaud67
Clean and reorganize repository for public release
4a48212
Raw
History Blame Contribute Delete
15.2 kB
import multiprocessing
import os
from pathlib import Path
import h5py
import numpy as np
import torch
from tqdm import tqdm
def _quantize_coords_batch(coords, coord_scale, coord_offset):
coords = np.asarray(coords, dtype=np.float64)
coord_offset = np.asarray(coord_offset, dtype=np.float64)
return np.rint((coords - coord_offset) / coord_scale).astype(np.int32)
def _save_batch(save_path, vis, logits, masks, targets, coords, vmin, vmax, coord_scale, coord_offset, tile_offset=None):
if tile_offset is None:
tile_offset = np.zeros(3, dtype=np.float64)
tile_offset = np.asarray(tile_offset, dtype=np.float64)
coord_offset = np.asarray(coord_offset, dtype=np.float64)
coords_global = np.asarray(coords, dtype=np.float64) + tile_offset
coords_offset_global = coord_offset + tile_offset
batch = {
"visibility": torch.tensor(np.array(vis), dtype=torch.float32),
"logits": torch.tensor(np.array(logits), dtype=torch.float32),
"mask": torch.tensor(np.array(masks), dtype=torch.bool),
"target": torch.tensor(np.array(targets), dtype=torch.long),
"coords_int": torch.tensor(_quantize_coords_batch(coords_global, coord_scale, coords_offset_global), dtype=torch.int32),
"coords_scale": torch.tensor(coord_scale, dtype=torch.float64),
"coords_offset": torch.tensor(coords_offset_global, dtype=torch.float64),
"coords_tile_offset": torch.tensor(tile_offset, dtype=torch.float64),
}
batch["visibility"] = normalize_visibility(batch["visibility"], vmin, vmax)
save_path.parent.mkdir(parents=True, exist_ok=True)
torch.save(batch, save_path, pickle_protocol=4)
def decode_coordinates(dataset, handle):
coords = np.asarray(dataset)
if np.issubdtype(coords.dtype, np.integer) and "coords_scale" in handle.attrs:
scale = float(handle.attrs["coords_scale"])
offset = np.asarray(handle.attrs.get("coords_offset", np.zeros(3, dtype=np.float32)), dtype=np.float32)
return coords.astype(np.float32) * scale + offset
return coords.astype(np.float32)
def decode_visibility(dataset, handle):
visibility = np.asarray(dataset)
if np.issubdtype(visibility.dtype, np.integer) and "visibility_quant_max" in handle.attrs:
quant_max = float(handle.attrs["visibility_quant_max"])
vmin = np.asarray(handle.attrs["visibility_vmin"], dtype=np.float32)
vmax = np.asarray(handle.attrs["visibility_vmax"], dtype=np.float32)
normalized = visibility.astype(np.float32) / max(quant_max, 1.0)
return normalized * (vmax - vmin) + vmin
return visibility.astype(np.float32)
def decode_logits(dataset, handle):
logits = np.asarray(dataset)
if np.issubdtype(logits.dtype, np.integer) and "logits_quant_max" in handle.attrs:
quant_max = float(handle.attrs["logits_quant_max"])
return logits.astype(np.float32) / max(quant_max, 1.0)
return logits.astype(np.float32)
def decode_dense_visibility(visibility, observations):
quant_max = float(observations["visibility_quant_max"])
vmin = np.asarray(observations["visibility_vmin"], dtype=np.float32)
vmax = np.asarray(observations["visibility_vmax"], dtype=np.float32)
normalized = visibility.astype(np.float32) / max(quant_max, 1.0)
return normalized * (vmax - vmin) + vmin
def decode_dense_logits(logits, observations):
quant_max = float(observations["logits_quant_max"])
return logits.astype(np.float32) / max(quant_max, 1.0)
def normalize_visibility(vis, vmin, vmax, eps=1e-8):
mins = torch.tensor(vmin, dtype=torch.float32).view(1, 1, -1)
maxs = torch.tensor(vmax, dtype=torch.float32).view(1, 1, -1)
vis_clamped = torch.max(torch.min(vis, maxs), mins)
return (vis_clamped - mins) / (maxs - mins + eps)
def _pad_views(array, max_views, pad_value=0, dtype=np.float32):
current_views = array.shape[0]
if current_views >= max_views:
return array[:max_views]
pad_shape = (max_views - current_views,) + array.shape[1:]
padding = np.full(pad_shape, pad_value, dtype=dtype)
return np.concatenate([array, padding], axis=0)
def process_h5_file(args):
path, output_dir, vmin, vmax, batch_size, max_views, coord_scale, train, val, test = args
batch_idx = 0
filename_prefix = Path(path).stem
with h5py.File(path, "r") as handle:
points = handle["points"]
keys = list(points.keys())
for start in range(0, len(keys), batch_size):
batch_keys = keys[start : start + batch_size]
vis = []
logits = []
masks = []
targets = []
coords = []
for key in batch_keys:
point = points[key]
visibility = decode_visibility(point["visibility"], handle)
logit_vectors = decode_logits(point["logit_vectors"], handle)
num_views = min(len(visibility), max_views)
vis.append(_pad_views(visibility, max_views, pad_value=0.0, dtype=np.float32))
logits.append(_pad_views(logit_vectors, max_views, pad_value=0.0, dtype=np.float32))
mask = np.zeros(max_views, dtype=bool)
mask[:num_views] = True
masks.append(mask)
targets.append(point["ground_truth"][()])
coords.append(decode_coordinates(point["coordinates"], handle))
if path in train:
save_path = Path(output_dir) / "train" / f"{filename_prefix}_batch_{batch_idx:05d}.pt"
elif path in test:
save_path = Path(output_dir) / "test" / f"{filename_prefix}_batch_{batch_idx:05d}.pt"
else:
save_path = Path(output_dir) / "val" / f"{filename_prefix}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords, dtype=np.float32), axis=0)
_save_batch(save_path, vis, logits, masks, targets, coords, vmin, vmax, coord_scale, coord_offset, np.zeros(3, dtype=np.float64))
batch_idx += 1
return f"{path} done"
def save_tile_observations_to_pt(
cfg,
tile_name,
split_name,
coords_array,
tile_offset,
all_observations,
output_dir,
batch_size=1024,
):
output_dir = Path(output_dir) / split_name
output_dir.mkdir(parents=True, exist_ok=True)
max_views = int(cfg["selection"]["max_views"])
min_views = int(cfg["selection"]["min_views"])
vmin = cfg["data"]["vmin"]
vmax = cfg["data"]["vmax"]
batch_idx = 0
coord_scale = float(cfg.get("storage", {}).get("coord_scale", 0.001))
vis = []
logits = []
masks = []
targets = []
coords = []
saved_paths = []
if isinstance(all_observations, dict) and all_observations.get("mode") == "dense":
counts = all_observations["counts"]
ground_truth = all_observations["ground_truth"]
for point_idx in range(len(counts)):
num_views = int(counts[point_idx])
if num_views < min_views:
continue
gt = int(ground_truth[point_idx])
if gt < 0:
continue
visibility = decode_dense_visibility(all_observations["visibility"][point_idx, :num_views], all_observations)
logit_vectors = decode_dense_logits(all_observations["logit_vectors"][point_idx, :num_views], all_observations)
vis.append(_pad_views(visibility, max_views, pad_value=0.0, dtype=np.float32))
logits.append(_pad_views(logit_vectors, max_views, pad_value=0.0, dtype=np.float32))
mask = np.zeros(max_views, dtype=bool)
mask[:num_views] = True
masks.append(mask)
targets.append(gt)
coords.append(np.asarray(coords_array[point_idx], dtype=np.float32))
if len(vis) == batch_size:
save_path = output_dir / f"{tile_name}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords, dtype=np.float32), axis=0)
_save_batch(save_path, vis, logits, masks, targets, coords, vmin, vmax, coord_scale, coord_offset, tile_offset)
saved_paths.append(str(save_path))
vis, logits, masks, targets, coords = [], [], [], [], []
batch_idx += 1
else:
for point_idx, observations in all_observations.items():
if len(observations["camera"]) < min_views:
continue
gt = observations["ground_truth"]
if gt is None:
continue
visibility = np.asarray(observations["visibility"], dtype=np.float32)
logit_vectors = np.asarray(observations["logit_vectors"], dtype=np.float32)
num_views = min(len(visibility), max_views)
vis.append(_pad_views(visibility, max_views, pad_value=0.0, dtype=np.float32))
logits.append(_pad_views(logit_vectors, max_views, pad_value=0.0, dtype=np.float32))
mask = np.zeros(max_views, dtype=bool)
mask[:num_views] = True
masks.append(mask)
targets.append(gt)
coords.append(np.asarray(coords_array[point_idx], dtype=np.float32))
if len(vis) == batch_size:
save_path = output_dir / f"{tile_name}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords, dtype=np.float32), axis=0)
_save_batch(save_path, vis, logits, masks, targets, coords, vmin, vmax, coord_scale, coord_offset, tile_offset)
saved_paths.append(str(save_path))
vis, logits, masks, targets, coords = [], [], [], [], []
batch_idx += 1
if vis:
save_path = output_dir / f"{tile_name}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords, dtype=np.float32), axis=0)
_save_batch(save_path, vis, logits, masks, targets, coords, vmin, vmax, coord_scale, coord_offset, tile_offset)
saved_paths.append(str(save_path))
return saved_paths
def save_compact_payloads_to_pt(
cfg,
tile_name,
split_name,
coords_array,
tile_offset,
payloads,
output_dir,
batch_size=1024,
):
output_dir = Path(output_dir) / split_name
output_dir.mkdir(parents=True, exist_ok=True)
max_views = int(cfg["selection"]["max_views"])
min_views = int(cfg["selection"]["min_views"])
vmin = cfg["data"]["vmin"]
vmax = cfg["data"]["vmax"]
payloads = [payload for payload in payloads if payload is not None and payload.get("point_indices") is not None]
if not payloads:
return []
point_indices = np.concatenate([payload["point_indices"] for payload in payloads], axis=0)
if point_indices.size == 0:
return []
visibility = np.concatenate([payload["visibility"] for payload in payloads], axis=0)
logits = np.concatenate([payload["logit_vectors"] for payload in payloads], axis=0)
ground_truth = np.concatenate([payload["ground_truth"] for payload in payloads], axis=0)
order = np.argsort(point_indices, kind="mergesort")
point_indices = point_indices[order]
visibility = visibility[order]
logits = logits[order]
ground_truth = ground_truth[order]
quant_max_vis = float(payloads[0]["visibility_quant_max"])
quant_max_logits = float(payloads[0]["logits_quant_max"])
payload_vmin = np.asarray(payloads[0]["visibility_vmin"], dtype=np.float32)
payload_vmax = np.asarray(payloads[0]["visibility_vmax"], dtype=np.float32)
saved_paths = []
vis_batch = []
logits_batch = []
masks_batch = []
targets_batch = []
coords_batch = []
batch_idx = 0
coord_scale = float(cfg.get("storage", {}).get("coord_scale", 0.001))
group_starts = np.r_[0, np.flatnonzero(np.diff(point_indices)) + 1]
group_ends = np.r_[group_starts[1:], point_indices.size]
for start, end in zip(group_starts, group_ends):
point_idx = int(point_indices[start])
num_obs = end - start
if num_obs < min_views:
continue
gt_slice = ground_truth[start:end]
valid_gt = gt_slice[gt_slice >= 0]
if valid_gt.size == 0:
continue
gt = int(valid_gt[0])
local_visibility = visibility[start:end]
if num_obs > max_views:
keep = np.lexsort((local_visibility[:, 0], local_visibility[:, 1]))[:max_views]
local_visibility = local_visibility[keep]
local_logits = logits[start:end][keep]
num_views = max_views
else:
local_logits = logits[start:end]
num_views = num_obs
vis_float = (local_visibility.astype(np.float32) / max(quant_max_vis, 1.0)) * (payload_vmax - payload_vmin) + payload_vmin
logits_float = local_logits.astype(np.float32) / max(quant_max_logits, 1.0)
vis_batch.append(_pad_views(vis_float, max_views, pad_value=0.0, dtype=np.float32))
logits_batch.append(_pad_views(logits_float, max_views, pad_value=0.0, dtype=np.float32))
mask = np.zeros(max_views, dtype=bool)
mask[:num_views] = True
masks_batch.append(mask)
targets_batch.append(gt)
coords_batch.append(np.asarray(coords_array[point_idx], dtype=np.float32))
if len(vis_batch) == batch_size:
save_path = output_dir / f"{tile_name}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords_batch, dtype=np.float32), axis=0)
_save_batch(
save_path,
vis_batch,
logits_batch,
masks_batch,
targets_batch,
coords_batch,
vmin,
vmax,
coord_scale,
coord_offset,
tile_offset,
)
saved_paths.append(str(save_path))
vis_batch, logits_batch, masks_batch, targets_batch, coords_batch = [], [], [], [], []
batch_idx += 1
if vis_batch:
save_path = output_dir / f"{tile_name}_batch_{batch_idx:05d}.pt"
coord_offset = np.min(np.asarray(coords_batch, dtype=np.float32), axis=0)
_save_batch(
save_path,
vis_batch,
logits_batch,
masks_batch,
targets_batch,
coords_batch,
vmin,
vmax,
coord_scale,
coord_offset,
tile_offset,
)
saved_paths.append(str(save_path))
return saved_paths
def save_dataset_to_pt_parallel(cfg, train, val, test, output_dir, batch_size=1024, num_workers=4):
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
max_views = cfg["selection"]["max_views"]
vmin = cfg["data"]["vmin"]
vmax = cfg["data"]["vmax"]
h5_paths = train + val + test
args_list = [
(path, str(output_dir), vmin, vmax, batch_size, max_views, float(cfg.get("storage", {}).get("coord_scale", 0.001)), train, val, test)
for path in h5_paths
]
with multiprocessing.Pool(num_workers) as pool:
for result in pool.imap_unordered(process_h5_file, args_list):
print(result)