As-designed vs as-scanned deviation

As-designed vs as-scanned deviation#

A real reverse-engineering question: a vendor ships a nominal CAD model of a part, and a depth camera reconstructs the part that actually arrived. Where, and by how much, does the as-built surface deviate from design?

This example uses two real models of the same physical object from the T-LESS dataset (Hodaň et al., WACV 2017, CC-BY 4.0): a manually authored CAD model and a model reconstructed from Primesense RGB-D scans. Both are in millimetres in a shared frame. We rigidly align the scan to the CAD nominal, then color the scan by signed distance to the design surface, the first-cut incoming-quality metric.

import numpy as np
import pandas as pd
import pyvista as pv

import pyvista_cad  # noqa: F401  # registers the .cad accessor
from pyvista_cad.examples import downloads

Load the as-designed CAD model and the as-scanned reconstruction.

/home/runner/work/pyvista-cad/pyvista-cad/examples/05_workflows/as_designed_vs_scan.py:31: PyVistaFutureWarning: The default value of `algorithm` for the filter
`PolyData.extract_surface` will change in the future. It currently defaults to
`'dataset_surface'`, but will change to `None`. Explicitly set the `algorithm` keyword to
silence this warning.
  design = pv.read(cad_path).extract_surface()
PolyData (0x7f6fa5f36500)
  N Cells:    25614
  N Points:   12806
  N Strips:   0
  X Bounds:   -4.750e+01, 4.750e+01
  Y Bounds:   -2.675e+01, 2.675e+01
  Z Bounds:   -2.950e+01, 2.950e+01
  N Arrays:   3


The scan carries vertex RGB and normals from the RGB-D reconstruction: real sensor data, not a perturbed copy.

PolyData (0x7f6fa5f341c0)
  N Cells:    42901
  N Points:   21852
  N Strips:   0
  X Bounds:   -4.838e+01, 4.815e+01
  Y Bounds:   -2.843e+01, 2.761e+01
  Z Bounds:   -3.065e+01, 2.919e+01
  N Arrays:   2


Rigidly register the scan onto the CAD nominal. Real scans land in an approximate pose; the deviation is only meaningful after an ICP alignment removes the residual rigid offset.

PolyData (0x7f6fa5f340a0)
  N Cells:    42901
  N Points:   21852
  N Strips:   0
  X Bounds:   -4.838e+01, 4.821e+01
  Y Bounds:   -2.823e+01, 2.808e+01
  Z Bounds:   -3.061e+01, 2.983e+01
  N Arrays:   2


Compute deviation: signed distance from each scan point to the CAD surface.

scan = scan.compute_implicit_distance(design)
dev = scan['implicit_distance']

pd.DataFrame(
    {
        'metric': ['min', 'max', 'mean', 'RMS'],
        'deviation_mm': [
            dev.min(),
            dev.max(),
            dev.mean(),
            np.sqrt((dev**2).mean()),
        ],
    }
)
metric deviation_mm
0 min -11.393228
1 max 4.331330
2 mean -0.385970
3 RMS 2.173232


Side by side: the as-designed CAD on the left, the registered scan colored by signed deviation on the right. Red is proud of nominal, blue is below it; the bulk of the surface is within scanner noise, with clear excursions where the reconstruction is incomplete.

clim = float(np.percentile(np.abs(dev), 95))

pl = pv.Plotter(shape=(1, 2))
pl.subplot(0, 0)
pl.cad.add(design, color='lightgray')
pl.subplot(0, 1)
pl.add_mesh(
    scan,
    scalars='implicit_distance',
    cmap='coolwarm',
    clim=(-clim, clim),
    scalar_bar_args={'title': 'deviation [mm]'},
)
pl.link_views()
pl.show()
as designed vs scan

Total running time of the script: (0 minutes 0.969 seconds)