Skip to content

Commit 0d4960b

Browse files
committed
CS-82: Updates to AirwaysFromCenterline
1 parent 6c34fd9 commit 0d4960b

File tree

5 files changed

+150
-135
lines changed

5 files changed

+150
-135
lines changed

CMake/cxInitializeLibraries.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ macro(cx_initialize_VTK)
185185
vtkInteractionWidgets
186186
vtkParallelCore
187187
vtkFiltersParallel
188-
vtkImagingMath vtkImagingMorphological vtkImagingColor vtkImagingStatistics
188+
vtkImagingMath vtkImagingMorphological vtkImagingColor vtkImagingStatistics vtkImagingStencil
189189
vtkRenderingOpenGL2
190190
vtkRenderingVolumeOpenGL2
191191
NO_MODULE)

source/plugins/org.custusx.filter.airwaysfromcenterline/cxAirwaysFromCenterline.cpp

+130-126
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22

33
#include "cxAirwaysFromCenterline.h"
44
#include <vtkPolyData.h>
5-
//#include "cxDoubleDataAdapterXml.h"
65
#include "cxBranchList.h"
76
#include "cxBranch.h"
8-
#include <vtkCellArray.h>
97
#include "vtkCardinalSpline.h"
108
#include <vtkLine.h>
11-
#include <vtkCellArray.h>
129
#include <vtkTubeFilter.h>
1310
#include <vtkLineSource.h>
14-
#include <vtkCleanPolyData.h>
15-
#include <vtkAppendPolyData.h>
16-
#include <vtkImplicitModeller.h>
1711
#include <vtkContourFilter.h>
12+
#include <vtkPolyDataToImageStencil.h>
13+
#include <cxImage.h>
14+
#include "cxContourFilter.h"
15+
#include <vtkImageData.h>
16+
#include <vtkSphereSource.h>
17+
#include <vtkDataSetSurfaceFilter.h>
18+
#include <vtkPointData.h>
19+
#include <vtkImageStencil.h>
1820

19-
typedef vtkSmartPointer<class vtkCardinalSpline> vtkCardinalSplinePtr;
2021

2122
namespace cx
2223
{
@@ -36,12 +37,12 @@ Eigen::MatrixXd AirwaysFromCenterline::getCenterlinePositions(vtkPolyDataPtr cen
3637
{
3738

3839
int N = centerline_r->GetNumberOfPoints();
39-
Eigen::MatrixXd CLpoints(3,N);
40-
for(vtkIdType i = 0; i < N; i++)
41-
{
42-
double p[3];
40+
Eigen::MatrixXd CLpoints(3,N);
41+
for(vtkIdType i = 0; i < N; i++)
42+
{
43+
double p[3];
4344
centerline_r->GetPoint(i,p);
44-
Eigen::Vector3d position;
45+
Eigen::Vector3d position;
4546
position(0) = p[0]; position(1) = p[1]; position(2) = p[2];
4647
CLpoints.block(0 , i , 3 , 1) = position;
4748
}
@@ -64,156 +65,159 @@ void AirwaysFromCenterline::processCenterline(vtkPolyDataPtr centerline_r)
6465
std::cout << "Number of branches in CT centerline: " << mBranchListPtr->getBranches().size() << std::endl;
6566
}
6667

67-
68-
/*
69-
RouteToTarget::smoothBranch is smoothing the positions of a centerline branch by using vtkCardinalSpline.
70-
The degree of smoothing is dependent on the branch radius and the shape of the branch.
71-
First, the method tests if a straight line from start to end of the branch is sufficient by the condition of
72-
all positions along the line being within the lumen of the airway (max distance from original centerline
73-
is set to branch radius).
74-
If this fails, one more control point is added to the spline at the time, until the condition is fulfilled.
75-
The control point added for each iteration is the position with the larges deviation from the original/unfiltered
76-
centerline.
77-
*/
78-
std::vector< Eigen::Vector3d > AirwaysFromCenterline::smoothBranch(BranchPtr branchPtr, int startIndex, Eigen::MatrixXd startPosition)
68+
vtkPolyDataPtr AirwaysFromCenterline::generateTubes()
7969
{
80-
vtkCardinalSplinePtr splineX = vtkSmartPointer<vtkCardinalSpline>::New();
81-
vtkCardinalSplinePtr splineY = vtkSmartPointer<vtkCardinalSpline>::New();
82-
vtkCardinalSplinePtr splineZ = vtkSmartPointer<vtkCardinalSpline>::New();
83-
84-
double branchRadius = branchPtr->findBranchRadius();
70+
std::vector<BranchPtr> branches = mBranchListPtr->getBranches();
8571

86-
//add control points to spline
72+
//------- create image data ---------------
73+
vtkImageDataPtr resultImage = vtkImageDataPtr::New();
74+
vtkPointsPtr pointsPtr = vtkPointsPtr::New();
8775

88-
//add first position
89-
Eigen::MatrixXd positions = branchPtr->getPositions();
90-
splineX->AddPoint(0,startPosition(0));
91-
splineY->AddPoint(0,startPosition(1));
92-
splineZ->AddPoint(0,startPosition(2));
76+
int numberOfPoints = 0;
77+
for (int i = 0; i < branches.size(); i++)
78+
numberOfPoints += branches[i]->getPositions().cols();
9379

80+
pointsPtr->SetNumberOfPoints(numberOfPoints);
9481

95-
// Add last position if no parent branch, else add parents position closest to current branch.
96-
// Branch positions are stored in order from head to feet (e.g. first position is top of trachea),
97-
// while route-to-target is generated from target to top of trachea.
98-
if(!branchPtr->getParentBranch())
82+
int pointIndex = 0;
83+
for (int i = 0; i < branches.size(); i++)
9984
{
100-
splineX->AddPoint(startIndex,positions(0,0));
101-
splineY->AddPoint(startIndex,positions(1,0));
102-
splineZ->AddPoint(startIndex,positions(2,0));
85+
Eigen::MatrixXd positions = branches[i]->getPositions();
86+
for (int j = 0; j < positions.cols(); j++)
87+
{
88+
pointsPtr->SetPoint(pointIndex, positions(0,j), positions(1,j), positions(2,j));
89+
pointIndex += 1;
90+
}
10391
}
104-
else
105-
{
106-
Eigen::MatrixXd parentPositions = branchPtr->getParentBranch()->getPositions();
107-
splineX->AddPoint(startIndex,parentPositions(0,parentPositions.cols()-1));
108-
splineY->AddPoint(startIndex,parentPositions(1,parentPositions.cols()-1));
109-
splineZ->AddPoint(startIndex,parentPositions(2,parentPositions.cols()-1));
11092

111-
}
93+
double bounds[6];
94+
pointsPtr->GetBounds(bounds);
11295

113-
//Add points until all filtered/smoothed postions are within branch radius distance of the unfiltered branch centerline posiition.
114-
//This is to make sure the smoothed centerline is within the lumen of the airways.
115-
double maxDistanceToOriginalPosition = branchRadius;
116-
int maxDistanceIndex = -1;
117-
std::vector< Eigen::Vector3d > smoothingResult;
96+
//Extend bounds to make room for surface model extended from centerline
97+
bounds[0] -= 10;
98+
bounds[1] += 10;
99+
bounds[2] -= 10;
100+
bounds[3] += 10;
101+
bounds[4] -= 10;
102+
bounds[5] -= 2; // to make top of trachea open
118103

119-
//add positions to spline
120-
while (maxDistanceToOriginalPosition >= branchRadius && splineX->GetNumberOfPoints() < startIndex)
121-
{
122-
if(maxDistanceIndex > 0)
123-
{
124-
//add to spline the position with largest distance to original position
125-
splineX->AddPoint(maxDistanceIndex,positions(0,startIndex - maxDistanceIndex));
126-
splineY->AddPoint(maxDistanceIndex,positions(1,startIndex - maxDistanceIndex));
127-
splineZ->AddPoint(maxDistanceIndex,positions(2,startIndex - maxDistanceIndex));
128-
}
104+
double spacing[3];
105+
spacing[0] = 0.5; //Smaller spacing improves resolution but increases run-time
106+
spacing[1] = 0.5;
107+
spacing[2] = 0.5;
108+
resultImage->SetSpacing(spacing);
129109

130-
//evaluate spline - get smoothed positions
131-
maxDistanceToOriginalPosition = 0.0;
132-
smoothingResult.clear();
133-
for(int j=0; j<=startIndex; j++)
134-
{
135-
double splineParameter = j;
136-
Eigen::Vector3d tempPoint;
137-
tempPoint(0) = splineX->Evaluate(splineParameter);
138-
tempPoint(1) = splineY->Evaluate(splineParameter);
139-
tempPoint(2) = splineZ->Evaluate(splineParameter);
140-
smoothingResult.push_back(tempPoint);
141-
142-
//calculate distance to original (non-filtered) position
143-
double distance = findDistanceToLine(tempPoint, positions);
144-
//finding the index with largest distance
145-
if (distance > maxDistanceToOriginalPosition)
146-
{
147-
maxDistanceToOriginalPosition = distance;
148-
maxDistanceIndex = j;
149-
}
150-
}
151-
}
110+
// compute dimensions
111+
int dim[3];
112+
for (int i = 0; i < 3; i++)
113+
dim[i] = static_cast<int>(std::ceil((bounds[i * 2 + 1] - bounds[i * 2]) / spacing[i]));
152114

153-
return smoothingResult;
154-
}
155115

156-
vtkPolyDataPtr AirwaysFromCenterline::generateTubes()
157-
{
116+
resultImage->SetDimensions(dim);
117+
resultImage->SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1);
158118

159-
vtkPolyDataPtr output = vtkPolyDataPtr::New();
160-
vtkLineSourcePtr lineSourcePtr = vtkLineSourcePtr::New();
161-
std::vector<BranchPtr> branches = mBranchListPtr->getBranches();
119+
double origin[3];
120+
origin[0] = bounds[0] + spacing[0] / 2;
121+
origin[1] = bounds[2] + spacing[1] / 2;
122+
origin[2] = bounds[4] + spacing[2] / 2;
123+
resultImage->SetOrigin(origin);
124+
125+
resultImage->AllocateScalars(VTK_UNSIGNED_CHAR,1);
162126

127+
128+
//-----Generate tumes and spheres for each branch----------
129+
vtkLineSourcePtr lineSourcePtr = vtkLineSourcePtr::New();
163130
for (int i = 0; i < branches.size(); i++)
164131
{
165132
Eigen::MatrixXd positions = branches[i]->getPositions();
166-
vtkPointsPtr pointsPtr = vtkPointsPtr::New();;
167-
pointsPtr->SetNumberOfPoints(positions.cols());
133+
vtkPointsPtr pointsPtr = vtkPointsPtr::New();
134+
int numberOfBranchPositions = positions.cols();
135+
pointsPtr->SetNumberOfPoints(numberOfBranchPositions);
136+
int displaceIndex = 0;
168137

169-
for (int j = 0; j < positions.cols(); j++)
138+
if (branches[i]->getParentBranch()) // Add parents last position to get connected tubes
170139
{
171-
pointsPtr->SetPoint(j, positions(0,j), positions(1,j), positions(2,j));
140+
displaceIndex = 1;
141+
pointsPtr->SetNumberOfPoints(numberOfBranchPositions + 1);
142+
Eigen::MatrixXd parentPositions = branches[i]->getParentBranch()->getPositions();
143+
int numberOfParentBranchPositions = parentPositions.cols();
144+
pointsPtr->SetPoint(0, parentPositions(0,numberOfParentBranchPositions-1),
145+
parentPositions(1,numberOfParentBranchPositions-1),
146+
parentPositions(2,numberOfParentBranchPositions-1));
172147
}
148+
149+
for (int j = 0; j < numberOfBranchPositions; j++)
150+
pointsPtr->SetPoint(j + displaceIndex, positions(0,j), positions(1,j), positions(2,j));
151+
173152
lineSourcePtr->SetPoints(pointsPtr);
174153

175154
// Create a tube (cylinder) around the line
176-
vtkSmartPointer<vtkTubeFilter> tubeFilterPtr = vtkSmartPointer<vtkTubeFilter>::New();
155+
vtkTubeFilterPtr tubeFilterPtr = vtkTubeFilterPtr::New();
177156
tubeFilterPtr->SetInputConnection(lineSourcePtr->GetOutputPort());
178157
tubeFilterPtr->SetRadius(branches[i]->findBranchRadius()); //default is .5
179158
tubeFilterPtr->SetNumberOfSides(50);
180159
tubeFilterPtr->Update();
181160

182-
//Append the new mesh
183-
vtkAppendPolyDataPtr appendFilterPtr = vtkSmartPointer<vtkAppendPolyData>::New();
184-
appendFilterPtr->AddInputData(output);
185-
appendFilterPtr->AddInputData(tubeFilterPtr->GetOutput());
186-
appendFilterPtr->Update();
187-
188-
// Remove any duplicate points.
189-
vtkSmartPointer<vtkCleanPolyData> cleanFilterPtr = vtkSmartPointer<vtkCleanPolyData>::New();
190-
cleanFilterPtr->SetInputConnection(appendFilterPtr->GetOutputPort());
191-
cleanFilterPtr->Update();
192-
193-
output = cleanFilterPtr->GetOutput();
161+
//Add mesh from tube to image
162+
vtkPolyDataToImageStencilPtr pol2stencTube = vtkPolyDataToImageStencilPtr::New();
163+
pol2stencTube->SetInputData(tubeFilterPtr->GetOutput());
164+
pol2stencTube->SetOutputOrigin(origin);
165+
pol2stencTube->SetOutputSpacing(spacing);
166+
pol2stencTube->SetOutputWholeExtent(resultImage->GetExtent());
167+
pol2stencTube->Update();
168+
169+
vtkImageStencilPtr imgstencTube = vtkImageStencilPtr::New();
170+
imgstencTube->SetInputData(resultImage);
171+
imgstencTube->SetStencilData(pol2stencTube->GetOutput());
172+
imgstencTube->ReverseStencilOn();
173+
imgstencTube->Update();
174+
175+
resultImage = imgstencTube->GetOutput();
176+
177+
178+
//Add sphere at end of branch
179+
vtkSphereSourcePtr spherePtr = vtkSphereSourcePtr::New();
180+
spherePtr->SetCenter(positions(0,numberOfBranchPositions-1),
181+
positions(1,numberOfBranchPositions-1),
182+
positions(2,numberOfBranchPositions-1));
183+
spherePtr->SetRadius(1.05 * branches[i]->findBranchRadius()); // 5% larger radius to close holes
184+
spherePtr->SetThetaResolution(50);
185+
spherePtr->SetPhiResolution(50);
186+
spherePtr->Update();
187+
188+
//Add sphere to image
189+
vtkPolyDataToImageStencilPtr pol2stencSphere = vtkPolyDataToImageStencilPtr::New();
190+
pol2stencSphere->SetInputData(spherePtr->GetOutput());
191+
pol2stencSphere->SetOutputOrigin(origin);
192+
pol2stencSphere->SetOutputSpacing(spacing);
193+
pol2stencSphere->SetOutputWholeExtent(resultImage->GetExtent());
194+
pol2stencSphere->Update();
195+
196+
197+
vtkImageStencilPtr imgstencSphere = vtkImageStencilPtr::New();
198+
imgstencSphere->SetInputData(resultImage);
199+
imgstencSphere->SetStencilData(pol2stencSphere->GetOutput());
200+
imgstencSphere->ReverseStencilOn();
201+
imgstencSphere->Update();
202+
203+
resultImage = imgstencSphere->GetOutput();
194204
}
195205

196-
// Create the implicit modeller
197-
vtkSmartPointer<vtkImplicitModeller> blobbyLogoImp =vtkSmartPointer<vtkImplicitModeller>::New();
198-
blobbyLogoImp->SetInputData(output);
199-
blobbyLogoImp->SetMaximumDistance(0.1);
200-
blobbyLogoImp->SetSampleDimensions(256, 256, 256);
201-
blobbyLogoImp->SetAdjustDistance(0.1);
202206

203-
// Extract an iso surface, i.e. the tube shell
204-
vtkContourFilterPtr blobbyLogoIso = vtkSmartPointer<vtkContourFilter>::New();
205-
blobbyLogoIso->SetInputConnection(blobbyLogoImp->GetOutputPort());
206-
blobbyLogoIso->SetValue(1, 1.5); //orig
207-
blobbyLogoIso->Update();
207+
//create contour from image
208+
vtkPolyDataPtr rawContour = ContourFilter::execute(
209+
resultImage,
210+
1, //treshold
211+
false, // reduce resolution
212+
true, // smoothing
213+
true, // keep topology
214+
0 // target decimation
215+
);
208216

209-
output = blobbyLogoIso->GetOutput();
217+
return rawContour;
210218

211-
return output;
212219
}
213220

214-
215-
216-
217221
double findDistanceToLine(Eigen::MatrixXd point, Eigen::MatrixXd line)
218222
{
219223
double minDistance = findDistance(point, line.col(0));

source/plugins/org.custusx.filter.airwaysfromcenterline/cxAirwaysFromCenterline.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
#include "cxMesh.h"
55
#include <QDomElement>
66

7+
typedef vtkSmartPointer<class vtkImageStencil> vtkImageStencilPtr;
8+
typedef vtkSmartPointer<class vtkCardinalSpline> vtkCardinalSplinePtr;
9+
typedef vtkSmartPointer<class vtkTubeFilter> vtkTubeFilterPtr;
10+
typedef vtkSmartPointer<class vtkCleanPolyData> vtkCleanPolyDataPtr;
11+
typedef vtkSmartPointer<class vtkPolyDataToImageStencil> vtkPolyDataToImageStencilPtr;
712

813
namespace cx
914
{
@@ -27,7 +32,6 @@ class AirwaysFromCenterline
2732
private:
2833
Eigen::MatrixXd mCLpoints;
2934
BranchListPtr mBranchListPtr;
30-
std::vector<Eigen::Vector3d> smoothBranch(BranchPtr branchPtr, int startIndex, Eigen::MatrixXd startPosition);
3135
};
3236

3337
double findDistanceToLine(Eigen::MatrixXd point, Eigen::MatrixXd line);

source/plugins/org.custusx.filter.airwaysfromcenterline/cxAirwaysFromCenterlineFilterService.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ void AirwaysFromCenterlineFilter::createOutputTypes()
8888
tempAirwaysModelMeshStringAdapter->setValueName("Airways surface model mesh");
8989
tempAirwaysModelMeshStringAdapter->setHelp("Generated airways surface model mesh (vtk-format).");
9090
mOutputTypes.push_back(tempAirwaysModelMeshStringAdapter);
91+
92+
StringPropertySelectMeshPtr tempSmoothedCenterlineMeshStringAdapter;
93+
tempSmoothedCenterlineMeshStringAdapter = StringPropertySelectMesh::New(mServices->patient());
94+
tempSmoothedCenterlineMeshStringAdapter->setValueName("Smoothed enterline");
95+
tempSmoothedCenterlineMeshStringAdapter->setHelp("Smoothed centerline (vtk-format).");
96+
mOutputTypes.push_back(tempSmoothedCenterlineMeshStringAdapter);
9197
}
9298

9399

@@ -103,10 +109,10 @@ bool AirwaysFromCenterlineFilter::execute()
103109

104110
mAirwaysFromCenterline->processCenterline(centerline_r);
105111

106-
//note: mOutput is in reference space
107-
mOutput = mAirwaysFromCenterline->generateTubes();
112+
//note: mOutputAirwayMesh is in reference space
113+
mOutputAirwayMesh = mAirwaysFromCenterline->generateTubes();
108114

109-
//if(mOutput->GetNumberOfPoints() < 1)
115+
//if(mOutputAirwayMesh->GetNumberOfPoints() < 1)
110116
// return false;
111117

112118
return true;
@@ -123,10 +129,10 @@ bool AirwaysFromCenterlineFilter::postProcess()
123129
QString nameCenterline = inputMesh->getName() + AirwaysFromCenterlineFilter::getNameSuffix() + "%1";
124130

125131
MeshPtr outputMesh = patientService()->createSpecificData<Mesh>(uidCenterline, nameCenterline);
126-
outputMesh->setVtkPolyData(mOutput);
132+
outputMesh->setVtkPolyData(mOutputAirwayMesh);
127133
patientService()->insertData(outputMesh);
128134

129-
//note: mOutput and outputMesh is in reference(r) space
135+
//note: mOutputAirwayMesh and outputMesh is in reference(r) space
130136

131137

132138
//Meshes are expected to be in data(d) space

0 commit comments

Comments
 (0)