Source code for pimm.datasets.utils

"""Collation helpers for ragged point-cloud batches.

Dataset items are flat dictionaries with per-point arrays and one-sample
``offset`` tensors. These helpers concatenate ragged fields and convert offsets
to cumulative batch offsets expected by Pointcept/pimm model heads.

Author: Xiaoyang Wu (xiaoyang.wu.cs@gmail.com)
Please cite our work if the code is helpful to you.
"""

import random
from collections.abc import Mapping, Sequence
import numpy as np
import torch
from torch.utils.data.dataloader import default_collate


[docs] def collate_fn(batch, mix_prob=0): """Recursively collate tensors, mappings, and list-style point samples. Tensor leaves are concatenated instead of stacked. Keys containing ``offset`` are treated as per-sample lengths and converted into cumulative offsets after concatenation. """ if not isinstance(batch, Sequence): raise TypeError(f"{batch.dtype} is not supported.") if isinstance(batch[0], torch.Tensor): return torch.cat(list(batch)) elif isinstance(batch[0], str): # str is also a kind of Sequence, judgement should before Sequence return list(batch) elif isinstance(batch[0], Sequence): for data in batch: data.append(torch.tensor([data[0].shape[0]])) batch = [collate_fn(samples) for samples in zip(*batch)] batch[-1] = torch.cumsum(batch[-1], dim=0).int() return batch elif isinstance(batch[0], Mapping): batch = { key: ( collate_fn([d[key] for d in batch]) if "offset" not in key # offset -> bincount -> concat bincount-> concat offset else torch.cumsum( collate_fn([d[key].diff(prepend=torch.tensor([0])) for d in batch]), dim=0, ) ) for key in batch[0] if not key.startswith("_") # skip non-tensor metadata } return batch else: return default_collate(batch)
def point_collate_fn(batch, mix_prob=0): """Collate point-cloud dictionaries and optionally apply pairwise mixup.""" assert isinstance( batch[0], Mapping ) # currently, only support input_dict, rather than input_list batch = collate_fn(batch) if random.random() < mix_prob: if "instance" in batch.keys(): offset = batch["offset"] start = 0 num_instance = 0 for i in range(len(offset)): if i % 2 == 0: num_instance = max(batch["instance"][start : offset[i]]) if i % 2 != 0: mask = batch["instance"][start : offset[i]] != -1 batch["instance"][start : offset[i]] += num_instance * mask start = offset[i] if "offset" in batch.keys(): batch["offset"] = torch.cat( [batch["offset"][1:-1:2], batch["offset"][-1].unsqueeze(0)], dim=0 ) return batch def gaussian_kernel(dist2: np.array, a: float = 1, c: float = 5): """Evaluate a scalar Gaussian kernel for squared distances.""" return a * np.exp(-dist2 / (2 * c**2)) def inseg_collate_fn(batch, mix_prob=0): """Collate instance-segmentation query lists into one flattened batch. Each incoming item is a list of dictionaries, one per query/candidate. """ assert isinstance( batch[0], list ) # currently, only support input_dict, rather than input_list assert isinstance( batch[0][0], Mapping ) # currently, only support input_dict, rather than input_list # Flatten the batch - each item is already a list of dictionaries flattened_batch = [] for item_list in batch: flattened_batch.extend(item_list) # Use the original collate function on the flattened batch return collate_fn(flattened_batch)