#!/usr/bin/env python3 from .data.common.scan import SingleViewScan, SingleViewUVScan from datetime import datetime import re import click import gzip import h5py as h5 import matplotlib.pyplot as plt import numpy as np import pyrender import trimesh import trimesh.transformations as T __doc__ = """ Here are a bunch of helper scripts exposed as cli command by poetry """ # these entrypoints are exposed by poetry as shell commands @click.command() @click.argument("h5file") @click.argument("key", default="") def show_h5_items(h5file: str, key: str): "Show contents of HDF5 dataset" f = h5.File(h5file, "r") if not key: mlen = max(map(len, f.keys())) for i in sorted(f.keys()): print(i.ljust(mlen), ":", str (f[i].dtype).ljust(10), repr(f[i].shape).ljust(16), "mean:", f[i][:].mean() ) else: if not f[key].shape: print(f[key].value) else: print(f[key][:]) @click.command() @click.argument("h5file") @click.argument("key", default="") def show_h5_img(h5file: str, key: str): "Show a 2D HDF5 dataset as an image" f = h5.File(h5file, "r") if not key: mlen = max(map(len, f.keys())) for i in sorted(f.keys()): print(i.ljust(mlen), ":", str(f[i].dtype).ljust(10), f[i].shape) else: plt.imshow(f[key]) plt.show() @click.command() @click.argument("h5file") @click.option("--force-distances", is_flag=True, help="Always show miss distances.") @click.option("--uv", is_flag=True, help="Load as UV scan cloud and convert it.") @click.option("--show-unit-sphere", is_flag=True, help="Show the unit sphere.") @click.option("--missing", is_flag=True, help="Show miss points that are not hits nor misses as purple.") def show_h5_scan_cloud( h5file : str, force_distances : bool = False, uv : bool = False, missing : bool = False, show_unit_sphere = False, ): "Show a SingleViewScan HDF5 dataset" print("Reading data...") t = datetime.now() if uv: scan = SingleViewUVScan.from_h5_file(h5file) if missing and scan.any_missing: if not scan.has_missing: scan.fill_missing_points() points_missing = scan.points[scan.missing] else: missing = False if not scan.is_single_view: scan.cam_pos = None scan = scan.to_scan() else: scan = SingleViewScan.from_h5_file(h5file) if missing: uvscan = scan.to_uv_scan() if scan.any_missing: uvscan.fill_missing_points() points_missing = uvscan.points[uvscan.missing] else: missing = False print("loadtime: ", datetime.now() - t) if force_distances and not scan.has_miss_distances: print("Computing miss distances...") scan.compute_miss_distances() use_miss_distances = force_distances print("Constructing scene...") if not scan.has_colors: scan.colors_hit = np.zeros_like(scan.points_hit) scan.colors_miss = np.zeros_like(scan.points_miss) scan.colors_hit [:] = ( 31/255, 119/255, 180/255) scan.colors_miss[:] = (243/255, 156/255, 18/255) use_miss_distances = True if scan.has_miss_distances and use_miss_distances: sdm = scan.distances_miss / scan.distances_miss.max() sdm = sdm[..., None] scan.colors_miss \ = np.array([0.8, 0, 0])[None, :] * sdm \ + np.array([0, 1, 0.2])[None, :] * (1-sdm) scene = pyrender.Scene() scene.add(pyrender.Mesh.from_points(scan.points_hit, colors=scan.colors_hit, normals=scan.normals_hit)) scene.add(pyrender.Mesh.from_points(scan.points_miss, colors=scan.colors_miss)) if missing: scene.add(pyrender.Mesh.from_points(points_missing, colors=(np.array((0xff, 0x00, 0xff))/255)[None, :].repeat(points_missing.shape[0], axis=0))) # camera: if not scan.points_cam is None: camera_mesh = trimesh.creation.uv_sphere(radius=scan.points_hit_std.max()*0.2) camera_mesh.visual.vertex_colors = [0.0, 0.8, 0.0] tfs = np.tile(np.eye(4), (len(scan.points_cam), 1, 1)) tfs[:,:3,3] = scan.points_cam scene.add(pyrender.Mesh.from_trimesh(camera_mesh, poses=tfs)) # UV sphere: if show_unit_sphere: unit_sphere_mesh = trimesh.creation.uv_sphere(radius=1) unit_sphere_mesh.invert() unit_sphere_mesh.visual.vertex_colors = [0.8, 0.8, 0.0] scene.add(pyrender.Mesh.from_trimesh(unit_sphere_mesh, poses=np.eye(4)[None, ...])) print("Launch!") viewer = pyrender.Viewer(scene, use_raymond_lighting=True, point_size=2) @click.command() @click.argument("meshfile") @click.option('--aabb', is_flag=True) @click.option('--z-skyward', is_flag=True) def show_model( meshfile : str, aabb : bool, z_skyward : bool, ): "Show a 3D model with pyrender, supports .gz suffix" if meshfile.endswith(".gz"): with gzip.open(meshfile, "r") as f: mesh = trimesh.load(f, file_type=meshfile.split(".", 1)[1].removesuffix(".gz")) else: mesh = trimesh.load(meshfile) if isinstance(mesh, trimesh.Scene): mesh = mesh.dump(concatenate=True) if aabb: from .data.common.mesh import rotate_to_closest_axis_aligned_bounds mesh.apply_transform(rotate_to_closest_axis_aligned_bounds(mesh, fail_ok=True)) if z_skyward: mesh.apply_transform(T.rotation_matrix(np.pi/2, (1, 0, 0))) print( *(i.strip() for i in pyrender.Viewer.__doc__.splitlines() if re.search(r"- ``[a-z0-9]``: ", i)), sep="\n" ) scene = pyrender.Scene() scene.add(pyrender.Mesh.from_trimesh(mesh)) pyrender.Viewer(scene, use_raymond_lighting=True)