Skip to content

Extend BruteForce search to non-XYZ point types - #6457

Open
jeevan6996 wants to merge 6 commits into
PointCloudLibrary:masterfrom
jeevan6996:extend-bruteforce-nonxyz-points
Open

Extend BruteForce search to non-XYZ point types#6457
jeevan6996 wants to merge 6 commits into
PointCloudLibrary:masterfrom
jeevan6996:extend-bruteforce-nonxyz-points

Conversation

@jeevan6996

Copy link
Copy Markdown

Summary

  • add PointRepresentation support to BruteForce search
  • keep the existing fast XYZ distance path when the representation is trivial 3D
  • use full DefaultFeatureRepresentation dimensions for descriptor point types where available
  • allow autoSelectMethod to fall back to BruteForce when FLANN/Nanoflann are unavailable
  • add focused BruteForce tests for custom representations, FPFH descriptors, and sparse invalid descriptors

Fixes #6421.

Validation

  • configured a focused local build with search/tests enabled
  • built target: cmake --build /private/tmp/pcl-bruteforce-build-6 --target test_brute_force_search
  • ran: ctest --test-dir /private/tmp/pcl-bruteforce-build-6/test -R brute_force_search --output-on-failure
  • ran: git diff --check

@jeevan6996

Copy link
Copy Markdown
Author

The CI linker failures were caused by AutoSelectMethod being explicitly instantiated for every PCL_POINT_TYPES, while BruteForce was precompiled only for the XYZ and descriptor subsets. That left symbols undefined for types such as SHOT352, Normal, and PointUV when the fallback was selected.

Updated search/src/brute_force.cpp to instantiate BruteForce for PCL_POINT_TYPES, keeping both instantiation sets aligned. Verified locally with Homebrew LLVM: pcl_search builds, test_brute_force_search links, and all 3 focused tests pass.

Comment thread search/include/pcl/search/brute_force.h Outdated
Comment on lines +66 to +69
if constexpr (!pcl::traits::has_xyz_v<PointT> && has_descriptor_size<PointT>::value)
return (pcl::make_shared<DefaultFeatureRepresentation<PointT>> ());
else
return (pcl::make_shared<DefaultPointRepresentation<PointT>> ());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this pull request. Could you please explain why you did this condition, since none of the other search methods do it like this?

@jeevan6996

Copy link
Copy Markdown
Author

Thanks. I checked the other search implementations and they default to DefaultPointRepresentation, which is limited to the first three float values unless a type-specific specialization exists.

This condition is intentional for BruteForce’s new non-XYZ descriptor support: types with descriptorSize need DefaultFeatureRepresentation so all descriptor dimensions are included in distance calculations. XYZ and ordinary point types retain the existing default behavior, and callers can still override the representation explicitly. Tests cover both descriptor and custom representations.

template <typename PointT> bool
pcl::search::BruteForce<PointT>::isValidPoint (const PointT& point) const
{
if constexpr (pcl::traits::has_xyz_v<PointT>)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new custom-representation path is not used by isValidPoint() for point types with XYZ traits:

if constexpr (pcl::traits::has_xyz_v<PointT>)
  return (pcl::isFinite (point));

This still validates x/y/z even when setPointRepresentation() installs a representation that intentionally uses a different subset of fields. For example, a representation that uses only x/y should be able to search a point whose z is NaN, but this causes the query assertion to fail (and causes sparse searches to skip the point). This also differs from the representation-based validity behavior used by the other search implementations.

Could this use point_representation_->isValid(point) consistently, with a regression test using a custom representation that ignores an invalid XYZ field?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, then there is even no point in having the isValidPoint function, instead point_representation_->isValid(point) can be checked inline.

@mvieth

mvieth commented Aug 14, 2026

Copy link
Copy Markdown
Member

Thanks. I checked the other search implementations and they default to DefaultPointRepresentation, which is limited to the first three float values unless a type-specific specialization exists.

This condition is intentional for BruteForce’s new non-XYZ descriptor support: types with descriptorSize need DefaultFeatureRepresentation so all descriptor dimensions are included in distance calculations. XYZ and ordinary point types retain the existing default behavior, and callers can still override the representation explicitly. Tests cover both descriptor and custom representations.

I just checked all types for which specializations of descriptorSize exist, and I identified several types where specializations of DefaultPointRepresentation exist and it is thus wrong to use DefaultFeatureRepresentation (e.g. ShapeContext1980). So I would suggest to just use DefaultPointRepresentation, not DefaultFeatureRepresentation, in BruteForce, to stay consistent with KdTree search and others.

But I have also identified four types which are missing a specialization of DefaultPointRepresentation in point_representation.h, so the following should be added to point_representation.h:

  template <>
  class DefaultPointRepresentation<GRSDSignature21> : public DefaultFeatureRepresentation <GRSDSignature21>
  {};

  template <>
  class DefaultPointRepresentation<BRISKSignature512> : public DefaultFeatureRepresentation <BRISKSignature512>
  {};

  template <>
  class DefaultPointRepresentation<ESFSignature640> : public DefaultFeatureRepresentation <ESFSignature640>
  {};

  template <>
  class DefaultPointRepresentation<GFPFHSignature16> : public DefaultFeatureRepresentation <GFPFHSignature16>
  {};

Can you do that? Thanks.

@mvieth

mvieth commented Aug 15, 2026

Copy link
Copy Markdown
Member

Please also change 512 to 66 here, then the tests should pass:

template<> struct descriptorSize<BRISKSignature512> { static constexpr const int value = 512; };

Seems like there has been a mistake when detail::traits::descriptorSize was added ( d39d3d3 ).

Comment thread search/include/pcl/search/brute_force.h Outdated
public:
BruteForce (bool sorted_results = false)
: Search<PointT> ("BruteForce", sorted_results)
, point_representation_ (detail::makeDefaultPointRepresentation<PointT> ())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
, point_representation_ (detail::makeDefaultPointRepresentation<PointT> ())
, point_representation_ (new DefaultPointRepresentation<PointT>)

I think there is not really a point in having the makeDefaultPointRepresentation function any more, so I would suggest removing it.

Comment thread common/include/pcl/impl/point_types.hpp Outdated
template<> struct descriptorSize<VFHSignature308> { static constexpr const int value = 308; };
template<> struct descriptorSize<GRSDSignature21> { static constexpr const int value = 21; };
template<> struct descriptorSize<BRISKSignature512> { static constexpr const int value = 512; };
template<> struct descriptorSize<BRISKSignature512> { static constexpr const int value = 66; };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be [64] ?(

unsigned char descriptor[64] = {0};
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, originally it was 64 (before it was mistakenly changed to 512). But in addition to the descriptor, BRISKSignature512 also has two floats scale and orientation. I am not familiar enough with BRISK to say for sure, whether scale and orientation should be considered or not when searching for similar descriptors. But if we say they should not be included, we cannot simply inherit from DefaultFeatureRepresentation but have to write a custom DefaultPointRepresentation specialization for BRISKSignature512 (similar to DefaultPointRepresentation <Narf36>, for example).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I look at the other features, there are also some which has additional fields beyond the array descriptors, which most of these sizes represents?

But keeping it at 66 for BRISKSignature512 in this PR - would lead to out-of-bounds reads, if one iterates over the array with the size given from descriptorSize?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then let's use 64, and the following in point_representation.h:

  template <>
  class DefaultPointRepresentation <BRISKSignature512> : public PointRepresentation <BRISKSignature512>
  {
    public:
      static constexpr const std::int32_t NR_DIMS = 64;

      DefaultPointRepresentation ()
      {
        nr_dimensions_ = NR_DIMS;
        trivial_ = false;
      }

      void
      copyToFloatArray (const BRISKSignature512 &p, float * out) const override
      {
        for (int i = 0; i < nr_dimensions_; ++i)
          out[i] = p.descriptor[i];
      }
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Help wanted: Extend BruteForce search to point types without xyz

3 participants