Files
marf/ifield/cli_utils.py
2025-01-09 15:43:11 +01:00

175 lines
5.8 KiB
Python

#!/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)