// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkImageMarchingCubes.h" #include "vtkCellArray.h" #include "vtkCommand.h" #include "vtkFloatArray.h" #include "vtkImageData.h" #include "vtkImageTransform.h" #include "vtkInformation.h" #include "vtkInformationExecutivePortKey.h" #include "vtkInformationVector.h" #include "vtkMarchingCubesTriangleCases.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkStreamingDemandDrivenPipeline.h" #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkImageMarchingCubes); //------------------------------------------------------------------------------ // Description: // Construct object with initial range (0,1) and single contour value // of 0.0. ComputeNormal is on, ComputeGradients is off and ComputeScalars is on. vtkImageMarchingCubes::vtkImageMarchingCubes() { this->ContourValues = vtkContourValues::New(); this->ComputeNormals = 1; this->ComputeGradients = 0; this->ComputeScalars = 1; this->LocatorPointIds = nullptr; this->InputMemoryLimit = 10240; // 10 mega Bytes } vtkImageMarchingCubes::~vtkImageMarchingCubes() { this->ContourValues->Delete(); } // Description: // Overload standard modified time function. If contour values are modified, // then this object is modified as well. vtkMTimeType vtkImageMarchingCubes::GetMTime() { vtkMTimeType mTime = this->Superclass::GetMTime(); vtkMTimeType contourValuesMTime = this->ContourValues->GetMTime(); mTime = (contourValuesMTime > mTime ? contourValuesMTime : mTime); return mTime; } //------------------------------------------------------------------------------ template int vtkImageMarchingCubesGetTypeSize(T*) { return sizeof(T); } //------------------------------------------------------------------------------ int vtkImageMarchingCubes::RequestUpdateExtent(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* vtkNotUsed(outputVector)) { // start with an empty UPDATE_EXTENT to ensure proper streaming // (the UPDATE_EXTENT will be set properly in RequestData()). int extent[6] = { 0, -1, 0, -1, 0, -1 }; vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), extent, 6); return 1; } //------------------------------------------------------------------------------ int vtkImageMarchingCubes::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 vtkImageData* inData = vtkImageData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkDemandDrivenPipeline* inputExec = vtkDemandDrivenPipeline::SafeDownCast(vtkExecutive::PRODUCER()->GetExecutive(inInfo)); vtkIdType numContours = this->ContourValues->GetNumberOfContours(); double* values = this->ContourValues->GetValues(); vtkDebugMacro("Starting Execute Method"); // Gradients must be computed (but not saved) if Compute normals is on. this->NeedGradients = this->ComputeGradients || this->ComputeNormals; // Determine the number of slices per request from input memory limit. int minSlicesPerChunk, chunkOverlap; if (this->NeedGradients) { minSlicesPerChunk = 4; chunkOverlap = 3; } else { minSlicesPerChunk = 2; chunkOverlap = 1; } inputExec->UpdateInformation(); // Each data type requires a different amount of memory. vtkIdType temp; switch (inData->GetScalarType()) { vtkTemplateMacro(temp = vtkImageMarchingCubesGetTypeSize(static_cast(nullptr))); default: vtkErrorMacro(<< "Could not determine input scalar type."); return 1; } // Get the WHOLE_EXTENT and divide it into chunks int extent[6]; inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), extent); // multiply by the area of each slice temp *= extent[1] - extent[0] + 1; temp *= extent[3] - extent[2] + 1; // temp holds memory per image. (+1 to avoid dividing by zero) this->NumberOfSlicesPerChunk = static_cast(this->InputMemoryLimit * 1024 / (temp + 1)); if (this->NumberOfSlicesPerChunk < minSlicesPerChunk) { vtkWarningMacro("Execute: Need " << minSlicesPerChunk * (temp / 1024) << " KB to load " << minSlicesPerChunk << " slices.\n"); this->NumberOfSlicesPerChunk = minSlicesPerChunk; } vtkDebugMacro("Execute: NumberOfSlicesPerChunk = " << this->NumberOfSlicesPerChunk); this->NumberOfSlicesPerChunk -= chunkOverlap; // Create the points, scalars, normals and Cell arrays for the output. // estimate the number of points from the volume dimensions vtkIdType estimatedSize = static_cast(extent[1] - extent[0] + 1); estimatedSize *= static_cast(extent[3] - extent[2] + 1); estimatedSize *= static_cast(extent[5] - extent[4] + 1); estimatedSize = static_cast(pow(1.0 * estimatedSize, 0.75)); estimatedSize = (estimatedSize / 1024) * 1024; // multiple of 1024 estimatedSize = std::max(estimatedSize, 1024); vtkDebugMacro(<< "Estimated number of points/triangles: " << estimatedSize); this->Points = vtkPoints::New(); this->Points->Allocate(estimatedSize, estimatedSize / 2); this->Triangles = vtkCellArray::New(); this->Triangles->AllocateEstimate(estimatedSize, 1); if (this->ComputeScalars) { this->Scalars = vtkFloatArray::New(); this->Scalars->Allocate(estimatedSize, estimatedSize / 2); } if (this->ComputeNormals) { this->Normals = vtkFloatArray::New(); this->Normals->SetNumberOfComponents(3); this->Normals->Allocate(3 * estimatedSize, 3 * estimatedSize / 2); } if (this->ComputeGradients) { this->Gradients = vtkFloatArray::New(); this->Gradients->SetNumberOfComponents(3); this->Gradients->Allocate(3 * estimatedSize, 3 * estimatedSize / 2); } // Initialize the internal point locator (edge table for one image of cubes). this->InitializeLocator(extent[0], extent[1], extent[2], extent[3]); // Loop through the chunks running marching cubes on each one int zMin = extent[4]; int zMax = extent[5]; for (int chunkMin = zMin, chunkMax; chunkMin < zMax && !this->CheckAbort(); chunkMin = chunkMax) { // Get the chunk from the input chunkMax = chunkMin + this->NumberOfSlicesPerChunk; chunkMax = std::min(chunkMax, zMax); extent[4] = chunkMin; extent[5] = chunkMax; // Expand if computing gradients with central differences if (this->NeedGradients) { --extent[4]; ++extent[5]; } // Don't go over boundary of data. extent[4] = std::max(extent[4], zMin); extent[5] = std::min(extent[5], zMax); // Get the chunk from the input inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), extent, 6); inputExec->Update(); this->March(inData, chunkMin, chunkMax, numContours, values); if (!this->GetAbortOutput()) { this->UpdateProgress(static_cast(chunkMax - zMin) / (zMax - zMin)); } if (vtkDataObject::GetGlobalReleaseDataFlag() || inInfo->Has(vtkStreamingDemandDrivenPipeline::RELEASE_DATA())) { inData->ReleaseData(); } } // Put results in our output vtkDebugMacro(<< "Created: " << this->Points->GetNumberOfPoints() << " points, " << this->Triangles->GetNumberOfCells() << " triangles"); output->SetPoints(this->Points); this->Points->Delete(); this->Points = nullptr; output->SetPolys(this->Triangles); this->Triangles->Delete(); this->Triangles = nullptr; if (this->ComputeScalars) { int idx = output->GetPointData()->AddArray(this->Scalars); output->GetPointData()->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); this->Scalars->Delete(); this->Scalars = nullptr; } if (this->ComputeNormals) { output->GetPointData()->SetNormals(this->Normals); this->Normals->Delete(); this->Normals = nullptr; } // Recover extra space. output->Squeeze(); vtkImageTransform::TransformPointSet(inData, output); // release the locators memory this->DeleteLocator(); return 1; } //------------------------------------------------------------------------------ // This method uses central differences to compute the gradient // of a point. Note: This method assumes that max > min for all 3 axes! // It does not consider the dataset spacing. // b0 (b1, b2) indicates the boundary conditions for the three axes.: // b0 = -1 => pixel is on x axis minimum of region. // b0 = 0 => no boundary conditions // b0 = +1 => pixel is on x axis maximum of region. template void vtkImageMarchingCubesComputePointGradient( T* ptr, double* g, int inc0, int inc1, int inc2, short b0, short b1, short b2) { if (b0 < 0) { g[0] = (double)(ptr[inc0]) - (double)(*ptr); } else if (b0 > 0) { g[0] = (double)(*ptr) - (double)(ptr[-inc0]); } else { g[0] = (double)(ptr[inc0]) - (double)(ptr[-inc0]); } if (b1 < 0) { g[1] = (double)(ptr[inc1]) - (double)(*ptr); } else if (b1 > 0) { g[1] = (double)(*ptr) - (double)(ptr[-inc1]); } else { g[1] = (double)(ptr[inc1]) - (double)(ptr[-inc1]); } if (b2 < 0) { g[2] = (double)(ptr[inc2]) - (double)(*ptr); } else if (b2 > 0) { g[2] = (double)(*ptr) - (double)(ptr[-inc2]); } else { g[2] = (double)(ptr[inc2]) - (double)(ptr[-inc2]); } } //------------------------------------------------------------------------------ // This method interpolates vertices to make a new point. template int vtkImageMarchingCubesMakeNewPoint(vtkImageMarchingCubes* self, int idx0, int idx1, int idx2, int inc0, int inc1, int inc2, T* ptr, int edge, int* imageExtent, double value) { int edgeAxis = 0; T* ptrB = nullptr; double temp, pt[3]; // decode the edge into starting point and axis direction switch (edge) { case 0: // 0,1 ptrB = ptr + inc0; edgeAxis = 0; break; case 1: // 1,2 ++idx0; ptr += inc0; ptrB = ptr + inc1; edgeAxis = 1; break; case 2: // 3,2 ++idx1; ptr += inc1; ptrB = ptr + inc0; edgeAxis = 0; break; case 3: // 0,3 ptrB = ptr + inc1; edgeAxis = 1; break; case 4: // 4,5 ++idx2; ptr += inc2; ptrB = ptr + inc0; edgeAxis = 0; break; case 5: // 5,6 ++idx0; ++idx2; ptr += inc0 + inc2; ptrB = ptr + inc1; edgeAxis = 1; break; case 6: // 7,6 ++idx1; ++idx2; ptr += inc1 + inc2; ptrB = ptr + inc0; edgeAxis = 0; break; case 7: // 4,7 ++idx2; ptr += inc2; ptrB = ptr + inc1; edgeAxis = 1; break; case 8: // 0,4 ptrB = ptr + inc2; edgeAxis = 2; break; case 9: // 1,5 ++idx0; ptr += inc0; ptrB = ptr + inc2; edgeAxis = 2; break; case 10: // 3,7 ++idx1; ptr += inc1; ptrB = ptr + inc2; edgeAxis = 2; break; case 11: // 2,6 ++idx0; ++idx1; ptr += inc0 + inc1; ptrB = ptr + inc2; edgeAxis = 2; break; } // interpolation factor temp = (value - *ptr) / (*ptrB - *ptr); // interpolate the point position switch (edgeAxis) { case 0: pt[0] = (double)idx0 + temp; pt[1] = (double)idx1; pt[2] = (double)idx2; break; case 1: pt[0] = (double)idx0; pt[1] = (double)idx1 + temp; pt[2] = (double)idx2; break; case 2: pt[0] = (double)idx0; pt[1] = (double)idx1; pt[2] = (double)idx2 + temp; break; } // Save the scale if we are generating scalars if (self->ComputeScalars) { self->Scalars->InsertNextValue(value); } // Interpolate to find normal from vectors. if (self->NeedGradients) { short b0, b1, b2; double g[3], gB[3]; // Find boundary conditions and compute gradient (first point) b0 = (idx0 == imageExtent[1]); if (idx0 == imageExtent[0]) { b0 = -1; } b1 = (idx1 == imageExtent[3]); if (idx1 == imageExtent[2]) { b1 = -1; } b2 = (idx2 == imageExtent[5]); if (idx2 == imageExtent[4]) { b2 = -1; } vtkImageMarchingCubesComputePointGradient(ptr, g, inc0, inc1, inc2, b0, b1, b2); // Find boundary conditions and compute gradient (second point) switch (edgeAxis) { case 0: ++idx0; b0 = (idx0 == imageExtent[1]); break; case 1: ++idx1; b1 = (idx1 == imageExtent[3]); break; case 2: ++idx2; b2 = (idx2 == imageExtent[5]); break; } vtkImageMarchingCubesComputePointGradient(ptrB, gB, inc0, inc1, inc2, b0, b1, b2); // Interpolate Gradient g[0] = g[0] + temp * (gB[0] - g[0]); g[1] = g[1] + temp * (gB[1] - g[1]); g[2] = g[2] + temp * (gB[2] - g[2]); if (self->ComputeGradients) { self->Gradients->InsertNextTuple(g); } if (self->ComputeNormals) { temp = -1.0 / sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]); g[0] *= temp; g[1] *= temp; g[2] *= temp; self->Normals->InsertNextTuple(g); } } return self->Points->InsertNextPoint(pt); } //------------------------------------------------------------------------------ // This method runs marching cubes on one cube. template void vtkImageMarchingCubesHandleCube(vtkImageMarchingCubes* self, int cellX, int cellY, int cellZ, vtkImageData* inData, T* ptr, int numContours, double* values) { vtkIdType inc0, inc1, inc2; int valueIdx; double value; int cubeIndex, ii; vtkIdType pointIds[3]; vtkMarchingCubesTriangleCases *triCase, *triCases; vtkInformation* inInfo = self->GetExecutive()->GetInputInformation(0, 0); triCases = vtkMarchingCubesTriangleCases::GetCases(); inData->GetIncrements(inc0, inc1, inc2); for (valueIdx = 0; valueIdx < numContours; ++valueIdx) { value = values[valueIdx]; // compute the case index cubeIndex = 0; if ((double)(ptr[0]) > value) { cubeIndex += 1; } if ((double)(ptr[inc0]) > value) { cubeIndex += 2; } if ((double)(ptr[inc0 + inc1]) > value) { cubeIndex += 4; } if ((double)(ptr[inc1]) > value) { cubeIndex += 8; } if ((double)(ptr[inc2]) > value) { cubeIndex += 16; } if ((double)(ptr[inc0 + inc2]) > value) { cubeIndex += 32; } if ((double)(ptr[inc0 + inc1 + inc2]) > value) { cubeIndex += 64; } if ((double)(ptr[inc1 + inc2]) > value) { cubeIndex += 128; } // Make sure we have triangles if (cubeIndex != 0 && cubeIndex != 255) { // Get edges. triCase = triCases + cubeIndex; int* edge = triCase->edges; // loop over triangles while (*edge > -1) { for (ii = 0; ii < 3; ++ii, ++edge) // insert triangle { // Get the index of the point pointIds[ii] = self->GetLocatorPoint(cellX, cellY, *edge); // If the point has not been created yet if (pointIds[ii] == -1) { int* extent = inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()); pointIds[ii] = vtkImageMarchingCubesMakeNewPoint( self, cellX, cellY, cellZ, inc0, inc1, inc2, ptr, *edge, extent, value); self->AddLocatorPoint(cellX, cellY, *edge, pointIds[ii]); } } self->Triangles->InsertNextCell(3, pointIds); } // for each triangle } } } //------------------------------------------------------------------------------ template void vtkImageMarchingCubesMarch(vtkImageMarchingCubes* self, vtkImageData* inData, T* ptr, int chunkMin, int chunkMax, int numContours, double* values) { int idx0, idx1, idx2; int min0, max0, min1, max1, min2, max2; vtkIdType inc0, inc1, inc2; T *ptr0, *ptr1, *ptr2; unsigned long target, count; // avoid warnings (void)ptr; // Get information to loop through images. inData->GetExtent(min0, max0, min1, max1, min2, max2); ptr2 = (T*)(inData->GetScalarPointer(min0, min1, chunkMin)); inData->GetIncrements(inc0, inc1, inc2); // Setup the abort interval target = (unsigned long)((max0 - min0 + 1) * (max1 - min1 + 1) / 50.0); ++target; count = 0; // Loop over all the cubes for (idx2 = chunkMin; idx2 < chunkMax; ++idx2) { ptr1 = ptr2; for (idx1 = min1; idx1 < max1; ++idx1) { if (!(count % target)) { if (self->CheckAbort()) { return; } } count++; // continue with last loop ptr0 = ptr1; for (idx0 = min0; idx0 < max0; ++idx0) { // put magnitudes into the cube structure. vtkImageMarchingCubesHandleCube(self, idx0, idx1, idx2, inData, ptr0, numContours, values); ptr0 += inc0; } ptr1 += inc1; } ptr2 += inc2; self->IncrementLocatorZ(); } } //------------------------------------------------------------------------------ // This method calls the proper templade function. void vtkImageMarchingCubes::March( vtkImageData* inData, int chunkMin, int chunkMax, int numContours, double* values) { void* ptr = inData->GetScalarPointer(); switch (inData->GetScalarType()) { vtkTemplateMacro(vtkImageMarchingCubesMarch( this, inData, static_cast(ptr), chunkMin, chunkMax, numContours, values)); default: vtkErrorMacro(<< "Unknown output ScalarType"); return; } } //============================================================================ // These method act as the point locator so vertices will be shared. // One 2d array of cubes is stored. (z dimension is ignored). // Points are indexed by their cube and edge. // Shared edges are only represented once. Cubes are responsible for // edges on their min faces. Their is an extra row and column of cubes // to store the max edges of the last row/column of cubes, //------------------------------------------------------------------------------ // This method allocates and initializes the point array. // One 2d array of cubes is stored. (z dimension is ignored). void vtkImageMarchingCubes::InitializeLocator(int min0, int max0, int min1, int max1) { // Free old memory delete[] this->LocatorPointIds; // Extra row and column this->LocatorDimX = (max0 - min0 + 2); this->LocatorDimY = (max1 - min1 + 2); this->LocatorMinX = min0; this->LocatorMinY = min1; // 5 non shared edges. vtkIdType size = 5; size *= static_cast(this->LocatorDimX); size *= static_cast(this->LocatorDimY); this->LocatorPointIds = new vtkIdType[size]; // Initialize the array for (vtkIdType idx = 0; idx < size; ++idx) { this->LocatorPointIds[idx] = -1; } } //------------------------------------------------------------------------------ // This method frees the locators memory. void vtkImageMarchingCubes::DeleteLocator() { // Free old memory delete[] this->LocatorPointIds; this->LocatorPointIds = nullptr; } //------------------------------------------------------------------------------ // This method moves the Z index of the locator up one slice. void vtkImageMarchingCubes::IncrementLocatorZ() { vtkIdType* ptr = this->LocatorPointIds; for (int y = 0; y < this->LocatorDimY; ++y) { for (int x = 0; x < this->LocatorDimX; ++x) { ptr[0] = ptr[4]; ptr[3] = ptr[1]; ptr[1] = ptr[2] = ptr[4] = -1; ptr += 5; } } } //------------------------------------------------------------------------------ // This method adds a point to the array. Cube is the X/Y cube, // segment is the index of the segment (same as marching cubes).(XYZ) // (0,0,0)->(1,0,0): 0, (1,0,0)->(1,1,0): 1, // (1,1,0)->(0,1,0): 2, (0,1,0)->(0,0,0): 3, // (0,0,1)->(1,0,1): 4, (1,0,1)->(1,1,1): 5, // (1,1,1)->(0,1,1): 6, (0,1,1)->(0,0,1): 7, // (0,0,0)->(0,0,1): 8, (1,0,0)->(1,0,1): 9, // (0,1,0)->(0,1,1): 10, (1,1,0)->(1,1,1): 11. // Shared edges are computed internally. (no error checking) void vtkImageMarchingCubes::AddLocatorPoint(int cellX, int cellY, int edge, vtkIdType ptId) { // Get the correct position in the array. vtkIdType* ptr = this->GetLocatorPointer(cellX, cellY, edge); *ptr = ptId; } //------------------------------------------------------------------------------ // This method gets a point from the locator. vtkIdType vtkImageMarchingCubes::GetLocatorPoint(int cellX, int cellY, int edge) { // Get the correct position in the array. vtkIdType* ptr = this->GetLocatorPointer(cellX, cellY, edge); return *ptr; } //------------------------------------------------------------------------------ // This method returns a pointer to an ID from a cube and an edge. vtkIdType* vtkImageMarchingCubes::GetLocatorPointer(int cellX, int cellY, int edge) { // Remove redundant edges (shared by more than one cube). // Take care of shared edges switch (edge) { case 9: ++cellX; edge = 8; break; case 10: ++cellY; edge = 8; break; case 11: ++cellX; ++cellY; edge = 8; break; case 5: ++cellX; edge = 7; break; case 6: ++cellY; edge = 4; break; case 1: ++cellX; edge = 3; break; case 2: ++cellY; edge = 0; break; } // relative to min and max. cellX -= this->LocatorMinX; cellY -= this->LocatorMinY; // compute new indexes for edges (0 to 4) // must be compatible with LocatorIncrementZ. if (edge == 7) { edge = 1; } if (edge == 8) { edge = 2; } // return correct pointer return this->LocatorPointIds + edge + (cellX + cellY * static_cast(this->LocatorDimX)) * 5; } //------------------------------------------------------------------------------ int vtkImageMarchingCubes::FillInputPortInformation(int, vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData"); return 1; } //------------------------------------------------------------------------------ void vtkImageMarchingCubes::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); this->ContourValues->PrintSelf(os, indent.GetNextIndent()); os << indent << "ComputeScalars: " << this->ComputeScalars << "\n"; os << indent << "ComputeNormals: " << this->ComputeNormals << "\n"; os << indent << "ComputeGradients: " << this->ComputeGradients << "\n"; os << indent << "InputMemoryLimit: " << this->InputMemoryLimit << "K bytes\n"; } VTK_ABI_NAMESPACE_END