@@ -2496,6 +2496,189 @@ class op_normal_estimate : public voxel_operation {
24962496 }
24972497};
24982498
2499+
2500+ class op_dimensionality_estimate : public voxel_operation {
2501+ public:
2502+ const std::vector<argument_spec>& arg_names () const {
2503+ static std::vector<argument_spec> nm_ = { { true , " input" , " voxels" }, { false , " max_depth" , " integer" }, { false , " max_depth_2" , " integer" }, {false , " distance_2" , " real" }, {false , " neighbourhood_size" , " integer" }, {false , " reposition" , " integer" }};
2504+ return nm_;
2505+ }
2506+ symbol_value invoke (const scope_map& scope) const {
2507+ static normal_and_curvature_t fmt;
2508+ normal_and_curvature<int16_t > v;
2509+
2510+ auto voxels = (regular_voxel_storage*)scope.get_value <abstract_voxel_storage*>(" input" );
2511+ auto result = voxels->empty_copy_as (&fmt);
2512+
2513+ boost::optional<int > max_depth, max_depth_2, neighbourhood_size;
2514+ boost::optional<double > distance_2;
2515+
2516+ try {
2517+ max_depth = scope.get_value <int >(" max_depth" );
2518+ } catch (scope_map::not_in_scope&) {}
2519+ try {
2520+ max_depth_2 = scope.get_value <int >(" max_depth_2" );
2521+ } catch (scope_map::not_in_scope&) {}
2522+ try {
2523+ neighbourhood_size = scope.get_value <int >(" neighbourhood_size" );
2524+ } catch (scope_map::not_in_scope&) {}
2525+ try {
2526+ distance_2 = scope.get_value <double >(" distance_2" );
2527+ } catch (scope_map::not_in_scope&) {}
2528+
2529+ if (!max_depth && !neighbourhood_size) {
2530+ throw std::runtime_error (" Either max_depth or neighbourhood_size needs to be provided" );
2531+ }
2532+
2533+ int reposition = scope.get_value_or <int >(" reposition" , 0 ) ? 2 : 1 ;
2534+
2535+ std::vector<float > coords;
2536+
2537+ if (neighbourhood_size) {
2538+ coords.reserve (*neighbourhood_size);
2539+ } else if (max_depth) {
2540+ auto box_dim = 1 + (*max_depth) * 2 ;
2541+ coords.reserve (box_dim * box_dim * box_dim);
2542+ }
2543+
2544+ for (auto it = voxels->begin (); it != voxels->end (); ++it) {
2545+ visitor<26 > vis;
2546+
2547+ auto seed = *it;
2548+
2549+ for (int iter = 0 ; iter < reposition; ++iter) {
2550+
2551+ int previous_coord_count = -1 ;
2552+ int max_depth_guess = ((int )std::ceil (std::pow (26 , 1 . / 3 ) - 1 )) / 2 ;
2553+
2554+ while (true ) {
2555+ if (neighbourhood_size) {
2556+ vis.max_depth = max_depth_guess;
2557+ } else {
2558+ vis.max_depth = iter == 1 && max_depth_2 ? max_depth_2 : max_depth;
2559+ }
2560+
2561+ coords.clear ();
2562+
2563+ vis ([&coords, neighbourhood_size](const tagged_index& pos) {
2564+ if (pos.which == tagged_index::VOXEL) {
2565+ if (!neighbourhood_size || (coords.size () / 3 < *neighbourhood_size)) {
2566+ coords.push_back (pos.pos .get (0 ));
2567+ coords.push_back (pos.pos .get (1 ));
2568+ coords.push_back (pos.pos .get (2 ));
2569+ }
2570+ } else {
2571+ throw std::runtime_error (" Unexpected" );
2572+ }
2573+ }, voxels, seed);
2574+
2575+ if (neighbourhood_size) {
2576+ if (coords.size () / 3 == *neighbourhood_size) {
2577+ break ;
2578+ }
2579+ if (previous_coord_count == (int )coords.size ()) {
2580+ break ;
2581+ }
2582+ if (max_depth) {
2583+ if (*max_depth == max_depth_guess) {
2584+ break ;
2585+ }
2586+ }
2587+ previous_coord_count = (int )coords.size ();
2588+ } else if (max_depth) {
2589+ break ;
2590+ }
2591+
2592+ ++max_depth_guess;
2593+ }
2594+
2595+ auto points = Eigen::Map<Eigen::Matrix<float , 3 , Eigen::Dynamic>>(coords.data (), 3 , coords.size () / 3 ).transpose ();
2596+
2597+ if (reposition == 2 && iter == 0 ) {
2598+ #if 1
2599+ auto mean = points.colwise ().mean ();
2600+ #elif 0
2601+ // median
2602+ Eigen::RowVector3f mean;
2603+ size_t i = 0 ;
2604+ for (const auto & c : points.colwise ()) {
2605+ std::vector<double > r;
2606+ r.reserve (c.size ());
2607+ for (auto & v : c) {
2608+ r.push_back (v);
2609+ }
2610+ std::sort (r.begin (), r.end ());
2611+ auto v = r.size () % 2 == 0
2612+ ? (r[(r.size () - 2 ) / 2 ] + r[(r.size () - 2 ) / 2 + 1 ]) / 2 .
2613+ : r[r.size () / 2 ];
2614+ mean (i++) = v;
2615+ }
2616+ #else
2617+ // bounding box center
2618+ auto mi = points.colwise().minCoeff();
2619+ auto ma = points.colwise().maxCoeff();
2620+ auto mean = (mi + ma) / 2.;
2621+ #endif
2622+
2623+ // Find point in neighborhood closest to median
2624+ auto minl = std::numeric_limits<double >::infinity ();
2625+ Eigen::RowVectorXf p;
2626+ for (const auto & r : points.rowwise ()) {
2627+ auto l = (r - mean).norm ();
2628+ if (l < minl) {
2629+ minl = l;
2630+ p = r;
2631+ }
2632+ }
2633+
2634+ // Assign to seed to reposition neighborhood around found median
2635+ seed.get <0 >() = (size_t )p.data ()[0 ];
2636+ seed.get <1 >() = (size_t )p.data ()[1 ];
2637+ seed.get <2 >() = (size_t )p.data ()[2 ];
2638+
2639+ // Continue within outer loop to restart traversal from new seed
2640+ continue ;
2641+ }
2642+
2643+ Eigen::MatrixXf centered = points.rowwise () - points.colwise ().mean ();
2644+
2645+ if (distance_2) {
2646+ auto norms = centered.rowwise ().norm ().array ();
2647+ auto mask = norms < (*distance_2);
2648+ Eigen::MatrixXf filtered (mask.count (), centered.cols ());
2649+ size_t idx = 0 ;
2650+ for (size_t i = 0 ; i < mask.size (); ++i) {
2651+ if (mask (i)) {
2652+ filtered.row (idx++) = centered.row (i);
2653+ }
2654+ }
2655+ centered = filtered;
2656+ }
2657+
2658+ Eigen::MatrixXf cov = centered.adjoint () * centered;
2659+ Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> eig (cov);
2660+
2661+ std::array<double , 3 > norm_eigv = {
2662+ eig.eigenvalues ().data ()[0 ] / eig.eigenvalues ().norm (),
2663+ eig.eigenvalues ().data ()[1 ] / eig.eigenvalues ().norm (),
2664+ eig.eigenvalues ().data ()[2 ] / eig.eigenvalues ().norm ()
2665+ };
2666+
2667+ v.nxyz_curv = {
2668+ (int16_t )(norm_eigv[0 ] * (float )(std::numeric_limits<int16_t >::max () - 1 )),
2669+ (int16_t )(norm_eigv[1 ] * (float )(std::numeric_limits<int16_t >::max () - 1 )),
2670+ (int16_t )(norm_eigv[2 ] * (float )(std::numeric_limits<int16_t >::max () - 1 )),
2671+ 0
2672+ };
2673+
2674+ result->Set (*it, &v);
2675+ }
2676+ }
2677+
2678+ return result;
2679+ }
2680+ };
2681+
24992682namespace {
25002683 class check_curvature_and_normal_deviation {
25012684 private:
0 commit comments