/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkImageMarchingCubes.cxx,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkImageMarchingCubes.h" #include "vtkCellArray.h" #include "vtkCommand.h" #include "vtkFloatArray.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkMarchingCubesCases.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkStreamingDemandDrivenPipeline.h" #include vtkCxxRevisionMacro(vtkImageMarchingCubes, "$Revision: 1.1 $"); 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 = NULL; this->InputMemoryLimit = 10000; // 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. unsigned long vtkImageMarchingCubes::GetMTime() { unsigned long mTime=this->Superclass::GetMTime(); unsigned long contourValuesMTime=this->ContourValues->GetMTime(); mTime = ( contourValuesMTime > mTime ? contourValuesMTime : mTime ); return mTime; } //---------------------------------------------------------------------------- template int vtkImageMarchingCubesGetTypeSize(T*) { return sizeof(T); } //---------------------------------------------------------------------------- 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 ouptut vtkImageData *inData = vtkImageData::SafeDownCast( inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData *output = vtkPolyData::SafeDownCast( outInfo->Get(vtkDataObject::DATA_OBJECT())); int extent[8], estimatedSize; int temp, zMin, zMax, chunkMin, chunkMax; int minSlicesPerChunk, chunkOverlap; int 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. if (this->NeedGradients) { minSlicesPerChunk = 4; chunkOverlap = 3; } else { minSlicesPerChunk = 2; chunkOverlap = 1; } inData->UpdateInformation(); // Each data type requires a different amount of memory. switch (inData->GetScalarType()) { vtkTemplateMacro( temp = vtkImageMarchingCubesGetTypeSize(static_cast(0)) ); default: vtkErrorMacro(<< "Could not determine input scalar type."); return 1; } 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 = temp; // temp holds memory per image. (+1 to avoid dividing by zero) this->NumberOfSlicesPerChunk = this->InputMemoryLimit * 1000 / (temp + 1); if (this->NumberOfSlicesPerChunk < minSlicesPerChunk) { vtkWarningMacro("Execute: Need " << minSlicesPerChunk*(temp/1000) << " 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 estimatedSize = (int) pow ((double) ((extent[1]-extent[0]+1) * (extent[3]-extent[2]+1) * (extent[5]-extent[4]+1)), 0.75); estimatedSize = estimatedSize / 1024 * 1024; //multiple of 1024 if (estimatedSize < 1024) { 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->Allocate(estimatedSize,estimatedSize/2); 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 oen image of cubes). this->InitializeLocator(extent[0], extent[1], extent[2], extent[3]); // Loop through the chunks running marching cubes on each one zMin = extent[4]; zMax = extent[5]; // to avoid warnings chunkMax = zMin + this->NumberOfSlicesPerChunk; for(chunkMin = zMin; chunkMin < zMax; chunkMin = chunkMax) { // Get the chunk from the input chunkMax = chunkMin + this->NumberOfSlicesPerChunk; if (chunkMax > zMax) { 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. if (extent[4] < zMin) { extent[4] = zMin; } if (extent[5] > zMax) { extent[5] = zMax; } // Get the chunk from the input inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), extent, 6); inData->Update(); this->InvokeEvent(vtkCommand::StartEvent,NULL); this->March(inData, chunkMin, chunkMax, numContours, values); if ( !this->AbortExecute ) { this->UpdateProgress(1.0); } this->InvokeEvent(vtkCommand::EndEvent,NULL); if (inData->ShouldIReleaseData()) { 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 = NULL; output->SetPolys(this->Triangles); this->Triangles->Delete(); this->Triangles = NULL; if (this->ComputeScalars) { int idx = output->GetPointData()->AddArray(this->Scalars); output->GetPointData()->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); this->Scalars->Delete(); this->Scalars = NULL; } if (this->ComputeNormals) { output->GetPointData()->SetNormals(this->Normals); this->Normals->Delete(); this->Normals = NULL; } // Recover extra space. output->Squeeze(); // 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 verticies 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 *spacing, double *origin, double value) { int edgeAxis = 0; T *ptrB = NULL; 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] = origin[0] + spacing[0] * ((double)idx0 + temp); pt[1] = origin[1] + spacing[1] * ((double)idx1); pt[2] = origin[2] + spacing[2] * ((double)idx2); break; case 1: pt[0] = origin[0] + spacing[0] * ((double)idx0); pt[1] = origin[1] + spacing[1] * ((double)idx1 + temp); pt[2] = origin[2] + spacing[2] * ((double)idx2); break; case 2: pt[0] = origin[0] + spacing[0] * ((double)idx0); pt[1] = origin[1] + spacing[1] * ((double)idx1); pt[2] = origin[2] + spacing[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])) / spacing[0]; g[1] = (g[1] + temp * (gB[1] - g[1])) / spacing[1]; g[2] = (g[2] + temp * (gB[2] - g[2])) / spacing[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; EDGE_LIST *edge; 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 trianlges if (cubeIndex != 0 && cubeIndex != 255) { // Get edges. triCase = triCases + cubeIndex; 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) { double *spacing = inData->GetSpacing(); double *origin = inData->GetOrigin(); int *extent = inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()); pointIds[ii] = vtkImageMarchingCubesMakeNewPoint(self, cellX, cellY, cellZ, inc0, inc1, inc2, ptr, *edge, extent, spacing, origin, 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 ptr = 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 progress reporting 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) { // update progress if necessary if (!(count%target)) { self->UpdateProgress(count/(50.0*target)); if (self->GetAbortExecute()) { 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 verticies 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) { int idx; int size; // Free old memory if (this->LocatorPointIds) { 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. size = (this->LocatorDimX)*(this->LocatorDimY)*5; this->LocatorPointIds = new int[size]; // Initialize the array for (idx = 0; idx < size; ++idx) { this->LocatorPointIds[idx] = -1; } } //---------------------------------------------------------------------------- // This method frees the locators memory. void vtkImageMarchingCubes::DeleteLocator() { // Free old memory if (this->LocatorPointIds) { delete [] this->LocatorPointIds; this->LocatorPointIds = NULL; } } //---------------------------------------------------------------------------- // This method moves the Z index of the locator up one slice. void vtkImageMarchingCubes::IncrementLocatorZ() { int x, y; int *ptr; ptr = this->LocatorPointIds; for (y = 0; y < this->LocatorDimY; ++y) { for (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 internaly. (no error checking) void vtkImageMarchingCubes::AddLocatorPoint(int cellX, int cellY, int edge, int ptId) { int *ptr; // Get the correct position in the array. ptr = this->GetLocatorPointer(cellX, cellY, edge); *ptr = ptId; } //---------------------------------------------------------------------------- // This method gets a point from the locator. int vtkImageMarchingCubes::GetLocatorPoint(int cellX, int cellY, int edge) { int *ptr; // Get the correct position in the array. ptr = this->GetLocatorPointer(cellX, cellY, edge); return *ptr; } //---------------------------------------------------------------------------- // This method returns a pointer to an ID from a cube and an edge. int *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 compatable with LocatorIncrementZ. if (edge == 7) { edge = 1; } if (edge == 8) { edge = 2; } // return correct pointer return this->LocatorPointIds + edge + (cellX + cellY * (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"; }