You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
13 KiB
373 lines
13 KiB
2 years ago
|
/*=========================================================================
|
||
|
|
||
|
Program: Visualization Toolkit
|
||
|
Module: $RCSfile: vtkImageSkeleton2D.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 "vtkImageSkeleton2D.h"
|
||
|
|
||
|
#include "vtkAlgorithmOutput.h"
|
||
|
#include "vtkImageData.h"
|
||
|
#include "vtkInformation.h"
|
||
|
#include "vtkInformationVector.h"
|
||
|
#include "vtkObjectFactory.h"
|
||
|
#include "vtkDataSetAttributes.h"
|
||
|
#include "vtkStreamingDemandDrivenPipeline.h"
|
||
|
|
||
|
vtkCxxRevisionMacro(vtkImageSkeleton2D, "$Revision: 1.38 $");
|
||
|
vtkStandardNewMacro(vtkImageSkeleton2D);
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Construct an instance of vtkImageSkeleton2D fitler.
|
||
|
vtkImageSkeleton2D::vtkImageSkeleton2D()
|
||
|
{
|
||
|
this->Prune = 0;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
void vtkImageSkeleton2D::SetNumberOfIterations(int num)
|
||
|
{
|
||
|
this->vtkImageIterateFilter::SetNumberOfIterations(num);
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// This method computes the extent of the input region necessary to generate
|
||
|
// an output region. Before this method is called "region" should have the
|
||
|
// extent of the output region. After this method finishes, "region" should
|
||
|
// have the extent of the required input region.
|
||
|
int vtkImageSkeleton2D::IterativeRequestUpdateExtent(vtkInformation* in,
|
||
|
vtkInformation* out)
|
||
|
{
|
||
|
int wholeExtent[6];
|
||
|
int outExt[6];
|
||
|
in->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent);
|
||
|
out->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), outExt);
|
||
|
|
||
|
int inExt[6];
|
||
|
inExt[4] = outExt[4];
|
||
|
inExt[5] = outExt[5];
|
||
|
for(int idx=0; idx < 2; ++idx)
|
||
|
{
|
||
|
inExt[idx*2] = outExt[idx*2] - 1;
|
||
|
inExt[idx*2+1] = outExt[idx*2+1] + 1;
|
||
|
|
||
|
// If the expanded region is out of the IMAGE Extent (grow min)
|
||
|
if (inExt[idx*2] < wholeExtent[idx*2])
|
||
|
{
|
||
|
inExt[idx*2] = wholeExtent[idx*2];
|
||
|
}
|
||
|
// If the expanded region is out of the IMAGE Extent (shrink max)
|
||
|
if (inExt[idx*2+1] > wholeExtent[idx*2+1])
|
||
|
{
|
||
|
// shrink the required region extent
|
||
|
inExt[idx*2+1] = wholeExtent[idx*2+1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
in->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt, 6);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// This method contains the second switch statement that calls the correct
|
||
|
// templated function for the mask types.
|
||
|
// This is my best attempt at skeleton. The rules are a little hacked up,
|
||
|
// but it is the only way I can think of to get the
|
||
|
// desired results with a 3x3 kernel.
|
||
|
template <class T>
|
||
|
void vtkImageSkeleton2DExecute(vtkImageSkeleton2D *self,
|
||
|
vtkImageData *inData, T *inPtr,
|
||
|
vtkImageData *outData, int *outExt,
|
||
|
T *outPtr, int id)
|
||
|
{
|
||
|
// For looping though output (and input) pixels.
|
||
|
int outMin0, outMax0, outMin1, outMax1, outMin2, outMax2, numComps;
|
||
|
int idx0, idx1, idx2, idxC;
|
||
|
vtkIdType inInc0, inInc1, inInc2;
|
||
|
vtkIdType outInc0, outInc1, outInc2;
|
||
|
T *inPtr0, *inPtr1, *inPtr2, *inPtrC;
|
||
|
T *outPtr0, *outPtr1, *outPtr2;
|
||
|
int wholeMin0, wholeMax0, wholeMin1, wholeMax1, wholeMin2, wholeMax2;
|
||
|
int prune = self->GetPrune();
|
||
|
float n[8];
|
||
|
int countFaces, countCorners;
|
||
|
unsigned long count = 0;
|
||
|
unsigned long target;
|
||
|
int erodeCase;
|
||
|
|
||
|
// Get information to march through data
|
||
|
inData->GetIncrements(inInc0, inInc1, inInc2);
|
||
|
outData->GetIncrements(outInc0, outInc1, outInc2);
|
||
|
outMin0 = outExt[0]; outMax0 = outExt[1];
|
||
|
outMin1 = outExt[2]; outMax1 = outExt[3];
|
||
|
outMin2 = outExt[4]; outMax2 = outExt[5];
|
||
|
self->GetInput()->GetWholeExtent(wholeMin0, wholeMax0, wholeMin1, wholeMax1,
|
||
|
wholeMin2, wholeMax2);
|
||
|
numComps = inData->GetNumberOfScalarComponents();
|
||
|
|
||
|
target = (unsigned long)(numComps*(outMax2-outMin2+1)*
|
||
|
(outMax1-outMin1+1)/50.0);
|
||
|
target++;
|
||
|
|
||
|
inPtrC = inPtr;
|
||
|
for (idxC = 0; idxC < numComps; ++idxC)
|
||
|
{
|
||
|
inPtr2 = inPtrC;
|
||
|
for (idx2 = outMin2; idx2 <= outMax2; ++idx2)
|
||
|
{
|
||
|
// erode input
|
||
|
inPtr1 = inPtr2;
|
||
|
for (idx1 = outMin1; !self->AbortExecute && idx1 <= outMax1; ++idx1)
|
||
|
{
|
||
|
if (!id)
|
||
|
{
|
||
|
if (!(count%target))
|
||
|
{
|
||
|
self->UpdateProgress(0.9*count/(50.0*target));
|
||
|
}
|
||
|
count++;
|
||
|
}
|
||
|
inPtr0 = inPtr1;
|
||
|
for (idx0 = outMin0; idx0 <= outMax0; ++idx0)
|
||
|
{
|
||
|
// Center pixel has to be on.
|
||
|
if (*inPtr0)
|
||
|
{
|
||
|
// neighbors independant of boundaries
|
||
|
n[0] = (idx0>wholeMin0) ? (float)*(inPtr0-inInc0) : 0;
|
||
|
n[1] = (idx0>wholeMin0)&&(idx1>wholeMin1)
|
||
|
? (float)*(inPtr0-inInc0-inInc1) : 0;
|
||
|
n[2] = (idx1>wholeMin1) ? (float)*(inPtr0-inInc1) : 0;
|
||
|
n[3] = (idx1>wholeMin1)&&(idx0<wholeMax0)
|
||
|
? (float)*(inPtr0-inInc1+inInc0) : 0;
|
||
|
n[4] = (idx0<wholeMax0) ? (float)*(inPtr0+inInc0) : 0;
|
||
|
n[5] = (idx0<wholeMax0)&&(idx1<wholeMax1)
|
||
|
? (float)*(inPtr0+inInc0+inInc1) : 0;
|
||
|
n[6] = (idx1<wholeMax1) ? (float)*(inPtr0+inInc1) : 0;
|
||
|
n[7] = (idx1<wholeMax1)&&(idx0>wholeMin0)
|
||
|
? (float)*(inPtr0+inInc1-inInc0) : 0;
|
||
|
|
||
|
// Lets try a case table. (shifting bits would be faster)
|
||
|
erodeCase = 0;
|
||
|
if (n[7] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[6] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[5] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[4] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[3] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[2] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[1] > 0) {++erodeCase;}
|
||
|
erodeCase *= 2;
|
||
|
if (n[0] > 0) {++erodeCase;}
|
||
|
|
||
|
if (erodeCase == 54 || erodeCase == 216)
|
||
|
{ // erode
|
||
|
// 54 top part of diagonal / double thick line
|
||
|
// 216 bottom part of diagonal \ double thick line
|
||
|
*inPtr0 = 1;
|
||
|
}
|
||
|
else if (erodeCase == 99 || erodeCase == 141)
|
||
|
{ // No errosion
|
||
|
// 99 bottom part of diagonal / double thick line
|
||
|
// 141 top part of diagonal \ double thick line
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// old heuristic method
|
||
|
countFaces = (n[0]>0)+(n[2]>0)+(n[4]>0)+(n[6]>0);
|
||
|
countCorners = (n[1]>0)+(n[3]>0)+(n[5]>0)+(n[7]>0);
|
||
|
|
||
|
// special case to void split dependent results.
|
||
|
// (should we just have a case table?)
|
||
|
if (countFaces == 2 && countCorners == 0 &&
|
||
|
n[2] > 0 && n[4] > 0)
|
||
|
{
|
||
|
*inPtr0 = 1;
|
||
|
}
|
||
|
|
||
|
// special case
|
||
|
if (prune > 1 && ((countFaces + countCorners) <= 1))
|
||
|
{
|
||
|
*inPtr0 = 1;
|
||
|
}
|
||
|
|
||
|
// one of four face neighbors has to be off
|
||
|
if (n[0] == 0 || n[2] == 0 ||
|
||
|
n[4] == 0 || n[6] == 0)
|
||
|
{
|
||
|
// Special condition not to prune diamond corners
|
||
|
if (prune > 1 || countFaces != 1 || countCorners != 2 ||
|
||
|
((n[1]==0 || n[2]==0 || n[3]==0) &&
|
||
|
(n[3]==0 || n[4]==0 || n[5]==0) &&
|
||
|
(n[5]==0 || n[6]==0 || n[7]==0) &&
|
||
|
(n[7]==0 || n[0]==0 || n[1]==0)))
|
||
|
{
|
||
|
// special condition (making another prune level)
|
||
|
// pruning 135 degree corners
|
||
|
if (prune || countFaces != 2 || countCorners != 2 ||
|
||
|
((n[1]==0 || n[2]==0 || n[3]==0 || n[4]) &&
|
||
|
(n[0]==0 || n[1]==0 || n[2]==0 || n[3]) &&
|
||
|
(n[7]==0 || n[0]==0 || n[1]==0 || n[2]) &&
|
||
|
(n[6]==0 || n[7]==0 || n[0]==0 || n[1]) &&
|
||
|
(n[5]==0 || n[6]==0 || n[7]==0 || n[0]) &&
|
||
|
(n[4]==0 || n[5]==0 || n[6]==0 || n[7]) &&
|
||
|
(n[3]==0 || n[4]==0 || n[5]==0 || n[6]) &&
|
||
|
(n[2]==0 || n[3]==0 || n[4]==0 || n[5])))
|
||
|
{
|
||
|
// remaining pixels need to be connected.
|
||
|
// do not break corner connectivity
|
||
|
if ((n[1] == 0 || n[0] > 1 || n[2] > 1) &&
|
||
|
(n[3] == 0 || n[2] > 1 || n[4] > 1) &&
|
||
|
(n[5] == 0 || n[4] > 1 || n[6] > 1) &&
|
||
|
(n[7] == 0 || n[6] > 1 || n[0] > 1))
|
||
|
{
|
||
|
// opposite faces
|
||
|
// (special condition so double thick lines
|
||
|
// will not be completely eroded)
|
||
|
if ((n[0] == 0 || n[4] == 0 || n[2] > 1 || n[6] > 1) &&
|
||
|
(n[2] == 0 || n[6] == 0 || n[0] > 1 || n[4] > 1))
|
||
|
{
|
||
|
// check to stop pruning (sort of a hack huristic)
|
||
|
if (prune > 1 || (countFaces > 2) ||
|
||
|
((countFaces == 2) && (countCorners > 1)))
|
||
|
{
|
||
|
*inPtr0 = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
inPtr0 += inInc0;
|
||
|
}
|
||
|
inPtr1 += inInc1;
|
||
|
}
|
||
|
inPtr2 += inInc2;
|
||
|
}
|
||
|
++inPtrC;
|
||
|
}
|
||
|
|
||
|
|
||
|
// copy to output
|
||
|
for (idxC = 0; idxC < numComps; ++idxC)
|
||
|
{
|
||
|
outPtr2 = outPtr;
|
||
|
inPtr2 = inPtr;
|
||
|
for (idx2 = outMin2; idx2 <= outMax2; ++idx2)
|
||
|
{
|
||
|
outPtr1 = outPtr2;
|
||
|
inPtr1 = inPtr2;
|
||
|
for (idx1 = outMin1; idx1 <= outMax1; ++idx1)
|
||
|
{
|
||
|
outPtr0 = outPtr1;
|
||
|
inPtr0 = inPtr1;
|
||
|
for (idx0 = outMin0; idx0 <= outMax0; ++idx0)
|
||
|
{
|
||
|
if (*inPtr0 <= 1)
|
||
|
{
|
||
|
*outPtr0 = (T)(0.0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*outPtr0 = *inPtr0;
|
||
|
}
|
||
|
|
||
|
inPtr0 += inInc0;
|
||
|
outPtr0 += outInc0;
|
||
|
}
|
||
|
inPtr1 += inInc1;
|
||
|
outPtr1 += outInc1;
|
||
|
}
|
||
|
inPtr2 += inInc2;
|
||
|
outPtr2 += outInc2;
|
||
|
}
|
||
|
++inPtr;
|
||
|
++outPtr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// This method contains the first switch statement that calls the correct
|
||
|
// templated function for the input and output region types.
|
||
|
void vtkImageSkeleton2D::ThreadedExecute(vtkImageData *inData,
|
||
|
vtkImageData *outData,
|
||
|
int outExt[6], int id)
|
||
|
{
|
||
|
void *inPtr;
|
||
|
void *outPtr = outData->GetScalarPointerForExtent(outExt);
|
||
|
vtkImageData *tempData;
|
||
|
int inExt[6];
|
||
|
|
||
|
// this filter expects that input is the same type as output.
|
||
|
if (inData->GetScalarType() != outData->GetScalarType())
|
||
|
{
|
||
|
vtkErrorMacro(<< "Execute: input ScalarType, " << inData->GetScalarType()
|
||
|
<< ", must match out ScalarType " << outData->GetScalarType());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
vtkInformation* inInfo = inData->GetPipelineInformation();
|
||
|
inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt);
|
||
|
|
||
|
vtkInformation *inScalarInfo = vtkDataObject::GetActiveFieldInformation(inInfo,
|
||
|
vtkDataObject::FIELD_ASSOCIATION_POINTS, vtkDataSetAttributes::SCALARS);
|
||
|
if (!inScalarInfo)
|
||
|
{
|
||
|
vtkErrorMacro("Missing ActiveScalar info in input information!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Make a temporary copy of the input data
|
||
|
tempData = vtkImageData::New();
|
||
|
tempData->SetScalarType( inScalarInfo->Get(vtkImageData::FIELD_ARRAY_TYPE()) );
|
||
|
tempData->SetExtent(inExt);
|
||
|
tempData->SetNumberOfScalarComponents(
|
||
|
inScalarInfo->Get(vtkImageData::FIELD_NUMBER_OF_COMPONENTS()) );
|
||
|
tempData->CopyAndCastFrom(inData, inExt);
|
||
|
|
||
|
inPtr = tempData->GetScalarPointerForExtent(outExt);
|
||
|
switch (tempData->GetScalarType())
|
||
|
{
|
||
|
vtkTemplateMacro(
|
||
|
vtkImageSkeleton2DExecute( this, tempData,
|
||
|
(VTK_TT *)(inPtr), outData, outExt,
|
||
|
(VTK_TT *)(outPtr), id));
|
||
|
default:
|
||
|
vtkErrorMacro(<< "Execute: Unknown ScalarType");
|
||
|
tempData->Delete();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
tempData->Delete();
|
||
|
}
|
||
|
|
||
|
void vtkImageSkeleton2D::PrintSelf(ostream& os, vtkIndent indent)
|
||
|
{
|
||
|
this->Superclass::PrintSelf(os,indent);
|
||
|
|
||
|
os << indent << "Prune: " << (this->Prune ? "On\n" : "Off\n");
|
||
|
|
||
|
}
|
||
|
|