// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include #include "vtkEuclideanClusterExtraction.h" #include "vtkAbstractPointLocator.h" #include "vtkFloatArray.h" #include "vtkIdList.h" #include "vtkIdTypeArray.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPointSet.h" #include "vtkPoints.h" #include "vtkStaticPointLocator.h" VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkEuclideanClusterExtraction); vtkCxxSetObjectMacro(vtkEuclideanClusterExtraction, Locator, vtkAbstractPointLocator); //------------------------------------------------------------------------------ // Construct with default extraction mode to extract largest cluster. vtkEuclideanClusterExtraction::vtkEuclideanClusterExtraction() { this->ClusterSizes = vtkIdTypeArray::New(); this->ExtractionMode = VTK_EXTRACT_LARGEST_CLUSTER; this->ColorClusters = false; this->ScalarConnectivity = false; this->ScalarRange[0] = 0.0; this->ScalarRange[1] = 1.0; this->ClosestPoint[0] = this->ClosestPoint[1] = this->ClosestPoint[2] = 0.0; this->Locator = vtkStaticPointLocator::New(); this->NeighborScalars = vtkFloatArray::New(); this->NeighborScalars->Allocate(64); this->NeighborPointIds = vtkIdList::New(); this->NeighborPointIds->Allocate(64); this->Seeds = vtkIdList::New(); this->SpecifiedClusterIds = vtkIdList::New(); this->NewScalars = nullptr; } //------------------------------------------------------------------------------ vtkEuclideanClusterExtraction::~vtkEuclideanClusterExtraction() { this->SetLocator(nullptr); this->ClusterSizes->Delete(); this->NeighborScalars->Delete(); this->NeighborPointIds->Delete(); this->Seeds->Delete(); this->SpecifiedClusterIds->Delete(); } //------------------------------------------------------------------------------ int vtkEuclideanClusterExtraction::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the input and output vtkPointSet* input = vtkPointSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkIdType numPts, i, ptId; vtkPoints* newPts; int maxPointsInCluster, clusterId, largestClusterId = 0; vtkPointData *pd = input->GetPointData(), *outputPD = output->GetPointData(); vtkDebugMacro(<< "Executing point clustering filter."); // Check input/allocate storage // if ((numPts = input->GetNumberOfPoints()) < 1) { vtkDebugMacro(<< "No data to cluster!"); return 1; } vtkPoints* inPts = input->GetPoints(); // Need to build a locator if (!this->Locator) { vtkErrorMacro(<< "Point locator required\n"); return 0; } this->Locator->SetDataSet(input); this->Locator->BuildLocator(); // See whether to consider scalar connectivity // this->InScalars = input->GetPointData()->GetScalars(); if (!this->ScalarConnectivity) { this->InScalars = nullptr; } else { this->NeighborScalars->SetNumberOfComponents(this->InScalars->GetNumberOfComponents()); this->ScalarRange[1] = std::max(this->ScalarRange[1], this->ScalarRange[0]); } // Initialize. Keep track of the points visited. // this->Visited = new char[numPts]; std::fill_n(this->Visited, numPts, static_cast(0)); this->ClusterSizes->Reset(); this->PointMap = new vtkIdType[numPts]; std::fill_n(this->PointMap, numPts, static_cast(-1)); this->NewScalars = vtkIdTypeArray::New(); this->NewScalars->SetName("ClusterId"); this->NewScalars->SetNumberOfTuples(numPts); newPts = vtkPoints::New(); newPts->SetDataType(input->GetPoints()->GetDataType()); newPts->Allocate(numPts); // Traverse all points marking those visited. Each new search // starts a new connected cluster. Connected clusters grow // using a connected wave propagation. // this->Wave = vtkIdList::New(); this->Wave->Allocate(numPts / 4 + 1, numPts); this->Wave2 = vtkIdList::New(); this->Wave2->Allocate(numPts / 4 + 1, numPts); this->PointNumber = 0; this->ClusterNumber = 0; maxPointsInCluster = 0; this->PointIds = vtkIdList::New(); this->PointIds->Allocate(8, VTK_CELL_SIZE); if (this->ExtractionMode != VTK_EXTRACT_POINT_SEEDED_CLUSTERS && this->ExtractionMode != VTK_EXTRACT_CLOSEST_POINT_CLUSTER) { // visit all points assigning cluster number for (ptId = 0; ptId < numPts; ptId++) { if (ptId && !(ptId % 10000)) { this->UpdateProgress(0.1 + 0.8 * ptId / numPts); } if (!this->Visited[ptId]) { this->NumPointsInCluster = 0; this->InsertIntoWave(this->Wave, ptId); this->TraverseAndMark(inPts); if (this->NumPointsInCluster > maxPointsInCluster) { maxPointsInCluster = this->NumPointsInCluster; largestClusterId = this->ClusterNumber; } if (this->NumPointsInCluster > 0) { this->ClusterSizes->InsertValue(this->ClusterNumber++, this->NumPointsInCluster); } this->Wave->Reset(); this->Wave2->Reset(); } } } else // clusters have been seeded, everything considered in same cluster { this->NumPointsInCluster = 0; if (this->ExtractionMode == VTK_EXTRACT_POINT_SEEDED_CLUSTERS) { this->NumPointsInCluster = 0; for (i = 0; i < this->Seeds->GetNumberOfIds(); i++) { ptId = this->Seeds->GetId(i); if (ptId >= 0) { this->InsertIntoWave(this->Wave, ptId); } } } else if (this->ExtractionMode == VTK_EXTRACT_CLOSEST_POINT_CLUSTER) { // loop over points, find closest one ptId = this->Locator->FindClosestPoint(this->ClosestPoint); this->InsertIntoWave(this->Wave, ptId); } this->UpdateProgress(0.5); // mark all seeded clusters this->TraverseAndMark(inPts); this->ClusterSizes->InsertValue(this->ClusterNumber, this->NumPointsInCluster); this->UpdateProgress(0.9); } vtkDebugMacro(<< "Extracted " << this->ClusterNumber << " cluster(s)"); this->Wave->Delete(); this->Wave2->Delete(); delete[] this->Visited; // Now that points have been marked, traverse the PointMap pulling // everything that has been visited and is selected for output. outputPD->CopyAllocate(pd); if (this->ExtractionMode == VTK_EXTRACT_POINT_SEEDED_CLUSTERS || this->ExtractionMode == VTK_EXTRACT_CLOSEST_POINT_CLUSTER || this->ExtractionMode == VTK_EXTRACT_ALL_CLUSTERS) { // extract any point that's been visited for (ptId = 0; ptId < numPts; ptId++) { if (this->PointMap[ptId] >= 0) { newPts->InsertPoint(this->PointMap[ptId], inPts->GetPoint(ptId)); outputPD->CopyData(pd, ptId, this->PointMap[ptId]); } } } else if (this->ExtractionMode == VTK_EXTRACT_SPECIFIED_CLUSTERS) { bool inCluster; for (ptId = 0; ptId < numPts; ptId++) { if (this->PointMap[ptId] >= 0) { clusterId = this->NewScalars->GetValue(this->PointMap[ptId]); for (inCluster = false, i = 0; i < this->SpecifiedClusterIds->GetNumberOfIds(); i++) { if (clusterId == this->SpecifiedClusterIds->GetId(i)) { inCluster = true; break; } } if (inCluster) { newPts->InsertPoint(this->PointMap[ptId], inPts->GetPoint(ptId)); outputPD->CopyData(pd, ptId, this->PointMap[ptId]); } } } } else // extract largest cluster { for (ptId = 0; ptId < numPts; ptId++) { if (this->PointMap[ptId] >= 0) { clusterId = this->NewScalars->GetValue(this->PointMap[ptId]); if (clusterId == largestClusterId) { newPts->InsertPoint(this->PointMap[ptId], inPts->GetPoint(ptId)); outputPD->CopyData(pd, ptId, this->PointMap[ptId]); } } } } // if coloring clusters; send down new scalar data if (this->ColorClusters) { int idx = outputPD->AddArray(this->NewScalars); outputPD->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); } this->NewScalars->Delete(); newPts->Squeeze(); output->SetPoints(newPts); delete[] this->PointMap; this->PointIds->Delete(); #ifndef NDEBUG // print out some debugging information int num = this->GetNumberOfExtractedClusters(); int count = 0; (void)count; // Only used in Debug builds. for (int ii = 0; ii < num; ii++) { count += this->ClusterSizes->GetValue(ii); } #endif vtkDebugMacro(<< "Total # of points accounted for: " << count); vtkDebugMacro(<< "Extracted " << newPts->GetNumberOfPoints() << " points"); newPts->Delete(); return 1; } //------------------------------------------------------------------------------ // Insert point into connected wave. Check to make sure it satisfies connectivity // criterion (if enabled). void vtkEuclideanClusterExtraction::InsertIntoWave(vtkIdList* wave, vtkIdType ptId) { this->Visited[ptId] = 1; if (this->InScalars) // is scalar connectivity enabled? { double s = this->InScalars->GetTuple1(ptId); if (s >= this->ScalarRange[0] && s <= this->ScalarRange[1]) { wave->InsertNextId(ptId); } } else { wave->InsertNextId(ptId); } } //------------------------------------------------------------------------------ // Update current point information including updating cluster number. Note: // traversal occurs across proximally located points, possibly limited by // scalar connectivity. // void vtkEuclideanClusterExtraction::TraverseAndMark(vtkPoints* inPts) { vtkIdType i, j, numPts, numIds, ptId; vtkIdList* tmpWave; double x[3]; while ((numIds = this->Wave->GetNumberOfIds()) > 0) { for (i = 0; i < numIds; i++) // for all points in this wave { ptId = this->Wave->GetId(i); this->PointMap[ptId] = this->PointNumber++; this->NewScalars->SetValue(this->PointMap[ptId], this->ClusterNumber); this->NumPointsInCluster++; inPts->GetPoint(ptId, x); this->Locator->FindPointsWithinRadius(this->Radius, x, this->NeighborPointIds); numPts = this->NeighborPointIds->GetNumberOfIds(); for (j = 0; j < numPts; ++j) { ptId = this->NeighborPointIds->GetId(j); if (!this->Visited[ptId]) { this->InsertIntoWave(this->Wave2, ptId); } // if point not yet visited } // for all neighbors } // for all cells in this connected wave tmpWave = this->Wave; this->Wave = this->Wave2; this->Wave2 = tmpWave; tmpWave->Reset(); } // while wave is not empty } //------------------------------------------------------------------------------ // Obtain the number of connected clusters. int vtkEuclideanClusterExtraction::GetNumberOfExtractedClusters() { return this->ClusterSizes->GetMaxId() + 1; } //------------------------------------------------------------------------------ // Initialize list of point ids used to seed clusters. void vtkEuclideanClusterExtraction::InitializeSeedList() { this->Modified(); this->Seeds->Reset(); } //------------------------------------------------------------------------------ // Add a seed id. Note: ids are 0-offset. void vtkEuclideanClusterExtraction::AddSeed(vtkIdType id) { this->Modified(); this->Seeds->InsertNextId(id); } //------------------------------------------------------------------------------ // Delete a seed id. Note: ids are 0-offset. void vtkEuclideanClusterExtraction::DeleteSeed(vtkIdType id) { this->Modified(); this->Seeds->DeleteId(id); } //------------------------------------------------------------------------------ // Initialize list of cluster ids to extract. void vtkEuclideanClusterExtraction::InitializeSpecifiedClusterList() { this->Modified(); this->SpecifiedClusterIds->Reset(); } //------------------------------------------------------------------------------ // Add a cluster id to extract. Note: ids are 0-offset. void vtkEuclideanClusterExtraction::AddSpecifiedCluster(int id) { this->Modified(); this->SpecifiedClusterIds->InsertNextId(id); } //------------------------------------------------------------------------------ // Delete a cluster id to extract. Note: ids are 0-offset. void vtkEuclideanClusterExtraction::DeleteSpecifiedCluster(int id) { this->Modified(); this->SpecifiedClusterIds->DeleteId(id); } //------------------------------------------------------------------------------ int vtkEuclideanClusterExtraction::FillInputPortInformation(int, vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPointSet"); return 1; } //------------------------------------------------------------------------------ void vtkEuclideanClusterExtraction::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Extraction Mode: "; os << this->GetExtractionModeAsString() << "\n"; os << indent << "Closest Point: (" << this->ClosestPoint[0] << ", " << this->ClosestPoint[1] << ", " << this->ClosestPoint[2] << ")\n"; os << indent << "Color Clusters: " << (this->ColorClusters ? "On\n" : "Off\n"); os << indent << "Scalar Connectivity: " << (this->ScalarConnectivity ? "On\n" : "Off\n"); double* range = this->GetScalarRange(); os << indent << "Scalar Range: (" << range[0] << ", " << range[1] << ")\n"; os << indent << "Locator: " << this->Locator << "\n"; } VTK_ABI_NAMESPACE_END