Source code for visionsim.cli.dataset

from __future__ import annotations

import functools
import shutil
from pathlib import Path

import numpy as np


[docs] def convert( input_dir: Path, output_dir: Path | None = None, force: bool = False, ) -> None: """Convert a ``.db`` database to a ``.json`` or vice-versa. Args: input_dir: directory in which to look for dataset output_dir: directory in which to save new dataset. If not set, save new metadata file in same directory, otherwise copy over all data to a new directory. force: if true, overwrite output file(s) if present """ from visionsim.dataset import Metadata if output_dir: if input_dir.resolve() == output_dir.resolve(): raise RuntimeError("Input and output directory cannot be the same!") if output_dir.exists() and not force: raise FileExistsError("Output directory already exists.") else: shutil.rmtree(output_dir, ignore_errors=True) meta = Metadata.from_path(input_dir) assert meta.path is not None rel_path = meta.path.relative_to(input_dir.resolve()) meta_path = rel_path.with_suffix(".db" if meta.path.suffix == ".json" else ".json") if output_dir: shutil.copytree(input_dir, output_dir) meta.save(output_dir / meta_path) (output_dir / rel_path).unlink(missing_ok=True) else: meta.save(input_dir / meta_path)
[docs] def merge(input_files: list[Path], names: list[str] | None = None, output_file: Path = Path("combined.json")) -> None: """Merge one or more dataset files. Typically there will be dataset file per data type (frames, depth, etc) but these can be combined if they are compatible (same number of frames, same camera, etc) to yield Nerfstudio-compatible "transform.json" files that might have "depth_file_path" or "mask_path" in addition to a "file_path". This does not touch the underlying data, only modifies the transforms files. This can be used to rename a data type for a single file, merge multiple metadata files that already have distinct data type names, or merge and rename many metadata files altogether. Args: input_files (list[Path]): List of datasets to merge, can either be the path of a metadata file or it's directory. names (list[str] | None, optional): What to rename each "path" argument to. Defaults to None. output_file (Path, optional): Where to save metadata file, should be a ``.json`` file. Defaults to "combined.json". """ import more_itertools as mitertools from visionsim.dataset import Metadata metas = [Metadata.from_path(p) for p in input_files] data_types = [dt for m in metas for dt in m.data_types] if set(len(m.cameras or []) for m in metas) != {1}: raise ValueError("Cannot merge datasets that have multiple cameras.") if len(set(cam.model_copy(update=dict(c=None)) for m in metas for cam in (m.cameras or []))) != 1: # Allow merge if data types have different number of channels raise ValueError("Cannot merge datasets that have different cameras.") if len(set(len(m) for m in metas)) != 1: raise ValueError("Datasets cannot be merged as they are not the same size.") if names is None: if len(set(data_types)) != len(data_types): raise ValueError(f"Data types must be unique, got {data_types}.") # Disable renaming of data types since they are unique and there might be many per metadata file data_types = [None] * len(input_files) # type: ignore names = [None] * len(input_files) # type: ignore else: if len(names) != len(input_files): raise ValueError( f"Expected as many names as input files, got {len(names)} and {len(input_files)} respectively." ) if len(input_files) != len(data_types): raise ValueError("Cannot rename data types when multiple types exist in a file.") def merge_transform(transforms): # We already checked that cameras are the same, datasets are the same length, and (renamed) data # types are unique, so we check for poses and per-frame args then just merge dicts if any( not np.allclose(a["transform_matrix"], b["transform_matrix"]) for a, b in mitertools.pairwise(transforms) ): raise ValueError("Frames in datasets do not share a common pose.") if any(t.get("offset") or t.get("bitpack_dim") for t in transforms): raise ValueError("Cannot merge datasets that use additional per-frame attributes.") return functools.reduce(lambda a, b: a | b, transforms) merged_transforms = [ merge_transform(transforms) for transforms in zip( *[ m.iter_dense_transforms(data_type=dt, rename_to=name, relative_to=output_file.resolve().parent) for m, dt, name in zip(metas, data_types, names) ] ) ] Metadata.from_dense_transforms(merged_transforms).save(output_file)