// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkStatisticalOutlierRemoval.h" #include "vtkAbstractPointLocator.h" #include "vtkArrayDispatch.h" #include "vtkArrayDispatchDataSetArrayList.h" #include "vtkDataArrayRange.h" #include "vtkIdList.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPointSet.h" #include "vtkPoints.h" #include "vtkSMPThreadLocalObject.h" #include "vtkSMPTools.h" #include "vtkStaticPointLocator.h" VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkStatisticalOutlierRemoval); vtkCxxSetObjectMacro(vtkStatisticalOutlierRemoval, Locator, vtkAbstractPointLocator); //------------------------------------------------------------------------------ // Helper classes to support efficient computing, and threaded execution. namespace { //------------------------------------------------------------------------------ // The threaded core of the algorithm (first pass) template struct ComputeMeanDistanceFunctor { TArray* Points; vtkAbstractPointLocator* Locator; int SampleSize; float* Distance; double Mean; // Don't want to allocate working arrays on every thread invocation. Thread local // storage lots of new/delete. vtkSMPThreadLocalObject PIds; vtkSMPThreadLocal ThreadMean; vtkSMPThreadLocal ThreadCount; ComputeMeanDistanceFunctor(TArray* points, vtkAbstractPointLocator* loc, int size, float* d) : Points(points) , Locator(loc) , SampleSize(size) , Distance(d) , Mean(0.0) { } // Just allocate a little bit of memory to get started. void Initialize() { vtkIdList*& pIds = this->PIds.Local(); pIds->Allocate(128); // allocate some memory double& threadMean = this->ThreadMean.Local(); threadMean = 0.0; vtkIdType& threadCount = this->ThreadCount.Local(); threadCount = 0; } // Compute average distance for each point, plus accumulate summation of // mean distances and count (for averaging in the Reduce() method). void operator()(vtkIdType ptId, vtkIdType endPtId) { auto points = vtk::DataArrayTupleRange<3>(this->Points); auto px = points.begin() + ptId; double x[3], y[3]; vtkIdList*& pIds = this->PIds.Local(); double& threadMean = this->ThreadMean.Local(); vtkIdType& threadCount = this->ThreadCount.Local(); for (; ptId < endPtId; ++ptId, ++px) { px->GetTuple(x); // The method FindClosestNPoints will include the current point, so // we increase the sample size by one. this->Locator->FindClosestNPoints(this->SampleSize + 1, x, pIds); vtkIdType numPts = pIds->GetNumberOfIds(); double sum = 0.0; vtkIdType nei; for (int sample = 0; sample < numPts; ++sample) { nei = pIds->GetId(sample); if (nei != ptId) // exclude ourselves { auto py = points[nei]; py.GetTuple(y); sum += sqrt(vtkMath::Distance2BetweenPoints(x, y)); } } // sum the lengths of all samples excluding current point // Average the lengths; again exclude ourselves if (numPts > 0) { this->Distance[ptId] = sum / static_cast(numPts - 1); threadMean += this->Distance[ptId]; threadCount++; } else // ignore if no points are found, something bad has happened { this->Distance[ptId] = VTK_FLOAT_MAX; // the effect is to eliminate it } } } // Compute the mean by compositing all threads void Reduce() { double mean = 0.0; vtkIdType count = 0; vtkSMPThreadLocal::iterator mItr; vtkSMPThreadLocal::iterator mEnd = this->ThreadMean.end(); for (mItr = this->ThreadMean.begin(); mItr != mEnd; ++mItr) { mean += *mItr; } vtkSMPThreadLocal::iterator cItr; vtkSMPThreadLocal::iterator cEnd = this->ThreadCount.end(); for (cItr = this->ThreadCount.begin(); cItr != cEnd; ++cItr) { count += *cItr; } count = (count < 1 ? 1 : count); this->Mean = mean / static_cast(count); } }; // ComputeMeanDistanceFunctor struct ComputeMeanDistanceWorker { template void operator()( TArray* points, vtkStatisticalOutlierRemoval* self, float* distances, double& mean) { ComputeMeanDistanceFunctor meanDist( points, self->GetLocator(), self->GetSampleSize(), distances); vtkSMPTools::For(0, points->GetNumberOfTuples(), meanDist); mean = meanDist.Mean; } }; //------------------------------------------------------------------------------ // Now that the mean is known, compute the standard deviation struct ComputeStdDev { float* Distances; double Mean; double StdDev; vtkSMPThreadLocal ThreadSigma; vtkSMPThreadLocal ThreadCount; ComputeStdDev(float* d, double mean) : Distances(d) , Mean(mean) , StdDev(0.0) { } void Initialize() { double& threadSigma = this->ThreadSigma.Local(); threadSigma = 0.0; vtkIdType& threadCount = this->ThreadCount.Local(); threadCount = 0; } void operator()(vtkIdType ptId, vtkIdType endPtId) { double& threadSigma = this->ThreadSigma.Local(); vtkIdType& threadCount = this->ThreadCount.Local(); float d; for (; ptId < endPtId; ++ptId) { d = this->Distances[ptId]; if (d < VTK_FLOAT_MAX) { threadSigma += (this->Mean - d) * (this->Mean - d); threadCount++; } else { continue; // skip bad point } } } void Reduce() { double sigma = 0.0; vtkIdType count = 0; vtkSMPThreadLocal::iterator sItr; vtkSMPThreadLocal::iterator sEnd = this->ThreadSigma.end(); for (sItr = this->ThreadSigma.begin(); sItr != sEnd; ++sItr) { sigma += *sItr; } vtkSMPThreadLocal::iterator cItr; vtkSMPThreadLocal::iterator cEnd = this->ThreadCount.end(); for (cItr = this->ThreadCount.begin(); cItr != cEnd; ++cItr) { count += *cItr; } this->StdDev = sqrt(sigma / static_cast(count)); } static void Execute(vtkIdType numPts, float* distances, double mean, double& sigma) { ComputeStdDev stdDev(distances, mean); vtkSMPTools::For(0, numPts, stdDev); sigma = stdDev.StdDev; } }; // ComputeStdDev //------------------------------------------------------------------------------ // Statistics are computed, now filter the points struct RemoveOutliers { double Mean; double Sigma; float* Distances; vtkIdType* PointMap; RemoveOutliers(double mean, double sigma, float* distances, vtkIdType* map) : Mean(mean) , Sigma(sigma) , Distances(distances) , PointMap(map) { } void operator()(vtkIdType ptId, vtkIdType endPtId) { vtkIdType* map = this->PointMap + ptId; float* d = this->Distances + ptId; double mean = this->Mean, sigma = this->Sigma; for (; ptId < endPtId; ++ptId) { *map++ = (fabs(*d++ - mean) <= sigma ? 1 : -1); } } static void Execute(vtkIdType numPts, float* distances, double mean, double sigma, vtkIdType* map) { RemoveOutliers remove(mean, sigma, distances, map); vtkSMPTools::For(0, numPts, remove); } }; // RemoveOutliers } // anonymous namespace //================= Begin class proper ======================================= //------------------------------------------------------------------------------ vtkStatisticalOutlierRemoval::vtkStatisticalOutlierRemoval() { this->SampleSize = 25; this->StandardDeviationFactor = 1.0; this->Locator = vtkStaticPointLocator::New(); this->ComputedMean = 0.0; this->ComputedStandardDeviation = 0.0; } //------------------------------------------------------------------------------ vtkStatisticalOutlierRemoval::~vtkStatisticalOutlierRemoval() { this->SetLocator(nullptr); } //------------------------------------------------------------------------------ // Traverse all the input points and gather statistics about average distance // between them, and the standard deviation of variation. Then filter points // within a specified deviation from the mean. int vtkStatisticalOutlierRemoval::FilterPoints(vtkPointSet* input) { // Perform the point removal // Start by building the locator if (!this->Locator) { vtkErrorMacro(<< "Point locator required\n"); return 0; } this->Locator->SetDataSet(input); this->Locator->BuildLocator(); // Compute statistics across the point cloud. Start my computing // mean distance to N closest neighbors. vtkIdType numPts = input->GetNumberOfPoints(); float* dist = new float[numPts]; double mean = 0.0, sigma = 0.0; ComputeMeanDistanceWorker worker; if (!vtkArrayDispatch::DispatchByArray::Execute( input->GetPoints()->GetData(), worker, this, dist, mean)) { worker(input->GetPoints()->GetData(), this, dist, mean); } // At this point the mean distance for each point, and across the point // cloud is known. Now compute global standard deviation. ComputeStdDev::Execute(numPts, dist, mean, sigma); // Finally filter the points based on specified deviation range. RemoveOutliers::Execute( numPts, dist, mean, this->StandardDeviationFactor * sigma, this->PointMap); // Assign derived variable this->ComputedMean = mean; this->ComputedStandardDeviation = sigma; // Clean up delete[] dist; return 1; } //------------------------------------------------------------------------------ void vtkStatisticalOutlierRemoval::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Sample Size: " << this->SampleSize << "\n"; os << indent << "Standard Deviation Factor: " << this->StandardDeviationFactor << "\n"; os << indent << "Locator: " << this->Locator << "\n"; os << indent << "Computed Mean: " << this->ComputedMean << "\n"; os << indent << "Computed Standard Deviation: " << this->ComputedStandardDeviation << "\n"; } VTK_ABI_NAMESPACE_END