/* * scan implementation * * Copyright (C) Andreas Nuechter, Kai Lingemann, Dorit Borrmann, Jan Elseberg, Thomas Escher * * Released under the GPL version 3. * */ #include "slam6d/scan.h" #include "slam6d/basicScan.h" #include "slam6d/managedScan.h" #include "slam6d/metaScan.h" #include "slam6d/searchTree.h" #include "slam6d/kd.h" #include "slam6d/Boctree.h" #include "slam6d/globals.icc" #include "normals/normals.h" #ifdef WITH_METRICS #include "slam6d/metrics.h" #endif #ifdef _MSC_VER #define _NO_PARALLEL_READ #endif #ifdef __APPLE__ #define _NO_PARALLEL_READ #endif using std::vector; vector Scan::allScans; bool Scan::scanserver = false; void Scan::openDirectory(bool scanserver, const std::string& path, IOType type, int start, int end) { Scan::scanserver = scanserver; if(scanserver) ManagedScan::openDirectory(path, type, start, end); else BasicScan::openDirectory(path, type, start, end); } void Scan::closeDirectory() { if(scanserver) ManagedScan::closeDirectory(); else BasicScan::closeDirectory(); } Scan::Scan() { unsigned int i; // pose and transformations for(i = 0; i < 3; ++i) rPos[i] = 0; for(i = 0; i < 3; ++i) rPosTheta[i] = 0; for(i = 0; i < 4; ++i) rQuat[i] = 0; M4identity(transMat); M4identity(transMatOrg); M4identity(dalignxf); // trees and reduction methods cuda_enabled = false; nns_method = -1; kd = 0; ann_kd_tree = 0; // reduction on-demand reduction_voxelSize = 0.0; reduction_nrpts = 0; reduction_pointtype = PointType(); // flags m_has_reduced = false; // octtree octtree_reduction_voxelSize = 0.0; octtree_voxelSize = 0.0; octtree_pointtype = PointType(); octtree_loadOct = false; octtree_saveOct = false; } Scan::~Scan() { if(kd) delete kd; } void Scan::setReductionParameter(double voxelSize, int nrpts, PointType pointtype) { reduction_voxelSize = voxelSize; reduction_nrpts = nrpts; reduction_pointtype = pointtype; } void Scan::setSearchTreeParameter(int nns_method, bool cuda_enabled) { searchtree_nnstype = nns_method; searchtree_cuda_enabled = cuda_enabled; } void Scan::setOcttreeParameter(double reduction_voxelSize, double voxelSize, PointType pointtype, bool loadOct, bool saveOct) { octtree_reduction_voxelSize = reduction_voxelSize; octtree_voxelSize = voxelSize; octtree_pointtype = pointtype; octtree_loadOct = loadOct; octtree_saveOct = saveOct; } void Scan::clear(unsigned int types) { if(types & DATA_XYZ) clear("xyz"); if(types & DATA_RGB) clear("rgb"); if(types & DATA_REFLECTANCE) clear("reflectance"); if(types & DATA_TEMPERATURE) clear("temperature"); if(types & DATA_AMPLITUDE) clear("amplitude"); if(types & DATA_TYPE) clear("type"); if(types & DATA_DEVIATION) clear("deviation"); } SearchTree* Scan::getSearchTree() { // if the search tree hasn't been created yet, calculate everything if(kd == 0) { createSearchTree(); } return kd; } void Scan::toGlobal() { calcReducedPoints(); transform(transMatOrg, INVALID); } /** * Computes a search tree depending on the type. */ void Scan::createSearchTree() { // multiple threads will call this function at the same time because they // all work on one pair of Scans, just let the first one (who sees a nullpointer) // do the creation boost::lock_guard lock(m_mutex_create_tree); if(kd != 0) return; // make sure the original points are created before starting the measurement DataXYZ xyz_orig(get("xyz reduced original")); #ifdef WITH_METRICS Timer tc = ClientMetric::create_tree_time.start(); #endif //WITH_METRICS createSearchTreePrivate(); #ifdef WITH_METRICS ClientMetric::create_tree_time.end(tc); #endif //WITH_METRICS } void Scan::calcReducedOnDemand() { // multiple threads will call this function at the same time // because they all work on one pair of Scans, // just let the first one (who sees count as zero) do the reduction boost::lock_guard lock(m_mutex_reduction); if(m_has_reduced) return; #ifdef WITH_METRICS Timer t = ClientMetric::on_demand_reduction_time.start(); #endif //WITH_METRICS calcReducedOnDemandPrivate(); m_has_reduced = true; #ifdef WITH_METRICS ClientMetric::on_demand_reduction_time.end(t); #endif //WITH_METRICS } void Scan::calcNormalsOnDemand() { // multiple threads will call this function at the same time // because they all work on one pair of Scans, // just let the first one (who sees count as zero) do the reduction boost::lock_guard lock(m_mutex_normals); if(m_has_normals) return; calcNormalsOnDemandPrivate(); m_has_normals = true; } void Scan::copyReducedToOriginal() { #ifdef WITH_METRICS Timer t = ClientMetric::copy_original_time.start(); #endif //WITH_METRICS DataXYZ xyz_reduced(get("xyz reduced")); unsigned int size = xyz_reduced.size(); DataXYZ xyz_reduced_orig(create("xyz reduced original", sizeof(double)*3*size)); for(unsigned int i = 0; i < size; ++i) { for(unsigned int j = 0; j < 3; ++j) { xyz_reduced_orig[i][j] = xyz_reduced[i][j]; } } #ifdef WITH_METRICS ClientMetric::copy_original_time.end(t); #endif //WITH_METRICS } void Scan::copyOriginalToReduced() { #ifdef WITH_METRICS Timer t = ClientMetric::copy_original_time.start(); #endif //WITH_METRICS DataXYZ xyz_reduced_orig(get("xyz reduced original")); unsigned int size = xyz_reduced_orig.size(); DataXYZ xyz_reduced(create("xyz reduced", sizeof(double)*3*size)); for(unsigned int i = 0; i < size; ++i) { for(unsigned int j = 0; j < 3; ++j) { xyz_reduced[i][j] = xyz_reduced_orig[i][j]; } } #ifdef WITH_METRICS ClientMetric::copy_original_time.end(t); #endif //WITH_METRICS } /** * Computes normals for all points */ void Scan::calcNormals() { cout << "calcNormals" << endl; DataXYZ xyz(get("xyz")); DataNormal xyz_normals(create("normal", sizeof(double)*3*xyz.size())); if(xyz.size() == 0) throw runtime_error("Could not calculate reduced points, XYZ data is empty"); vector points; points.reserve(xyz.size()); vector normals; normals.reserve(xyz.size()); for(unsigned int j = 0; j < xyz.size(); j++) { points.push_back(Point(xyz[j][0], xyz[j][1], xyz[j][2])); } const int K_NEIGHBOURS = 10; calculateNormalsApxKNN(normals, points, K_NEIGHBOURS, get_rPos(), 1.0); for (unsigned int i = 0; i < normals.size(); ++i) { xyz_normals[i][0] = normals[i].x; xyz_normals[i][1] = normals[i].y; xyz_normals[i][2] = normals[i].z; } } /** * Computes an octtree of the current scan, then getting the * reduced points as the centers of the octree voxels. */ void Scan::calcReducedPoints() { #ifdef WITH_METRICS Timer t = ClientMetric::scan_load_time.start(); #endif //WITH_METRICS // get xyz to start the scan load, separated here for time measurement DataXYZ xyz(get("xyz")); DataXYZ xyz_normals(get("")); if (reduction_pointtype.hasNormal()) { DataXYZ my_xyz_normals(get("normal")); xyz_normals = my_xyz_normals; } DataReflectance reflectance(get("")); if (reduction_pointtype.hasReflectance()) { DataReflectance my_reflectance(get("reflectance")); reflectance = my_reflectance; } #ifdef WITH_METRICS ClientMetric::scan_load_time.end(t); Timer tl = ClientMetric::calc_reduced_points_time.start(); #endif //WITH_METRICS if(reduction_voxelSize <= 0.0) { // copy the points DataXYZ xyz_reduced(create("xyz reduced", sizeof(double)*3*xyz.size())); for(unsigned int i = 0; i < xyz.size(); ++i) { for(unsigned int j = 0; j < 3; ++j) { xyz_reduced[i][j] = xyz[i][j]; } } if (reduction_pointtype.hasReflectance()) { DataReflectance reflectance_reduced(create("reflectance reduced", sizeof(float)*reflectance.size())); for(unsigned int i = 0; i < xyz.size(); ++i) { reflectance_reduced[i] = reflectance[i]; } } if (reduction_pointtype.hasNormal()) { DataNormal normal_reduced(create("normal reduced", sizeof(double)*3*xyz.size())); for(unsigned int i = 0; i < xyz.size(); ++i) { for(unsigned int j = 0; j < 3; ++j) { normal_reduced[i][j] = xyz_normals[i][j]; } } } } else { double **xyz_in = new double*[xyz.size()]; for (unsigned int i = 0; i < xyz.size(); ++i) { xyz_in[i] = new double[reduction_pointtype.getPointDim()]; unsigned int j = 0; for (; j < 3; ++j) xyz_in[i][j] = xyz[i][j]; if (reduction_pointtype.hasReflectance()) xyz_in[i][j++] = reflectance[i]; if (reduction_pointtype.hasNormal()) for (unsigned int l = 0; l < 3; ++l) xyz_in[i][j] = xyz_normals[i][l]; } // start reduction // build octree-tree from CurrentScan // put full data into the octtree BOctTree *oct = new BOctTree(xyz_in, xyz.size(), reduction_voxelSize, reduction_pointtype); vector center; center.clear(); if (reduction_nrpts > 0) { if (reduction_nrpts == 1) { oct->GetOctTreeRandom(center); } else { oct->GetOctTreeRandom(center, reduction_nrpts); } } else { oct->GetOctTreeCenter(center); } // storing it as reduced scan unsigned int size = center.size(); DataXYZ xyz_reduced(create("xyz reduced", sizeof(double)*3*size)); DataReflectance reflectance_reduced(get("")); DataNormal normal_reduced(get("")); if (reduction_pointtype.hasReflectance()) { DataReflectance my_reflectance_reduced(create("reflectance reduced", sizeof(float)*size)); reflectance_reduced = my_reflectance_reduced; } if (reduction_pointtype.hasNormal()) { DataNormal my_normal_reduced(create("normal reduced", sizeof(double)*3*size)); normal_reduced = my_normal_reduced; } for(unsigned int i = 0; i < size; ++i) { unsigned int j = 0; for (; j < 3; ++j) xyz_reduced[i][j] = center[i][j]; if (reduction_pointtype.hasReflectance()) reflectance_reduced[i] = center[i][j++]; if (reduction_pointtype.hasNormal()) for (unsigned int l = 0; l < 3; ++l) normal_reduced[i][l] = center[i][j++]; } } #ifdef WITH_METRICS ClientMetric::calc_reduced_points_time.end(tl); #endif //WITH_METRICS } /** * Merges the scan's intrinsic coordinates with the robot position. * @param prevScan The scan that's transformation is extrapolated, * i.e., odometry extrapolation * * For additional information see the following paper (jfr2007.pdf): * * Andreas Nüchter, Kai Lingemann, Joachim Hertzberg, and Hartmut Surmann, * 6D SLAM - 3D Mapping Outdoor Environments Journal of Field Robotics (JFR), * Special Issue on Quantitative Performance Evaluation of Robotic and Intelligent * Systems, Wiley & Son, ISSN 1556-4959, Volume 24, Issue 8-9, pages 699 - 722, * August/September, 2007 * */ void Scan::mergeCoordinatesWithRoboterPosition(Scan* prevScan) { double tempMat[16], deltaMat[16]; M4inv(prevScan->get_transMatOrg(), tempMat); MMult(prevScan->get_transMat(), tempMat, deltaMat); transform(deltaMat, INVALID); //apply delta transformation of the previous scan } /** * The method transforms all points with the given transformation matrix. */ void Scan::transformAll(const double alignxf[16]) { DataXYZ xyz(get("xyz")); unsigned int i=0 ; // #pragma omp parallel for for(; i < xyz.size(); ++i) { transform3(alignxf, xyz[i]); } // TODO: test for ManagedScan compability, may need a touch("xyz") to mark saving the new values } //! Internal function of transform which alters the reduced points void Scan::transformReduced(const double alignxf[16]) { #ifdef WITH_METRICS Timer t = ClientMetric::transform_time.start(); #endif //WITH_METRICS DataXYZ xyz_reduced(get("xyz reduced")); unsigned int i=0; // #pragma omp parallel for for( ; i < xyz_reduced.size(); ++i) { transform3(alignxf, xyz_reduced[i]); } DataNormal normal_reduced(get("normal reduced")); for (unsigned int i = 0; i < normal_reduced.size(); ++i) { transform3normal(alignxf, normal_reduced[i]); } #ifdef WITH_METRICS ClientMetric::transform_time.end(t); #endif //WITH_METRICS } //! Internal function of transform which handles the matrices void Scan::transformMatrix(const double alignxf[16]) { double tempxf[16]; // apply alignxf to transMat and update pose vectors MMult(alignxf, transMat, tempxf); memcpy(transMat, tempxf, sizeof(transMat)); Matrix4ToEuler(transMat, rPosTheta, rPos); Matrix4ToQuat(transMat, rQuat); #ifdef DEBUG cerr << "(" << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPosTheta[0] << ", " << rPosTheta[1] << ", " << rPosTheta[2] << ")" << endl; cerr << transMat << endl; #endif // apply alignxf to dalignxf MMult(alignxf, dalignxf, tempxf); memcpy(dalignxf, tempxf, sizeof(transMat)); } /** * Transforms the scan by a given transformation and writes a new frame. The idea * is to write for every transformation in all files, such that the show program * is able to determine, whcih scans have to be drawn in which color. Hidden scans * (or later processed scans) are written with INVALID. * * @param alignxf Transformation matrix * @param colour Specifies which colour should the written to the frames file * @param islum Is the transformtion part of LUM, i.e., all scans are transformed? * In this case only LUM transformation is stored, otherwise all scans are processed * -1 no transformation is stored * 0 ICP transformation * 1 LUM transformation, all scans except last scan * 2 LUM transformation, last scan only */ void Scan::transform(const double alignxf[16], const AlgoType type, int islum) { MetaScan* meta = dynamic_cast(this); if(meta) { for(unsigned int i = 0; i < meta->size(); ++i) { meta->getScan(i)->transform(alignxf, type, -1); } } #ifdef TRANSFORM_ALL_POINTS transformAll(alignxf); #endif //TRANSFORM_ALL_POINTS #ifdef DEBUG cerr << alignxf << endl; cerr << "(" << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPosTheta[0] << ", " << rPosTheta[1] << ", " << rPosTheta[2] << ") ---> "; #endif // transform points transformReduced(alignxf); // update matrices transformMatrix(alignxf); // store transformation in frames if(type != INVALID) { #ifdef WITH_METRICS Timer t = ClientMetric::add_frames_time.start(); #endif //WITH_METRICS bool in_meta; MetaScan* meta = dynamic_cast(this); int found = 0; unsigned int scans_size = allScans.size(); switch (islum) { case -1: // write no tranformation break; case 0: for(unsigned int i = 0; i < scans_size; ++i) { Scan* scan = allScans[i]; in_meta = false; if(meta) { for(unsigned int j = 0; j < meta->size(); ++j) { if(meta->getScan(j) == scan) { found = i; in_meta = true; } } } if(scan == this || in_meta) { found = i; scan->addFrame(type); } else { if(found == 0) { scan->addFrame(ICPINACTIVE); } else { scan->addFrame(INVALID); } } } break; case 1: addFrame(type); break; case 2: for(unsigned int i = 0; i < scans_size; ++i) { Scan* scan = allScans[i]; if(scan == this) { found = i; addFrame(type); allScans[0]->addFrame(type); continue; } if (found != 0) { scan->addFrame(INVALID); } } break; default: cerr << "invalid point transformation mode" << endl; } #ifdef WITH_METRICS ClientMetric::add_frames_time.end(t); #endif //WITH_METRICS } } /** * Transforms the scan by a given transformation and writes a new frame. The idea * is to write for every transformation in all files, such that the show program * is able to determine, whcih scans have to be drawn in which color. Hidden scans * (or later processed scans) are written with INVALID. * * @param alignQuat Quaternion for the rotation * @param alignt Translation vector * @param colour Specifies which colour should the written to the frames file * @param islum Is the transformtion part of LUM, i.e., all scans are transformed? * In this case only LUM transformation is stored, otherwise all scans are processed * -1 no transformation is stored * 0 ICP transformation * 1 LUM transformation, all scans except last scan * 2 LUM transformation, last scan only */ void Scan::transform(const double alignQuat[4], const double alignt[3], const AlgoType type, int islum) { double alignxf[16]; QuatToMatrix4(alignQuat, alignt, alignxf); transform(alignxf, type, islum); } /** * Transforms the scan, so that the given Matrix * prepresent the next pose. * * @param alignxf Transformation matrix to which this scan will be set to * @param islum Is the transformation part of LUM? */ void Scan::transformToMatrix(double alignxf[16], const AlgoType type, int islum) { double tinv[16]; M4inv(transMat, tinv); transform(tinv, INVALID); transform(alignxf, type, islum); } /** * Transforms the scan, so that the given Euler angles * prepresent the next pose. * * @param rP Translation to which this scan will be set to * @param rPT Orientation as Euler angle to which this scan will be set * @param islum Is the transformation part of LUM? */ void Scan::transformToEuler(double rP[3], double rPT[3], const AlgoType type, int islum) { #ifdef WITH_METRICS // called in openmp context in lum6Deuler.cc:422 ClientMetric::transform_time.set_threadsafety(true); ClientMetric::add_frames_time.set_threadsafety(true); #endif //WITH_METRICS double tinv[16]; double alignxf[16]; M4inv(transMat, tinv); transform(tinv, INVALID); EulerToMatrix4(rP, rPT, alignxf); transform(alignxf, type, islum); #ifdef WITH_METRICS ClientMetric::transform_time.set_threadsafety(false); ClientMetric::add_frames_time.set_threadsafety(false); #endif //WITH_METRICS } /** * Transforms the scan, so that the given Euler angles * prepresent the next pose. * * @param rP Translation to which this scan will be set to * @param rPQ Orientation as Quaternion to which this scan will be set * @param islum Is the transformation part of LUM? */ void Scan::transformToQuat(double rP[3], double rPQ[4], const AlgoType type, int islum) { double tinv[16]; double alignxf[16]; M4inv(transMat, tinv); transform(tinv, INVALID); QuatToMatrix4(rPQ, rP, alignxf); transform(alignxf, type, islum); } /** * Calculates Source\Target * Calculates a set of corresponding point pairs and returns them. It * computes the k-d trees and deletes them after the pairs have been * found. This slow function should be used only for testing * * @param pairs The resulting point pairs (vector will be filled) * @param Target The scan to whiche the points are matched * @param thread_num number of the thread (for parallelization) * @param rnd randomized point selection * @param max_dist_match2 maximal allowed distance for matching */ void Scan::getNoPairsSimple(vector &diff, Scan* Source, Scan* Target, int thread_num, double max_dist_match2) { DataXYZ xyz_reduced(Source->get("xyz reduced")); KDtree* kd = new KDtree(PointerArray(Target->get("xyz reduced")).get(), Target->size("xyz reduced")); cout << "Max: " << max_dist_match2 << endl; for (unsigned int i = 0; i < xyz_reduced.size(); i++) { double p[3]; p[0] = xyz_reduced[i][0]; p[1] = xyz_reduced[i][1]; p[2] = xyz_reduced[i][2]; double *closest = kd->FindClosest(p, max_dist_match2, thread_num); if (!closest) { diff.push_back(xyz_reduced[i]); //diff.push_back(closest); } } delete kd; } /** * Calculates a set of corresponding point pairs and returns them. It * computes the k-d trees and deletes them after the pairs have been * found. This slow function should be used only for testing * * @param pairs The resulting point pairs (vector will be filled) * @param Source The scan whose points are matched to Targets' points * @param Target The scan to whiche the points are matched * @param thread_num number of the thread (for parallelization) * @param rnd randomized point selection * @param max_dist_match2 maximal allowed distance for matching */ void Scan::getPtPairsSimple(vector *pairs, Scan* Source, Scan* Target, int thread_num, int rnd, double max_dist_match2, double *centroid_m, double *centroid_d) { KDtree* kd = new KDtree(PointerArray(Source->get("xyz reduced")).get(), Source->size("xyz reduced")); DataXYZ xyz_reduced(Target->get("xyz reduced")); for (unsigned int i = 0; i < xyz_reduced.size(); i++) { if (rnd > 1 && rand(rnd) != 0) continue; // take about 1/rnd-th of the numbers only double p[3]; p[0] = xyz_reduced[i][0]; p[1] = xyz_reduced[i][1]; p[2] = xyz_reduced[i][2]; double *closest = kd->FindClosest(p, max_dist_match2, thread_num); if (closest) { centroid_m[0] += closest[0]; centroid_m[1] += closest[1]; centroid_m[2] += closest[2]; centroid_d[0] += p[0]; centroid_d[1] += p[1]; centroid_d[2] += p[2]; PtPair myPair(closest, p); pairs->push_back(myPair); } } centroid_m[0] /= pairs[thread_num].size(); centroid_m[1] /= pairs[thread_num].size(); centroid_m[2] /= pairs[thread_num].size(); centroid_d[0] /= pairs[thread_num].size(); centroid_d[1] /= pairs[thread_num].size(); centroid_d[2] /= pairs[thread_num].size(); delete kd; } /** * Calculates a set of corresponding point pairs and returns them. * The function uses the k-d trees stored the the scan class, thus * the function createTrees and deletTrees have to be called before * resp. afterwards. * Here we implement the so called "fast corresponding points"; k-d * trees are not recomputed, instead the apply the inverse transformation * to to the given point set. * * @param pairs The resulting point pairs (vector will be filled) * @param Source The scan whose points are matched to Targets' points * @param Target The scan to whiche the points are matched * @param thread_num number of the thread (for parallelization) * @param rnd randomized point selection * @param max_dist_match2 maximal allowed distance for matching * @return a set of corresponding point pairs */ void Scan::getPtPairs(vector *pairs, Scan* Source, Scan* Target, int thread_num, int rnd, double max_dist_match2, double &sum, double *centroid_m, double *centroid_d, PairingMode pairing_mode) { // initialize centroids for(unsigned int i = 0; i < 3; ++i) { centroid_m[i] = 0; centroid_d[i] = 0; } // get point pairs DataXYZ xyz_reduced(Target->get("xyz reduced")); DataNormal normal_reduced(Target->get("normal reduced")); Source->getSearchTree()->getPtPairs(pairs, Source->dalignxf, xyz_reduced, normal_reduced, 0, xyz_reduced.size(), thread_num, rnd, max_dist_match2, sum, centroid_m, centroid_d, pairing_mode); // normalize centroids unsigned int size = pairs->size(); if(size != 0) { for(unsigned int i = 0; i < 3; ++i) { centroid_m[i] /= size; centroid_d[i] /= size; } } } /** * Calculates a set of corresponding point pairs and returns them. * The function uses the k-d trees stored the the scan class, thus * the function createTrees and delteTrees have to be called before * resp. afterwards. * * @param pairs The resulting point pairs (vector will be filled) * @param Source The scan whose points are matched to Targets' points * @param Target The scan to whiche the points are matched * @param thread_num The number of the thread that is computing ptPairs in parallel * @param step The number of steps for parallelization * @param rnd randomized point selection * @param max_dist_match2 maximal allowed distance for matching * @param sum The sum of distances of the points * * These intermediate values are for the parallel ICP algorithm * introduced in the paper * "The Parallel Iterative Closest Point Algorithm" * by Langis / Greenspan / Godin, IEEE 3DIM 2001 * */ void Scan::getPtPairsParallel(vector *pairs, Scan* Source, Scan* Target, int thread_num, int step, int rnd, double max_dist_match2, double *sum, double centroid_m[OPENMP_NUM_THREADS][3], double centroid_d[OPENMP_NUM_THREADS][3], PairingMode pairing_mode) { // initialize centroids for(unsigned int i = 0; i < 3; ++i) { centroid_m[thread_num][i] = 0; centroid_d[thread_num][i] = 0; } // get point pairs SearchTree* search = Source->getSearchTree(); // differentiate between a meta scan (which has no reduced points) and a normal scan // if Source is also a meta scan it already has a special meta-kd-tree MetaScan* meta = dynamic_cast(Target); if(meta) { for(unsigned int i = 0; i < meta->size(); ++i) { // determine step for each scan individually DataXYZ xyz_reduced(meta->getScan(i)->get("xyz reduced")); DataNormal normal_reduced(Target->get("normal reduced")); unsigned int max = xyz_reduced.size(); unsigned int step = max / OPENMP_NUM_THREADS; // call ptpairs for each scan and accumulate ptpairs, centroids and sum search->getPtPairs(&pairs[thread_num], Source->dalignxf, xyz_reduced, normal_reduced, step * thread_num, step * thread_num + step, thread_num, rnd, max_dist_match2, sum[thread_num], centroid_m[thread_num], centroid_d[thread_num], pairing_mode); } } else { DataXYZ xyz_reduced(Target->get("xyz reduced")); DataNormal normal_reduced(Target->get("normal reduced")); search->getPtPairs(&pairs[thread_num], Source->dalignxf, xyz_reduced, normal_reduced, thread_num * step, thread_num * step + step, thread_num, rnd, max_dist_match2, sum[thread_num], centroid_m[thread_num], centroid_d[thread_num], pairing_mode); } // normalize centroids unsigned int size = pairs[thread_num].size(); if(size != 0) { for(unsigned int i = 0; i < 3; ++i) { centroid_m[thread_num][i] /= size; centroid_d[thread_num][i] /= size; } } } unsigned int Scan::getMaxCountReduced(ScanVector& scans) { unsigned int max = 0; for(std::vector::iterator it = scans.begin(); it != scans.end(); ++it) { unsigned int count = (*it)->size("xyz reduced"); if(count > max) max = count; } return max; }