shot_descriptor — 3D feature_register op

• Data kinds: points × normals → descriptor

• Call: import fullseye as fs; fs.ledger.shot_descriptor(points, normals, kp_idx, tree, radius, n_azim=8, n_elev=2, n_rad=2, n_cos=11) (to call the implementation directly, import feat_shot; feat_shot.shot_descriptor(points, normals, kp_idx, tree, radius, n_azim=8, n_elev=2, n_rad=2, n_cos=11); from the registry, ops3d.get("shot_descriptor"))

Usage

The SHOT descriptor (Tombari 2010). It builds a local reference frame at each keypoint and takes a spherical support.

The support is divided into radial 2 × elevation 2 × azimuth 8 = 32 spatial cells; in each cell, the "cosine of the angle between the LRF z axis and the neighbour point normal" is accumulated by quadrilinear interpolation into a histogram of n_cos=11 bins → the 32×11=352 dimensions are L2-normalised. Returns (Kp,352). Points without a valid LRF get a zero vector.

Raises ValueError: if the row count of normals does not match points (mixing in the normals of another point cloud makes neighbour indices go out of range and raise a raw IndexError — the same class as compute_fpfh).

Arguments: points (N,3), normals (N,3) unit normals, kp_idx are the point indices of the keypoints (output of iss_keypoints), tree is a scipy.spatial.cKDTree built from points (prepared by the caller), radius is the support radius (used both for LRF estimation and for gathering neighbours). Changing n_azim, n_elev, n_rad, n_cos makes the dimension n_azim*n_elev*n_rad*n_cos.

Procedure: the LRF takes the eigenvectors of the covariance with distance weight max(radius-d, 0) (x = largest, z = smallest eigenvalue), aligns them to the sign of the neighbour majority and makes the frame right-handed. Keypoints with fewer than 5 neighbours, or whose LRF is degenerate, stay zero vectors (they are excluded on the matching side). Each neighbour point is linearly interpolated in radius, elevation (arccos(qz/r)/π) and normal cosine angle with bin centres at 0.5, while azimuth is wrapped around the circle and accumulated; finally each row is L2-normalised. Returns float64 (len(kp_idx), n_azim*n_elev*n_rad*n_cos) (352 by default). When comparing 2 clouds, use the same radius and the same normal sign rule on both sides (if the normal orientation is flipped, the cosine-angle histogram is mirrored). register_shot applies this function to both clouds and goes on through matching and RANSAC.

Background guides (the physics and conventions behind this op)

• blas_threads_and_memory — Why matrix decompositions are slow — BLAS threads, caches, memory layout

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

Runnable examples (verified samples that actually call this op)

• feature_register — py -3.11 examples_3d/feature_register.py

Ops the type connects to (they accept descriptor as input)

fuse_to_voxel · shape_distance

Same category (feature_register)

harris3d_keypoints · iss_keypoints · compute_fpfh · register_spin · register_fpfh · register_shot


*Provenance: feat_shot.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.