使用vtkVoxelContoursToSurfaceFilter由平行轮廓重建三角面片

在医学影像中,由于CT、MRI等成像设备的数据均以断层影像的形式输出,对其进行轮廓提取和三维重建就成为一个常见的需求。
将一组相互平行的轮廓线,以三角面片连接,形成放样曲面,即为surface from contours问题。这一问题的经典解决方法可参见论文Piecewise-Linear Interpolation between Polygonal Slices(简单贪心)
及approximating complex surfaces by triangulation of contour lines
(动态规划)。
在VTK库中,类vtkVoxelContoursToSurfaceFilter提供了一个基于网格体元的实现。

create surface from contours
vtkVoxelContoursToSurfaceFilter is a filter that takes contours and produces surfaces. There are some restrictions for the contours:
The contours are input as vtkPolyData, with the contours being polys in the vtkPolyData.
The contours lie on XY planes - each contour has a constant Z
The contours are ordered in the polys of the vtkPolyData such that all contours on the first (lowest) XY plane are first, then continuing in order of increasing Z value.
The X, Y and Z coordinates are all integer values.
The desired sampling of the contour data is 1x1x1 - Aspect can be used to control the aspect ratio in the output polygonal dataset.
This filter takes the contours and produces a structured points dataset of signed floating point number indicating distance from a contour. A contouring filter is then applied to generate 3D surfaces from a stack of 2D contour distance slices. This is done in a streaming fashion so as not to use to much memory.

以下为代码注释

// 曲面提取主函数
// Append data sets into single unstructured grid
int vtkVoxelContoursToSurfaceFilter::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
  vtkPolyData *input = vtkPolyData::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData *output = vtkPolyData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkCellArray         *inputPolys = input->GetPolys();
  int                  gridSize[3];
  double                gridOrigin[3];
  double                contourBounds[6];
  int                  chunkSize;
  int                  currentSlice, lastSlice, currentIndex;
  int                  i, j;
  int                  numberOfInputCells;
  int                  currentInputCellIndex;
  vtkIdType            npts = 0;
  vtkIdType            *pts = 0;
  double                point1[3], point2[3];
  double                currentZ;
  vtkStructuredPoints  *volume;
  float                *volumePtr, *slicePtr;
  vtkContourFilter     *contourFilter;
  vtkPolyData          *contourOutput;
  vtkAppendPolyData    *appendFilter;

  // Get the bounds of the input contours
  input->GetBounds( contourBounds );

  if (contourBounds[0] > contourBounds[1])  // xmin > xmax
    { // empty input
    return 1;
    }

// From the bounds, compute the grid size, and origin
//指定网格原点  这里有一个附加平移
//沿xy轴的是因为我们希望数据点落在网格之间 
//沿z轴是因为需要一个附加面作为终止结点
  gridOrigin[0] = contourBounds[0] - 0.5;
  gridOrigin[1] = contourBounds[2] - 0.5;
  gridOrigin[2] = contourBounds[4] - 1.0;

//指定网格尺寸 附加数字是差不多的原因
  // The difference between the bounds, plus one to account a
  // sample on the first and last location, plus one to account
  // for the larger grid size ( the 0.5 unit border ) On Z, we
  // want to sample exactly on the contours so we don't need to
  // add the extra 1, but we have added two extra planes so we
  // need another 2.
  gridSize[0] = (int) (contourBounds[1] - contourBounds[0] + 2);
  gridSize[1] = (int) (contourBounds[3] - contourBounds[2] + 2);
  gridSize[2] = (int) (contourBounds[5] - contourBounds[4] + 3);

  // How many slices in a chunk? This will later be decremented
  // by one to account for the fact that the last slice in the
  // previous chuck is copied to the first slice in the next chunk.
  // Stay within memory limit. There are 4 bytes per double.
  //根据内存限制 计算网格尺寸
  chunkSize = this->MemoryLimitInBytes / ( gridSize[0] * gridSize[1] * 4 );
  if ( chunkSize > gridSize[2] )
    {
    chunkSize = gridSize[2];
    }

  currentSlice          = 0;
  currentZ              = contourBounds[4] - 1.0;
  currentIndex          = 0;
  lastSlice             = gridSize[2] - 1;
  numberOfInputCells    = inputPolys->GetNumberOfCells();
  currentInputCellIndex = 0;

//建立网格
  volume = vtkStructuredPoints::New();
  volume->SetDimensions( gridSize[0], gridSize[1], chunkSize );
  volume->SetSpacing( this->Spacing );
  volume->AllocateScalars( VTK_FLOAT, 1 );
  volumePtr = (float *)(volume->GetPointData()->GetScalars()->GetVoidPointer(0));

  contourFilter = vtkContourFilter::New();
  contourFilter->SetInputData( volume );
  contourFilter->SetNumberOfContours(1);
  contourFilter->SetValue( 0, 0.0 );

  appendFilter = vtkAppendPolyData::New();

  inputPolys->InitTraversal();
  inputPolys->GetNextCell( npts, pts );

  while ( currentSlice <= lastSlice )
    {
    // Make sure the origin of the volume is in the right
    // place so that the appended polydata all matches up
    // nicely.
    volume->SetOrigin( gridOrigin[0], gridOrigin[1],
                       gridOrigin[2] +
                       this->Spacing[2] * (currentSlice - (currentSlice!=0)) );

    for ( i = currentIndex; i < chunkSize; i++ )
      {
      slicePtr = volumePtr + i * gridSize[0] * gridSize[1];

      // Clear out the slice memory - set it all to a large negative
      // value indicating no surfaces are nearby, and we assume we
      // are outside of any surface
      for ( j = 0; j < gridSize[0] * gridSize[1]; j++ )
        {
        *(slicePtr+j) = -9.99e10;
        }

      // If we are past the end, don't do anything
      if ( currentSlice > lastSlice )
        {
        continue;
        }

      this->LineListLength = 0;

      // Read in the lines for the contours on this slice
      while ( currentInputCellIndex < numberOfInputCells )
        {
        // Check if we are still on the right z slice
        input->GetPoint( pts[0], point1 );
        if ( point1[2] != currentZ )
          {
          break;
          }

        // This contour is on the right z slice - add the lines
        // to our list
        for ( j = 0; j < npts; j++ )
          {
          input->GetPoint( pts[j], point1 );
          input->GetPoint( pts[(j+1)%npts], point2 );
          //将该层coutour的所有线段加入列表
          this->AddLineToLineList( point1[0], point1[1],
                                   point2[0], point2[1] );
          }

        inputPolys->GetNextCell( npts, pts );
        currentInputCellIndex++;
        }

      // 将加入的所有线段 分别按照端点的x y坐标顺序排序 排序结果存于SortedXList SortedYList变量中供CastLines函数使用
      this->SortLineList();

      // Cast lines in x and y filling in distance
      // 沿x轴和y轴扫描填充volume
      this->CastLines( slicePtr, gridOrigin, gridSize, 0 );
      this->CastLines( slicePtr, gridOrigin, gridSize, 1 );

      // Move on to the next slice
      currentSlice++;
      currentIndex++;
      currentZ += 1.0;
      }
    //不知道干什么用的  似乎是对数据前后平滑一下
    this->PushDistances( volumePtr, gridSize, chunkSize );

    // Update the contour filter and grab the output
    // Make a new output for it, then grab the output and
    // add it to the append filter, then delete the output
    // which is ok since it was registered by the appendFilter
    contourOutput = vtkPolyData::New();
    contourFilter->Update();
    contourOutput->ShallowCopy(contourFilter->GetOutput());
    appendFilter->AddInputData( contourOutput );
    contourOutput->Delete();


    if ( currentSlice <= lastSlice )
      {
      // Copy last slice to first slice
      memcpy( volumePtr, volumePtr + (chunkSize-1)*gridSize[0]*gridSize[1],
              sizeof(float) * gridSize[0] * gridSize[1] );

      // reset currentIndex to 1
      currentIndex = 1;
      }
    }

  appendFilter->Update();

  // Grab the appended data as the output to this filter
  output->SetPoints( appendFilter->GetOutput()->GetPoints() );
  output->SetVerts(  appendFilter->GetOutput()->GetVerts() );
  output->SetLines(  appendFilter->GetOutput()->GetLines() );
  output->SetPolys(  appendFilter->GetOutput()->GetPolys() );
  output->SetStrips( appendFilter->GetOutput()->GetStrips() );
  output->GetPointData()->PassData(appendFilter->GetOutput()->GetPointData());

  contourFilter->Delete();
  appendFilter->Delete();
  volume->Delete();

  return 1;
}



你可能感兴趣的:(图形学与游戏引擎)