// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkPointSetToOctreeImageFilter.h" #include "vtkArrayDispatch.h" #include "vtkArrayDispatchDataSetArrayList.h" #include "vtkAtomicMutex.h" #include "vtkCellData.h" #include "vtkDataArrayRange.h" #include "vtkFloatArray.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPartitionedDataSet.h" #include "vtkPointData.h" #include "vtkPointSet.h" #include "vtkSMPTools.h" #include "vtkUnsignedCharArray.h" #include VTK_ABI_NAMESPACE_BEGIN //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkPointSetToOctreeImageFilter); //------------------------------------------------------------------------------ vtkPointSetToOctreeImageFilter::vtkPointSetToOctreeImageFilter() { this->SetInputArrayToProcess( 0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, vtkDataSetAttributes::SCALARS); } //------------------------------------------------------------------------------ vtkPointSetToOctreeImageFilter::~vtkPointSetToOctreeImageFilter() = default; //------------------------------------------------------------------------------ void vtkPointSetToOctreeImageFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "NumberOfPointsPerCell: " << this->NumberOfPointsPerCell << endl; os << indent << "ProcessInputPointArray: " << this->ProcessInputPointArray << endl; os << indent << "ComputeLastValue: " << this->ComputeLastValue << endl; os << indent << "ComputeMin: " << this->ComputeMin << endl; os << indent << "ComputeMax: " << this->ComputeMax << endl; os << indent << "ComputeCount: " << this->ComputeCount << endl; os << indent << "ComputeSum: " << this->ComputeSum << endl; os << indent << "ComputeMean: " << this->ComputeMean << endl; } //------------------------------------------------------------------------------ int vtkPointSetToOctreeImageFilter::FillInputPortInformation( int vtkNotUsed(port), vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPointSet"); return 1; } //------------------------------------------------------------------------------ template struct vtkPointSetToOctreeImageFilter::PointSetToImageFunctor { vtkPointSet* Input; vtkImageData* Output; TPointsArray* Points; vtkUnsignedCharArray* Octree; vtkDataArray* InField; vtkFloatArray* OutField; const std::vector& Functions; bool UseFieldArray; double Origin[3]; double Spacing[3]; double Spacing_2[3]; int Dimensions[3]; int Extent[6]; std::unique_ptr Locks; PointSetToImageFunctor(vtkPointSet* input, vtkImageData* output, TPointsArray* points, vtkUnsignedCharArray* octree, vtkDataArray* inField, vtkFloatArray* outField, const std::vector& functions) : Input(input) , Output(output) , Points(points) , Octree(octree) , InField(inField) , OutField(outField) , Functions(functions) , UseFieldArray(inField != nullptr) { output->GetOrigin(this->Origin); output->GetSpacing(this->Spacing); this->Spacing_2[0] = 0.5 * this->Spacing[0]; this->Spacing_2[1] = 0.5 * this->Spacing[1]; this->Spacing_2[2] = 0.5 * this->Spacing[2]; output->GetDimensions(this->Dimensions); output->GetExtent(this->Extent); this->Locks = std::unique_ptr(new vtkAtomicMutex[this->Output->GetNumberOfCells()]); } void Initialize() {} void operator()(vtkIdType begin, vtkIdType end) { double* origin = this->Origin; double* spacing = this->Spacing; double* spacing_2 = this->Spacing_2; int* dimensions = this->Dimensions; int* extent = this->Extent; vtkImageData* output = this->Output; int numFunctions = static_cast(this->Functions.size()); // no need to iterate over the mean function since it will be computed in the reduce step if (this->UseFieldArray && this->Functions.back() == FieldFunctions::MEAN) { --numFunctions; } double outPt[3]; int ijk[3]; vtkIdType outPtId; vtkIdType outCellId; unsigned char octreeValue; float inFieldValue; const auto inPoints = vtk::DataArrayTupleRange<3>(this->Points); unsigned char* octree = this->Octree->GetPointer(0); vtk::detail::ValueRange inField; vtk::detail::TupleRange, vtk::detail::DynamicTupleSize> outField; if (this->UseFieldArray) { inField = vtk::DataArrayValueRange<1>(this->InField); outField = vtk::DataArrayTupleRange(this->OutField); } for (vtkIdType i = begin; i < end; ++i) { // get point const auto& inPt = inPoints[i]; // calculate ijk ijk[0] = static_cast((inPt[0] - origin[0]) / spacing[0]); ijk[0] = std::clamp(ijk[0], extent[0], extent[1] - 1); ijk[1] = static_cast((inPt[1] - origin[1]) / spacing[1]); ijk[1] = std::clamp(ijk[1], extent[2], extent[3] - 1); ijk[2] = static_cast((inPt[2] - origin[2]) / spacing[2]); ijk[2] = std::clamp(ijk[2], extent[4], extent[5] - 1); // calculate output point and cell id outPtId = ijk[0] + ijk[1] * dimensions[0] + ijk[2] * dimensions[0] * dimensions[1]; outCellId = ijk[0] + ijk[1] * extent[1] + ijk[2] * extent[1] * extent[3]; // get output point output->GetPoint(outPtId, outPt); // add half spacing to output point outPt[0] += spacing_2[0]; outPt[1] += spacing_2[1]; outPt[2] += spacing_2[2]; // calculate scalar octree value octreeValue = inPt[0] > outPt[0] ? 2 : 1; octreeValue *= inPt[1] > outPt[1] ? 4 : 1; octreeValue *= inPt[2] > outPt[2] ? 16 : 1; std::lock_guard lock(this->Locks[outCellId]); // this operation is deterministic because we perform a bitwise OR octree[outCellId] |= octreeValue; if (this->UseFieldArray) { auto outTuple = outField[outCellId]; inFieldValue = static_cast(inField[i]); for (int j = 0; j < numFunctions; ++j) { switch (this->Functions[j]) { case FieldFunctions::LAST_VALUE: outTuple[j] = inFieldValue; break; case FieldFunctions::MIN: outTuple[j] = std::min(static_cast(outTuple[j]), inFieldValue); break; case FieldFunctions::MAX: outTuple[j] = std::max(static_cast(outTuple[j]), inFieldValue); break; case FieldFunctions::COUNT: outTuple[j] += 1.0f; break; case FieldFunctions::SUM: outTuple[j] += inFieldValue; break; default: break; } } } } } void Reduce() { // Compute mean if (this->UseFieldArray && this->Functions.back() == FieldFunctions::MEAN) { vtkSMPTools::For(0, this->OutField->GetNumberOfTuples(), [&](vtkIdType begin, vtkIdType end) { auto outField = vtk::DataArrayTupleRange(this->OutField, begin, end); const int numFunctions = static_cast(this->Functions.size()); const int meanIndex = numFunctions - 1; const int sumIndex = numFunctions - 2; const int countIndex = numFunctions - 3; for (auto tuple : outField) { if (tuple[countIndex] != 0.0f) { tuple[meanIndex] = static_cast(tuple[sumIndex]) / tuple[countIndex]; } } }); } } }; //------------------------------------------------------------------------------ struct vtkPointSetToOctreeImageFilter::PointSetToImageWorker { template void operator()(TPointsArray* inPointsArray, vtkUnsignedCharArray* octreeArray, vtkPointSet* input, vtkImageData* output, vtkDataArray* inField, vtkFloatArray* outField, const std::vector& functions) { PointSetToImageFunctor functor( input, output, inPointsArray, octreeArray, inField, outField, functions); vtkSMPTools::For(0, input->GetNumberOfPoints(), functor); } }; //------------------------------------------------------------------------------ int vtkPointSetToOctreeImageFilter::RequestData( vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the input vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkPointSet* input = vtkPointSet::GetData(inInfo); // get the output vtkInformation* outInfo = outputVector->GetInformationObject(0); vtkPartitionedDataSet* outputPDS = vtkPartitionedDataSet::GetData(outInfo); if (!input || input->GetNumberOfPoints() == 0) { vtkErrorMacro("No input or empty input."); return 0; } if (this->NumberOfPointsPerCell > input->GetNumberOfPoints()) { vtkErrorMacro("NumberOfPointsPerCell must be less than or equal to the number of points."); return 0; } // get input points information double pointSetBounds[6]; input->GetBounds(pointSetBounds); // compute output image information as it's done in vtkStaticPointLocator when Automatic is on. double imageBounds[6]; int nDivs[3]; vtkIdType numBuckets = static_cast(static_cast(input->GetNumberOfPoints()) / static_cast(this->NumberOfPointsPerCell)); vtkBoundingBox bbox(pointSetBounds); bbox.ComputeDivisions(numBuckets, imageBounds, nDivs); const double origin[3] = { imageBounds[0], imageBounds[2], imageBounds[4] }; const double spacing[3] = { (imageBounds[1] - imageBounds[0]) / static_cast(nDivs[0]), (imageBounds[3] - imageBounds[2]) / static_cast(nDivs[1]), (imageBounds[5] - imageBounds[4]) / static_cast(nDivs[2]) }; const int dimensions[3] = { 1 + nDivs[0], 1 + nDivs[1], 1 + nDivs[2] }; const vtkIdType numberOfCells = nDivs[0] * nDivs[1] * nDivs[2]; // create output image octree array vtkNew octree; octree->SetName("octree"); octree->SetNumberOfValues(numberOfCells); vtkSMPTools::Fill(octree->GetPointer(0), octree->GetPointer(0) + numberOfCells, 0); // create output image field array vtkSmartPointer outField = nullptr; vtkDataArray* inField = nullptr; std::vector functions; if (this->ProcessInputPointArray) { inField = this->GetInputArrayToProcess(0, inputVector); if (!inField) { vtkErrorMacro("Array to process is null."); return 0; } if (inField->GetNumberOfTuples() != input->GetNumberOfPoints()) { vtkErrorMacro("Array to process must have as many tuples as the number of points."); return 0; } if (inField->GetNumberOfComponents() != 1) { vtkErrorMacro("Array to process '" << inField->GetName() << "' must have 1 component."); return 0; } int numberOfFunctions = this->ComputeLastValue + this->ComputeMin + this->ComputeMax + (this->ComputeCount || this->ComputeMean) + (this->ComputeSum || this->ComputeMean) + this->ComputeMean; if (numberOfFunctions == 0) { vtkErrorMacro("No function has been requested to be computed."); return 0; } functions.resize(static_cast(numberOfFunctions)); outField = vtkSmartPointer::New(); outField->SetName(inField->GetName()); outField->SetNumberOfComponents(numberOfFunctions); int counter = 0; if (this->ComputeLastValue) { functions[counter] = FieldFunctions::LAST_VALUE; outField->SetComponentName(counter++, "LastValue"); } if (this->ComputeMin) { functions[counter] = FieldFunctions::MIN; outField->SetComponentName(counter++, "Min"); } if (this->ComputeMax) { functions[counter] = FieldFunctions::MAX; outField->SetComponentName(counter++, "Max"); } if (this->ComputeCount || this->ComputeMean) { functions[counter] = FieldFunctions::COUNT; outField->SetComponentName(counter++, "Count"); } if (this->ComputeSum || this->ComputeMean) { functions[counter] = FieldFunctions::SUM; outField->SetComponentName(counter++, "Sum"); } if (this->ComputeMean) { functions[counter] = FieldFunctions::MEAN; outField->SetComponentName(counter++, "Mean"); } outField->SetNumberOfTuples(numberOfCells); // initialize output image field array std::vector defaultValues(functions.size()); for (int i = 0; i < numberOfFunctions; ++i) { switch (functions[i]) { case FieldFunctions::LAST_VALUE: defaultValues[i] = 0.0f; break; case FieldFunctions::MIN: defaultValues[i] = std::numeric_limits::max(); break; case FieldFunctions::MAX: defaultValues[i] = std::numeric_limits::lowest(); break; case FieldFunctions::COUNT: defaultValues[i] = 0; break; case FieldFunctions::SUM: defaultValues[i] = 0; break; case FieldFunctions::MEAN: defaultValues[i] = 0; break; } } vtkSMPTools::For(0, numberOfCells, [&](vtkIdType begin, vtkIdType end) { auto outFieldRange = vtk::DataArrayTupleRange(outField, begin, end); for (auto outTuple : outFieldRange) { std::copy(defaultValues.begin(), defaultValues.end(), outTuple.begin()); } }); } // define output image vtkNew output; output->SetDimensions(dimensions); output->SetOrigin(origin); output->SetSpacing(spacing); output->GetCellData()->SetScalars(octree); if (inField) { output->GetCellData()->AddArray(outField); } // add output image to output partitioned dataset outputPDS->SetNumberOfPartitions(1); outputPDS->SetPartition(0, output); // fill octree and field arrays auto inPointsArray = input->GetPoints()->GetData(); PointSetToImageWorker worker; using Dispatcher = vtkArrayDispatch::DispatchByArray; if (!Dispatcher::Execute( inPointsArray, worker, octree, input, output, inField, outField.Get(), functions)) { worker(inPointsArray, octree, input, output, inField, outField.Get(), functions); } return 1; } VTK_ABI_NAMESPACE_END