Shape Signatures#

Pose-invariant shape representations for comparing and classifying 3D shapes based on statistical shape distributions.

Note

Prerequisites: Basic understanding of 3D meshes and shape geometry

Shape Signatures Module#

The Shape Signatures module provides shape distribution signatures for comparing and classifying 3D shapes. It implements the methods described in the paper “Shape Distributions” by Osada et al. (2002).


Overview#

Shape signatures are compact representations of 3D geometry that enable:

  • Shape comparison: Measure similarity between two shapes

  • Shape classification: Group similar shapes together

  • Shape retrieval: Find shapes similar to a query shape

  • Pose-invariant matching: Compare shapes regardless of position/orientation

The key insight is that certain geometric measurements (distances, angles, areas) sampled from a shape’s surface form characteristic distributions that are:

  • Rotation invariant: Same distribution regardless of orientation

  • Translation invariant: Same distribution regardless of position

  • Scale normalizable: Can be normalized for scale independence


Signature Types#

The module implements five signature types from the Osada et al. paper:

Signature

Measurement

Description

A3Signature

Angle

Angle formed by three random surface points

D1Signature

Distance

Distance from centroid to random surface point

D2Signature

Distance

Distance between two random surface points

D3Signature

Area

Square root of triangle area (three random points)

D4Signature

Volume

Cube root of tetrahedron volume (four random points)

Choosing a Signature Type#

  • D2Signature: Best general-purpose choice, good discrimination

  • A3Signature: Good for shapes with distinctive angular features

  • D1Signature: Simple, fast, but less discriminative

  • D3Signature: Good for surface-dominated shapes

  • D4Signature: Best for volumetric shapes, but requires more points


Class: Signature#

Description#

Base class for all shape signatures. Subclasses implement specific measurement methods.

Constructor#

from volmdlr_tools.shapes.utils.signature import D2Signature

signature = D2Signature(
    shape_distribution: list[float],
    name: str = ""
)

Parameters:

  • shape_distribution: Pre-computed distribution values

  • name: Instance name


Creating Signatures#

From a VolumeModel#

from volmdlr.model import VolumeModel
from volmdlr_tools.shapes.utils.signature import D2Signature

# Load shape
volume_model = VolumeModel.from_step("part.step")

# Create signature
signature = D2Signature.from_volume_model(
    volume_model,
    meshing_tolerance=0.001,
    meshing_angular_tolerance=0.5,
    n_points=1024**2  # ~1 million sample points
)

From a Mesh#

from volmdlr_tools.shapes.utils.signature import D2Signature

# Create signature from existing mesh
signature = D2Signature.from_mesh(
    mesh=mesh,
    n_points=1024**2
)

From Point Cloud#

import numpy as np
from volmdlr_tools.shapes.utils.signature import D2Signature

# Create signature from point cloud
points = np.array([...])  # Nx3 array of surface points
signature = D2Signature.from_points(points)

Computing Similarity#

Basic Similarity#

# Create signatures for two shapes
sig1 = D2Signature.from_volume_model(model1)
sig2 = D2Signature.from_volume_model(model2)

# Compute similarity (0 to 1, higher = more similar)
similarity = sig1.similarity(sig2, n_bins=1024)
print(f"Similarity: {similarity:.4f}")

Histogram Bins#

The n_bins parameter controls histogram resolution:

# Higher bins = more precise but sensitive to noise
high_res_similarity = sig1.similarity(sig2, n_bins=2048)

# Lower bins = more robust but less discriminative
low_res_similarity = sig1.similarity(sig2, n_bins=256)

Normalization#

For scale-independent comparison:

# Normalize the signature (subtract mean)
normalized_sig = signature.normalization()

# Compare normalized signatures
similarity = normalized_sig1.similarity(normalized_sig2)

Visualization#

Shape Distribution Graph#

# Display the shape distribution histogram
graph = signature.graph(n_bins=1024)

# Or use plot_data view decorator
signature.plot()  # Opens interactive plot

Complete Example#

from pathlib import Path
from volmdlr.model import VolumeModel
from volmdlr_tools.shapes.utils.signature import D2Signature, A3Signature

# Load multiple shapes
shapes_folder = Path("data/step/")
shape_files = list(shapes_folder.glob("*.step"))

# Compute signatures for all shapes
signatures = {}
for shape_file in shape_files:
    model = VolumeModel.from_step(str(shape_file))
    sig = D2Signature.from_volume_model(model, n_points=100000)
    signatures[shape_file.stem] = sig

# Find similar shapes
query_name = "bolt_m6"
query_sig = signatures[query_name]

print(f"Shapes similar to {query_name}:")
for name, sig in signatures.items():
    if name != query_name:
        similarity = query_sig.similarity(sig)
        if similarity > 0.8:
            print(f"  {name}: {similarity:.4f}")

Signature Classes#

A3Signature - Angle Distribution#

Measures angles formed by triplets of random surface points.

from volmdlr_tools.shapes.utils.signature import A3Signature

sig = A3Signature.from_volume_model(model)

Best for: Shapes with distinctive angular features (corners, edges)

D1Signature - Centroid Distance#

Measures distance from the shape centroid to random surface points.

from volmdlr_tools.shapes.utils.signature import D1Signature

sig = D1Signature.from_volume_model(model)

Best for: Simple, fast comparison; shapes with distinctive radial profiles

D2Signature - Point-to-Point Distance#

Measures distance between pairs of random surface points.

from volmdlr_tools.shapes.utils.signature import D2Signature

sig = D2Signature.from_volume_model(model)

Best for: General-purpose shape comparison; most commonly used

D3Signature - Triangle Area#

Measures square root of triangle areas formed by triplets of random points.

from volmdlr_tools.shapes.utils.signature import D3Signature

sig = D3Signature.from_volume_model(model)

Best for: Shapes where surface area distribution is distinctive

D4Signature - Tetrahedron Volume#

Measures cube root of tetrahedron volumes formed by quadruplets of random points.

from volmdlr_tools.shapes.utils.signature import D4Signature

sig = D4Signature.from_volume_model(model)

Best for: Volumetric shapes; requires more sample points for accuracy


Performance Considerations#

Sample Points#

More points = better accuracy but slower computation:

# Fast, lower accuracy (good for initial filtering)
sig_fast = D2Signature.from_volume_model(model, n_points=10000)

# Balanced (good for most use cases)
sig_balanced = D2Signature.from_volume_model(model, n_points=100000)

# High accuracy (for final comparison)
sig_accurate = D2Signature.from_volume_model(model, n_points=1000000)

Caching#

Signatures cache their histogram computations:

# First call computes and caches
bins_1024 = sig.get_bins(n_bins=1024)

# Subsequent calls with same n_bins use cache
bins_1024_again = sig.get_bins(n_bins=1024)  # Fast, uses cache

Meshing Parameters#

Control mesh quality for signature computation:

sig = D2Signature.from_volume_model(
    model,
    meshing_tolerance=0.001,        # Linear tolerance
    meshing_angular_tolerance=0.5,  # Angular tolerance in degrees
    n_points=100000
)

Use Cases#

Shape Classification#

# Define reference signatures for each class
class_signatures = {
    "bolt": D2Signature.from_volume_model(bolt_model),
    "nut": D2Signature.from_volume_model(nut_model),
    "washer": D2Signature.from_volume_model(washer_model),
}

# Classify unknown shape
unknown_sig = D2Signature.from_volume_model(unknown_model)

best_match = None
best_similarity = 0
for class_name, ref_sig in class_signatures.items():
    similarity = unknown_sig.similarity(ref_sig)
    if similarity > best_similarity:
        best_similarity = similarity
        best_match = class_name

print(f"Shape classified as: {best_match} (similarity: {best_similarity:.4f})")

Duplicate Detection#

# Find duplicate shapes in a collection
duplicates = []
for i, (name1, sig1) in enumerate(signatures.items()):
    for name2, sig2 in list(signatures.items())[i+1:]:
        if sig1.similarity(sig2) > 0.95:
            duplicates.append((name1, name2))

Shape Retrieval#

# Find top-N most similar shapes to a query
def find_similar(query_sig, database_sigs, top_n=5):
    similarities = [
        (name, query_sig.similarity(sig))
        for name, sig in database_sigs.items()
    ]
    similarities.sort(key=lambda x: x[1], reverse=True)
    return similarities[:top_n]

results = find_similar(query_signature, shape_database, top_n=10)

Reference#

The implementation is based on:

Osada, R., Funkhouser, T., Chazelle, B., & Dobkin, D. (2002). Shape distributions. ACM Transactions on Graphics, 21(4), 807-832. http://graphics.stanford.edu/courses/cs468-08-fall/pdf/osada.pdf

See Also#