/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkImageToPolyDataFilter.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 "vtkImageToPolyDataFilter.h" #include "vtkAppendPolyData.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkEdgeTable.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkLine.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkScalarsToColors.h" #include "vtkUnsignedCharArray.h" vtkCxxRevisionMacro(vtkImageToPolyDataFilter, "$Revision: 1.31 $"); vtkStandardNewMacro(vtkImageToPolyDataFilter); vtkCxxSetObjectMacro(vtkImageToPolyDataFilter,LookupTable,vtkScalarsToColors); vtkImageToPolyDataFilter::vtkImageToPolyDataFilter() { this->OutputStyle = VTK_STYLE_POLYGONALIZE; this->ColorMode = VTK_COLOR_MODE_LINEAR_256; this->Smoothing = 1; this->NumberOfSmoothingIterations = 40; this->Decimation = 1; this->DecimationError = 1.5; this->Error = 100; this->SubImageSize = 250; this->Table = vtkUnsignedCharArray::New(); this->LookupTable = NULL; } vtkImageToPolyDataFilter::~vtkImageToPolyDataFilter() { this->Table->Delete(); if ( this->LookupTable ) { this->LookupTable->Delete(); } } // declare helper functions // int vtkImageToPolyDataFilter::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 *input = vtkImageData::SafeDownCast( inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData *output = vtkPolyData::SafeDownCast( outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData *tmpOutput; vtkPolyData *tmpInput; vtkAppendPolyData *append; vtkPolyData *appendOutput; vtkDataArray *inScalars = input->GetPointData()->GetScalars(); vtkIdType numPixels=input->GetNumberOfPoints(); int dims[3], numComp; double origin[3], spacing[3]; vtkUnsignedCharArray *pixels; int type; int numPieces[2], extent[4]; int i, j, newDims[3], totalPieces, pieceNum, abortExecute=0; double newOrigin[3]; // Check input and initialize vtkDebugMacro(<<"Vectorizing image..."); if ( inScalars == NULL || numPixels < 1 ) { vtkDebugMacro(<<"Not enough input to create output"); return 1; } append = vtkAppendPolyData::New(); tmpOutput=vtkPolyData::New(); tmpInput=vtkPolyData::New(); numComp=inScalars->GetNumberOfComponents(); type=inScalars->GetDataType(); appendOutput=append->GetOutput(); input->GetDimensions(dims); input->GetOrigin(origin); input->GetSpacing(spacing); // Figure out how many pieces to break the image into (the image // might be too big to process). The filter does a series of appends // to join the pieces together. numPieces[0] = ((dims[0]-2) / this->SubImageSize) + 1; numPieces[1] = ((dims[1]-2) / this->SubImageSize) + 1; totalPieces = numPieces[0]*numPieces[1]; appendOutput->Initialize(); //empty the output append->AddInput(tmpOutput); //output of piece append->AddInput(tmpInput); //output of previoius append // Loop over this many pieces for (pieceNum=j=0; j < numPieces[1] && !abortExecute; j++) { extent[2] = j*this->SubImageSize; //the y range extent[3] = (j+1)*this->SubImageSize; if ( extent[3] >= dims[1] ) { extent[3] = dims[1] - 1; } for (i=0; i < numPieces[0] && !abortExecute; i++) { extent[0] = i*this->SubImageSize; //the x range extent[1] = (i+1)*this->SubImageSize; if ( extent[1] >= dims[0] ) { extent[1] = dims[0] - 1; } vtkDebugMacro(<<"Processing #" << pieceNum); this->UpdateProgress ((double)pieceNum/totalPieces); if (this->GetAbortExecute()) { abortExecute = 1; break; } pieceNum++; // Figure out characteristics of current sub-image newDims[0] = extent[1] - extent[0] + 1; newDims[1] = extent[3] - extent[2] + 1; newOrigin[0] = origin[0] + extent[0]*spacing[0]; newOrigin[1] = origin[1] + extent[2]*spacing[1]; newOrigin[2] = 0.0; // Create a quantized copy of the image based on the color table // pixels = this->QuantizeImage(inScalars, numComp, type, dims, extent); vtkDebugMacro(<<"Quantizing color...image size (" <OutputStyle == VTK_STYLE_PIXELIZE ) { this->PixelizeImage(pixels, newDims, newOrigin, spacing, tmpOutput); } else if ( this->OutputStyle == VTK_STYLE_RUN_LENGTH ) { this->RunLengthImage(pixels, newDims, newOrigin, spacing, tmpOutput); } else //VTK_STYLE_POLYGONALIZE { this->PolygonalizeImage(pixels, newDims, newOrigin, spacing,tmpOutput); } // Append pieces together // tmpInput->CopyStructure(appendOutput); tmpInput->GetPointData()->PassData(appendOutput->GetPointData()); tmpInput->GetCellData()->PassData(appendOutput->GetCellData()); append->Update(); // Clean up this iteration // pixels->Delete(); tmpInput->Initialize(); tmpOutput->Initialize(); } // for i pieces } // for j pieces // Create the final output contained in the append filter output->CopyStructure(appendOutput); output->GetPointData()->PassData(appendOutput->GetPointData()); output->GetCellData()->PassData(appendOutput->GetCellData()); append->Delete(); tmpInput->Delete(); tmpOutput->Delete(); return 1; } void vtkImageToPolyDataFilter::PixelizeImage(vtkUnsignedCharArray *pixels, int dims[3], double origin[3], double spacing[3], vtkPolyData *output) { int numPts, numCells, i, j, id; vtkIdType pts[4]; vtkPoints *newPts; vtkCellArray *newPolys; double x[3]; vtkUnsignedCharArray *polyColors; unsigned char *ptr, *colors=pixels->GetPointer(0); // create the points - see whether to create or append numPts = (dims[0]+1) * (dims[1]+1); newPts = vtkPoints::New(); newPts->SetNumberOfPoints(numPts); x[2] = 0.0; for (id=0, j=0; j<=dims[1]; j++) { x[1] = origin[1] + j*spacing[1]; for (i=0; i<=dims[0]; i++) { x[0] = origin[0] + i*spacing[0]; newPts->SetPoint(id, x); id++; } } output->SetPoints(newPts); newPts->Delete(); // create the cells and cell colors // numCells = dims[0] * dims[1]; newPolys = vtkCellArray::New(); newPolys->Allocate(newPolys->EstimateSize(numCells,4)); polyColors = vtkUnsignedCharArray::New(); polyColors->SetNumberOfValues(3*numCells); //for rgb polyColors->SetNumberOfComponents(3); // loop over all pixels, creating a quad per pixel. // Note: copying point data (pixel values) to cell data (quad colors). for (id=0, j=0; jInsertNextCell(4, pts); ptr = colors + 3*id; polyColors->SetValue(3*id, ptr[0]); polyColors->SetValue(3*id+1, ptr[1]); polyColors->SetValue(3*id+2, ptr[2]); id++; } } output->SetPolys(newPolys); newPolys->Delete(); output->GetCellData()->SetScalars(polyColors); polyColors->Delete(); } void vtkImageToPolyDataFilter::RunLengthImage(vtkUnsignedCharArray *pixels, int dims[3], double origin[3], double spacing[3], vtkPolyData *output) { int i, j; vtkIdType pts[4], id; vtkPoints *newPts; vtkCellArray *newPolys; double x[3], minX, maxX, minY, maxY; vtkUnsignedCharArray *polyColors; unsigned char *ptr, *colors=pixels->GetPointer(0), *color; // Setup data newPts = vtkPoints::New(); newPolys = vtkCellArray::New(); newPolys->Allocate(newPolys->EstimateSize(dims[0]*dims[1]/10,4)); polyColors = vtkUnsignedCharArray::New(); polyColors->Allocate(3*dims[0]*dims[1]/10); //for rgb polyColors->SetNumberOfComponents(3); // Loop over row-by-row generating quad polygons x[2] = 0.0; for (j=0; jIsSameColor(color,ptr) ) { break; } else { i++; } } if ( i >= dims[0] ) { maxX = origin[0] + (dims[0]-1)*spacing[0]; } else { maxX = origin[0] + (i-1)*spacing[0] + 0.5*spacing[0]; } // Create quad cell x[0] = minX; x[1] = minY; pts[0] = newPts->InsertNextPoint(x); x[0] = maxX; pts[1] = newPts->InsertNextPoint(x); x[1] = maxY; pts[2] = newPts->InsertNextPoint(x); x[0] = minX; pts[3] = newPts->InsertNextPoint(x); id = newPolys->InsertNextCell(4,pts); polyColors->InsertValue(3*id, color[0]); polyColors->InsertValue(3*id+1, color[1]); polyColors->InsertValue(3*id+2, color[2]); } } output->SetPoints(newPts); newPts->Delete(); output->SetPolys(newPolys); newPolys->Delete(); output->GetCellData()->SetScalars(polyColors); polyColors->Delete(); } void vtkImageToPolyDataFilter::PolygonalizeImage(vtkUnsignedCharArray *pixels, int dims[3], double origin[3], double spacing[3], vtkPolyData *output) { int numPolys; int numPixels=dims[0]*dims[1]; // Perform connected traversal on quantized points. This builds // the initial "polygons" in implicit form. // this->PolyColors = vtkUnsignedCharArray::New(); this->PolyColors->SetNumberOfComponents(3); this->PolyColors->Allocate(5000); numPolys = this->ProcessImage(pixels, dims); vtkDebugMacro(<<"Visited regions..." << numPolys << " polygons"); // Build edges around the boundary of the polygons. Also identify // junction points where 3 or 4 polygons meet. // vtkPoints *points = vtkPoints::New(); points->Allocate(numPixels/2, numPixels/2); vtkUnsignedCharArray *pointDescr = vtkUnsignedCharArray::New(); pointDescr->Allocate(numPixels/2, numPixels/2); vtkCellArray *edgeConn = vtkCellArray::New(); edgeConn->Allocate(numPixels/2, numPixels/2); vtkPolyData *edges = vtkPolyData::New(); edges->SetPoints(points); edges->SetLines(edgeConn); points->Delete(); edgeConn->Delete(); this->BuildEdges(pixels, dims, origin, spacing, pointDescr, edges); vtkDebugMacro(<<"Edges built..."); // Now that we've got the edges, we have to build the "loops" around the // polygons that define the polygon explicitly. // vtkUnsignedCharArray *polyColors = vtkUnsignedCharArray::New(); polyColors->SetNumberOfComponents(3); polyColors->SetNumberOfValues(numPolys*3); this->BuildPolygons(pointDescr, edges, numPolys, polyColors); this->PolyColors->Delete(); delete [] this->Visited; vtkDebugMacro(<<"Constructed polygons..."); // Smooth edge network. Some points are identified as fixed, others // move using Laplacian smoothing. // if ( this->Smoothing ) { this->SmoothEdges(pointDescr, edges); vtkDebugMacro(<<"Edges smoothed..."); } // Decimate edge network. There will be colinear vertices along edges. // These are eliminated. // if ( this->Decimation ) { this->DecimateEdges(edges, pointDescr, this->DecimationError); } // Create output polydata. Each polyon is output with its edges. // this->GeneratePolygons(edges, numPolys, output, polyColors, pointDescr); vtkDebugMacro(<<"Output generated..."); // clean up and get out edges->Delete(); polyColors->Delete(); pointDescr->Delete(); } // The following are private helper functions---------------------------------- // vtkUnsignedCharArray *vtkImageToPolyDataFilter::QuantizeImage( vtkDataArray *inScalars, int numComp, int type, int dims[3], int extent[4]) { int numPixels, i, j, idx, id; vtkUnsignedCharArray *pixels; unsigned char *ptr, *ptr2, *color, *outPixels; unsigned char *inPixels; // doing a portion of the image numPixels = (extent[1]-extent[0]+1) * (extent[3]-extent[2]+1); pixels = vtkUnsignedCharArray::New(); pixels->SetNumberOfValues(3*numPixels); outPixels = pixels->GetPointer(0); // Figure out how to quantize // if ( this->ColorMode == VTK_COLOR_MODE_LINEAR_256 ) { // Check scalar type if ( type != VTK_UNSIGNED_CHAR || numComp != 3 ) { vtkErrorMacro(<<"Wrong input scalar type"); return 0; } else { inPixels = static_cast(inScalars)->GetPointer(0); } // Generate a color table used to quantize the points // if ( this->GetMTime() > this->TableMTime ) { this->BuildTable(inPixels); } for (id=0, j=extent[2]; j <= extent[3]; j++) { for (i=extent[0]; i <= extent[1]; i++) { idx = i + j*dims[0]; ptr = inPixels + 3*idx; ptr2 = outPixels + 3*id; color = this->GetColor(ptr); ptr2[0] = color[0]; ptr2[1] = color[1]; ptr2[2] = color[2]; id++; } } }//using build in table else //using provided lookup table { if ( numComp != 1 || this->LookupTable == NULL ) { vtkErrorMacro(<<"LUT mode requires single component scalar and LUT"); return 0; } double s; for (id=0, j=extent[2]; j <= extent[3]; j++) { for (i=extent[0]; i <= extent[1]; i++) { idx = i + j*dims[0]; s = inScalars->GetComponent(idx,0); color = this->LookupTable->MapValue(s); ptr2 = outPixels + 3*id; ptr2[0] = color[0]; ptr2[1] = color[1]; ptr2[2] = color[2]; id++; } } } return pixels; } void vtkImageToPolyDataFilter::BuildTable(unsigned char *vtkNotUsed(inPixels)) { int red, green, blue, idx=0; this->Table->SetNumberOfValues(256*3); // use 3-3-2 bits for rgb for (blue=0; blue<256; blue+=64) { for (green=0; green<256; green+=32) { for (red=0; red<256; red+=32) { this->Table->SetValue(idx++, red); this->Table->SetValue(idx++, green); this->Table->SetValue(idx++, blue); } } } } int vtkImageToPolyDataFilter::FillInputPortInformation(int, vtkInformation *info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData"); return 1; } void vtkImageToPolyDataFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "Output Style: "; if ( this->OutputStyle == VTK_STYLE_PIXELIZE ) { os << indent << "Pixelize\n"; } else if ( this->OutputStyle == VTK_STYLE_RUN_LENGTH ) { os << indent << "RunLength\n"; } else // this->OutputStyle == VTK_STYLE_POLYGONALIZE { os << indent << "Polygonalize\n"; } os << indent << "Color Mode: "; if ( this->ColorMode == VTK_STYLE_PIXELIZE ) { os << indent << "LUT\n"; } else // this->ColorMode == VTK_STYLE_POLYGONALIZE { os << indent << "Linear256\n"; } os << indent << "Smoothing: " << (this->Smoothing ? "On\n" : "Off\n"); os << indent << "Number of Smoothing Iterations: " << this->NumberOfSmoothingIterations << "\n"; os << indent << "Decimation: " << (this->Decimation ? "On\n" : "Off\n"); os << indent << "Decimation Error: " << (this->DecimationError ? "On\n" : "Off\n"); os << indent << "Error: " << this->Error << "\n"; os << indent << "Sub-Image Size: " << this->SubImageSize << "\n"; if ( this->LookupTable ) { os << indent << "LookupTable:\n"; this->LookupTable->PrintSelf(os,indent.GetNextIndent()); } else { os << indent << "LookupTable: (none)\n"; } } //--------------------------private helper functions--------------------------- // Determines whether two pixels are the same color int vtkImageToPolyDataFilter::IsSameColor(unsigned char *p1, unsigned char *p2) { int d2 = (p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1]) + (p1[2]-p2[2])*(p1[2]-p2[2]); return (d2 > this->Error ? 0 : 1); } unsigned char *vtkImageToPolyDataFilter::GetColor(unsigned char *rgb) { // round to nearest value int red = (rgb[0] + 16) / 32; red = (red > 7 ? 7 : red); int green =(rgb[1] + 16) / 32; green = (green > 7 ? 7 : green); int blue = (rgb[2] + 32) / 64; blue = (blue > 3 ? 3 : blue); return this->Table->GetPointer(3*(red + green*8 + blue*64)); } void vtkImageToPolyDataFilter::GetIJ(int id, int &i, int &j, int dims[3]) { i = id % dims[0]; j = id / dims[0]; } // Get the left-right-top-bottom neighboring pixels of a given pixel // The method has been modified to return right neighbor (mode==0); // or top neighbor (mode==1) or all neighbors (mode==2). int vtkImageToPolyDataFilter::GetNeighbors(unsigned char *ptr, int &i, int &j, int dims[2],unsigned char *neighbors[4], int mode) { int numNeis=0; if ( mode == 0 ) { if ( (i+1) < dims[0] ) { neighbors[numNeis++] = ptr + 3; //jump over rgb } if ( (i-1) >= 0 ) { neighbors[numNeis++] = ptr - 3; //jump over rgb } } else if ( mode == 1 ) { if ( (j+1) < dims[1] ) { neighbors[numNeis++] = ptr + 3*dims[0]; } } else { if ( (i+1) < dims[0] ) { neighbors[numNeis++] = ptr + 3; //jump over rgb } if ( (i-1) >= 0 ) { neighbors[numNeis++] = ptr - 3; } if ( (j+1) < dims[1] ) { neighbors[numNeis++] = ptr + 3*dims[0]; } if ( (j-1) >= 0 ) { neighbors[numNeis++] = ptr - 3*dims[0]; } } return numNeis; } // Marks connected regions with different colors. int vtkImageToPolyDataFilter::ProcessImage(vtkUnsignedCharArray *scalars, int dims[2]) { int numPixels = dims[0]*dims[1]; vtkIdList *wave, *wave2, *tmpWave; int numIds, regionNumber, i, j, k, id, x, y, numNeighbors; unsigned char *neighbors[4], *ptr; unsigned char *pixels = scalars->GetPointer(0); // Collect groups of pixels together into similar colored regions. These // will be eventually grouped into polygons and/or lines. // // mark all pixels unvisited regionNumber = -1; this->Visited = new int [numPixels]; memset (this->Visited, (int)-1, numPixels*sizeof(int)); // set up the connected traversal wave = vtkIdList::New(); wave->Allocate(static_cast(numPixels/4.0), static_cast(numPixels/4.0)); wave2 = vtkIdList::New(); wave2->Allocate(static_cast(numPixels/4.0), static_cast(numPixels/4.0)); // visit connected pixels. Pixels are connected if they are topologically // adjacent and they have "equal" color values. for (i=0; i < numPixels; i++) { if ( this->Visited[i] == -1 ) {//start a connected wave this->Visited[i] = ++regionNumber; ptr = pixels + 3*i; this->PolyColors->InsertValue(3*regionNumber, ptr[0]); //assign color this->PolyColors->InsertValue(3*regionNumber+1, ptr[1]); this->PolyColors->InsertValue(3*regionNumber+2, ptr[2]); wave->Reset(); wave2->Reset(); // To prevent creating polygons with inner loops, we're going to start // the wave as a "vertical" stack of pixels, and then propogate the // wave horizontally only. wave->InsertId(0,i); this->GetIJ(i, x, y, dims); while ( (numNeighbors = this->GetNeighbors(ptr, x, y, dims, neighbors, 1)) ) { id = (neighbors[0] - pixels) / 3; if ( this->Visited[id] == -1 && this->IsSameColor(ptr, neighbors[0]) ) { this->Visited[id] = regionNumber; wave->InsertNextId(id); ptr = pixels + 3*id; this->GetIJ(id, x, y, dims); } else { break; } } // Okay, defined vertical wave, now propogate horizontally numIds = wave->GetNumberOfIds(); while ( numIds > 0 ) { for (j=0; jGetId(j); ptr = pixels + 3*id; this->GetIJ(id, x, y, dims); numNeighbors = this->GetNeighbors(ptr, x, y, dims, neighbors, 0); for (k=0; kVisited[id] == -1 && this->IsSameColor(ptr, neighbors[k]) ) { this->Visited[id] = regionNumber; wave2->InsertNextId(id); } }//for each pixel neighbor }//for pixels left in wave numIds = wave2->GetNumberOfIds(); tmpWave = wave; wave = wave2; wave2 = tmpWave; wave2->Reset(); }//while still propogating }//if not, start wave }//for all pixels wave->Delete(); wave2->Delete(); return regionNumber+1; } // Create polygons and place into output void vtkImageToPolyDataFilter::GeneratePolygons(vtkPolyData *edges, int vtkNotUsed(numPolys), vtkPolyData *output, vtkUnsignedCharArray *polyColors, vtkUnsignedCharArray *pointDescr) { vtkCellArray *newPolys, *inPolys; int i, numPts; vtkIdType *pts = 0; vtkIdType npts = 0; // Copy the points via reference counting // output->SetPoints(edges->GetPoints()); // Create the polygons - points may have been decimated so these // points have to be culled. // inPolys = edges->GetPolys(); newPolys = vtkCellArray::New(); newPolys->Allocate(inPolys->GetSize()); for ( inPolys->InitTraversal(); inPolys->GetNextCell(npts,pts); ) { newPolys->InsertNextCell(0); numPts = 0; for (i=0; iGetValue(pts[i]) != 2 ) { newPolys->InsertCellPoint(pts[i]); numPts++; } } newPolys->UpdateCellCount(numPts); } output->SetPolys(newPolys); newPolys->Delete(); output->GetCellData()->SetScalars(polyColors); } // Uses clipping approach to build the polygon edges int vtkImageToPolyDataFilter::BuildEdges(vtkUnsignedCharArray *vtkNotUsed(pixels), int dims[3], double origin[3], double spacing[3], vtkUnsignedCharArray *pointDescr, vtkPolyData *edges) { double x[3]; int i, j, edgeCount; vtkIdType ptId, p0, p1, p2, p3, startId, attrId, id[8], pts[4]; vtkCellArray *edgeConn = edges->GetLines(); vtkPoints *points = edges->GetPoints(); // Build edges around perimeter of image. Note that the point ids // The first four points are the image corners and are inserted and // marked so that they can't be moved during smoothing. points->InsertPoint(0, origin); pointDescr->InsertValue(0, 1); // Keep track of the polygons that use each edge as well as associated // intersection points on edge (if any) this->EdgeTable = vtkEdgeTable::New(); this->EdgeTable->InitEdgeInsertion(dims[0]*dims[1],1); this->EdgeUseTable = vtkEdgeTable::New(); this->EdgeUseTable->InitEdgeInsertion(dims[0]*dims[1],1); this->EdgeUses = vtkIntArray::New(); this->EdgeUses->SetNumberOfComponents(2); this->EdgeUses->Allocate(4*dims[0]*dims[1],dims[0]*dims[1]); // Generate corner points of image x[0] = origin[0] + (dims[0]-1)*spacing[0]; x[1] = origin[1]; x[2] = 0.0; points->InsertPoint(1, x); pointDescr->InsertValue(1, 1); x[0] = origin[0] + (dims[0]-1)*spacing[0]; x[1] = origin[1] + (dims[1]-1)*spacing[1];; x[2] = 0.0; points->InsertPoint(2, x); pointDescr->InsertValue(2, 1); x[0] = origin[0]; x[1] = origin[1] + (dims[1]-1)*spacing[1]; x[2] = 0.0; points->InsertPoint(3, x); pointDescr->InsertValue(3, 1); // Let's create perimeter edges - bottom x edge startId = 0; x[1] = origin[1]; for (i=0; i<(dims[0]-1); i++) { p0 = i; p1 = i + 1; if ( this->Visited[p0] != this->Visited[p1] ) { x[0] = origin[0] + i*spacing[0] + 0.5*spacing[0]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 1); //can't be smoothed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(ptId); attrId = this->EdgeUseTable->InsertEdge(startId,ptId); this->EdgeUses->InsertValue(2*attrId, this->Visited[p0]); this->EdgeUses->InsertValue(2*attrId+1, -1); startId = ptId; } } edgeConn->InsertNextCell(2); //finish off the edge edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(1); attrId = this->EdgeUseTable->InsertEdge(startId,1); this->EdgeUses->InsertValue(2*attrId, this->Visited[dims[0]-1]); this->EdgeUses->InsertValue(2*attrId+1, -1); // Let's create perimeter edges - top x edge startId = 3; x[1] = origin[1] + (dims[1]-1)*spacing[1]; for (i=0; i<(dims[0]-1); i++) { p0 = i + dims[0]*(dims[1]-1); p1 = p0 + 1; if ( this->Visited[p0] != this->Visited[p1] ) { x[0] = origin[0] + i*spacing[0] + 0.5*spacing[0]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 1); //can't be smoothed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(ptId); attrId = this->EdgeUseTable->InsertEdge(startId,ptId); this->EdgeUses->InsertValue(2*attrId, this->Visited[p0]); this->EdgeUses->InsertValue(2*attrId+1, -1); startId = ptId; } } edgeConn->InsertNextCell(2); //finish off the edge edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(2); attrId = this->EdgeUseTable->InsertEdge(startId,2); this->EdgeUses->InsertValue(2*attrId, this->Visited[dims[1]*dims[0]-1]); this->EdgeUses->InsertValue(2*attrId+1, -1); // Let's create perimeter edges - min y edge startId = 0; x[0] = origin[0]; for (j=0; j<(dims[1]-1); j++) { p0 = j*dims[0]; p1 = p0 + dims[0]; if ( this->Visited[p0] != this->Visited[p1] ) { x[1] = origin[1] + j*spacing[1] + 0.5*spacing[1]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 1); //can't be smoothed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(ptId); attrId = this->EdgeUseTable->InsertEdge(startId,ptId); this->EdgeUses->InsertValue(2*attrId, this->Visited[p0]); this->EdgeUses->InsertValue(2*attrId+1, -1); startId = ptId; } } edgeConn->InsertNextCell(2); //finish off the edge edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(3); attrId = this->EdgeUseTable->InsertEdge(startId,3); this->EdgeUses->InsertValue(2*attrId, this->Visited[(dims[1]-1)*dims[0]]); this->EdgeUses->InsertValue(2*attrId+1, -1); // Let's create perimeter edges - max y edge startId = 1; x[0] = origin[0] + (dims[0]-1)*spacing[0]; for (j=0; j<(dims[1]-1); j++) { p0 = j*dims[0] + (dims[0]-1); p1 = p0 + dims[0]; if ( this->Visited[p0] != this->Visited[p1] ) { x[1] = origin[1] + j*spacing[1] + 0.5*spacing[1]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 1); //can't be smoothed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(ptId); attrId = this->EdgeUseTable->InsertEdge(startId,ptId); this->EdgeUses->InsertValue(2*attrId, this->Visited[p0]); this->EdgeUses->InsertValue(2*attrId+1, -1); startId = ptId; } } edgeConn->InsertNextCell(2); //finish off the edge edgeConn->InsertCellPoint(startId); edgeConn->InsertCellPoint(2); attrId = this->EdgeUseTable->InsertEdge(startId,2); this->EdgeUses->InsertValue(2*attrId, this->Visited[dims[1]*dims[0]-1]); this->EdgeUses->InsertValue(2*attrId+1, -1); // Loop over all edges generating intersection points and outer boundary // edge segments. // for (j=1; j<(dims[1]-1); j++) //loop over all x edges (except boundary) { x[1] = origin[1] + j*spacing[1]; for (i=0; i<(dims[0]-1); i++) { p0 = i + j*dims[0]; p1 = p0 + 1; if ( this->Visited[p0] != this->Visited[p1] ) { x[0] = origin[0] + i*spacing[0] + 0.5*spacing[0]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 0); } } } for (i=1; i<(dims[0]-1); i++) //loop over all y edges (except boundary) { x[0] = origin[0] + i*spacing[0]; for (j=0; j<(dims[1]-1); j++) { p0 = i + j*dims[0]; p1 = i + (j+1)*dims[0]; if ( this->Visited[p0] != this->Visited[p1] ) { x[1] = origin[1] + j*spacing[1] + 0.5*spacing[1]; ptId = points->InsertNextPoint(x); this->EdgeTable->InsertEdge(p0,p1,ptId); pointDescr->InsertValue(ptId, 0); //can be smoothed } } } // All intersection points are generated, now create edges. Use a clipping // approach to create line segments. Later we'll connect them into polylines. // for (j=0; j<(dims[1]-1); j++) //loop over all x edges { for (i=0; i<(dims[0]-1); i++) { edgeCount = 0; p0 = i + j*dims[0]; p1 = p0 + 1; p2 = i+1 + (j+1)*dims[0]; p3 = p2 - 1; if ( (ptId=this->EdgeTable->IsEdge(p0,p1)) != -1 ) { id[2*edgeCount] = p0; id[2*edgeCount+1] = p1; pts[edgeCount++] = ptId; } if ( (ptId=this->EdgeTable->IsEdge(p1,p2)) != -1 ) { id[2*edgeCount] = p1; id[2*edgeCount+1] = p2; pts[edgeCount++] = ptId; } if ( (ptId=this->EdgeTable->IsEdge(p2,p3)) != -1 ) { id[2*edgeCount] = p2; id[2*edgeCount+1] = p3; pts[edgeCount++] = ptId; } if ( (ptId=this->EdgeTable->IsEdge(p3,p0)) != -1 ) { id[2*edgeCount] = p3; id[2*edgeCount+1] = p0; pts[edgeCount++] = ptId; } if ( edgeCount == 4 ) { x[0] = origin[0] + i*spacing[0] + 0.5*spacing[0]; x[1] = origin[1] + j*spacing[1] + 0.5*spacing[1]; ptId = points->InsertNextPoint(x); pointDescr->InsertValue(ptId, 0); //intersection points are fixed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[0]); attrId = this->EdgeUseTable->InsertEdge(ptId,pts[0]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[0]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[1]]); edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[1]); attrId = this->EdgeUseTable->InsertEdge(ptId,pts[1]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[2]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[3]]); edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[2]); attrId = this->EdgeUseTable->InsertEdge(ptId,pts[2]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[4]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[5]]); edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[3]); attrId = this->EdgeUseTable->InsertEdge(ptId,pts[3]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[6]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[7]]); } else if ( edgeCount == 3 ) { x[0] = origin[0] + i*spacing[0] + 0.5*spacing[0]; x[1] = origin[1] + j*spacing[1] + 0.5*spacing[1]; ptId = points->InsertNextPoint(x); pointDescr->InsertValue(ptId, 0); //intersection points are fixed edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[0]); attrId = this->EdgeUseTable->InsertEdge(ptId, pts[0]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[0]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[1]]); edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[1]); attrId = this->EdgeUseTable->InsertEdge(ptId, pts[1]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[2]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[3]]); edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(ptId); edgeConn->InsertCellPoint(pts[2]); attrId = this->EdgeUseTable->InsertEdge(ptId, pts[2]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[4]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[5]]); } else if ( edgeCount == 2 ) { edgeConn->InsertNextCell(2); edgeConn->InsertCellPoint(pts[0]); edgeConn->InsertCellPoint(pts[1]); attrId = this->EdgeUseTable->InsertEdge(pts[0],pts[1]); this->EdgeUses->InsertValue(2*attrId, this->Visited[id[0]]); this->EdgeUses->InsertValue(2*attrId+1, this->Visited[id[1]]); } else if ( edgeCount == 1 ) { vtkErrorMacro(<<"Bad mojo"); return 0; } } } // Cleanup this->EdgeUseTable->Delete(); this->EdgeTable->Delete(); return 0; } void vtkImageToPolyDataFilter::BuildPolygons(vtkUnsignedCharArray *vtkNotUsed(pointDescr), vtkPolyData *edges, int numPolys, vtkUnsignedCharArray *polyColors) { vtkPoints *points = edges->GetPoints(); vtkIdType numPts = points->GetNumberOfPoints(), ptId; int i, j, k, *polyId, *polyId2, edgeId; vtkIdType *cells, *pts, *cells2, npts, cellId; int numPolyPts, p1, p2; unsigned short ncells, ncells2; unsigned char *polyVisited, *ptr; vtkCellArray *newPolys; // Make sure we can topological info edges->BuildLinks(); // Mark all polygons as unvisited polyVisited = new unsigned char [numPolys]; for (i=0; iAllocate(newPolys->EstimateSize(numPolys,25)); // Loop over all edge points tracking around each polygon for (ptId=0; ptIdGetPointCells(ptId, ncells, cells); if (ncells < 2) { vtkErrorMacro(<<"Bad mojo"); return; } //for each edge, walk around polygon (if not visited before) for (i=0; iEdgeUses->GetPointer(2*edgeId); for (j=0; j<2; j++) { if ( polyId[j] != -1 && !polyVisited[polyId[j]] ) {//build loop polyVisited[polyId[j]] = 1; numPolyPts = 1; cellId = newPolys->InsertNextCell(0); //will update count later newPolys->InsertCellPoint(ptId); // Update polygonal color ptr = this->PolyColors->GetPointer(3*polyId[j]); polyColors->SetValue(3*cellId, ptr[0]); //assign poly color polyColors->SetValue(3*cellId+1, ptr[1]); polyColors->SetValue(3*cellId+2, ptr[2]); p1 = ptId; p2 = -1; while ( 1 ) { edges->GetCellPoints(edgeId, npts, pts); p2 = (pts[0] != p1 ? pts[0] : pts[1]); if (p2 == ptId) { break; } newPolys->InsertCellPoint(p2); numPolyPts++; edges->GetPointCells(p2, ncells2, cells2); if (ncells < 2) { vtkErrorMacro(<<"Bad mojo"); return; } for (k=0; kEdgeUses->GetPointer(2*cells2[k]); if ( cells2[k] != edgeId && (polyId2[0] == polyId[j] || polyId2[1] == polyId[j]) ) { p1 = p2; edgeId = cells2[k]; break; } } }//while not completed loop newPolys->UpdateCellCount(numPolyPts); }//if polygon not yet visited }//for each use of edge by polygon (at most 2 polygons) }//for each edge connected to this point }//for all points in edge list edges->SetPolys(newPolys); newPolys->Delete(); this->EdgeUses->Delete(); delete [] polyVisited; } void vtkImageToPolyDataFilter::SmoothEdges(vtkUnsignedCharArray *pointDescr, vtkPolyData *edges) { vtkPoints *points=edges->GetPoints(); vtkIdType numPts=points->GetNumberOfPoints(), ptId; int i, iterNum; int connId; double x[3], xconn[3], xave[3], factor; unsigned short int ncells; vtkIdType *cells, *pts, npts; // For each smoothing operation, loop over points. Points that can be // smoothed are moved in the direction of the average of their neighbor // points. for ( iterNum=0; iterNumNumberOfSmoothingIterations; iterNum++ ) { if ( (iterNum % 2) ) //alternate smoothing direction { factor = -0.331; } else { factor = 0.330; } for (ptId=0; ptIdGetValue(ptId) == 0 ) //can smooth { points->GetPoint(ptId, x); edges->GetPointCells(ptId, ncells, cells); xave[0] = xave[1] = xave[2] = 0.0; for (i=0; iGetCellPoints(cells[i], npts, pts); if (pts[0] != ptId) { connId = pts[0]; } else if (npts > 1) { connId = pts[1]; } else { vtkErrorMacro("Bad cell in smoothing operation"); connId = pts[0]; } points->GetPoint(connId, xconn); xave[0] += xconn[0]; xave[1] += xconn[1]; xave[2] += xconn[2]; } if ( ncells > 0 ) { xave[0] /= ncells; xave[1] /= ncells; xave[2] /= ncells; x[0] = x[0] + factor * (xave[0] - x[0]); x[1] = x[1] + factor * (xave[1] - x[1]); x[2] = x[2] + factor * (xave[2] - x[2]); points->SetPoint(ptId, x); } }//if smoothable point }//for all points }//for all smoothing operations } // Remove points that are nearly co-linear to reduce the total point count // void vtkImageToPolyDataFilter::DecimateEdges(vtkPolyData *edges, vtkUnsignedCharArray *pointDescr, double tol2) { vtkPoints *points=edges->GetPoints(); vtkIdType numPts=points->GetNumberOfPoints(), ptId, prevId, nextId; vtkIdType npts; double x[3], xPrev[3], xNext[3]; unsigned short int ncells; vtkIdType *cells, *pts; // Loop over all points, finding those that are connected to just two // edges. If the point is colinear to the previous and next edge point, // then mark it as deleted. for (ptId=0; ptIdGetValue(ptId) == 0 ) { points->GetPoint(ptId, x); edges->GetPointCells(ptId, ncells, cells); if ( ncells == 2 ) { edges->GetCellPoints(cells[0], npts, pts); prevId = (pts[0] != ptId ? pts[0] : pts[1]); points->GetPoint(prevId, xPrev); edges->GetCellPoints(cells[1], npts, pts); nextId = (pts[0] != ptId ? pts[0] : pts[1]); points->GetPoint(nextId, xNext); if ( vtkLine::DistanceToLine(x, xPrev, xNext) <= tol2 ) { pointDescr->SetValue(ptId, 2); //mark deleted } } } //if manifold } //for all points }