Skip to content

Commit 75fa2e7

Browse files
authored
Merge pull request #114 from Gregoire-V/ShapeToDensityFeature
Feature to convert a tracks file to a density map
2 parents 304c95e + 3b5e3f4 commit 75fa2e7

File tree

1 file changed

+182
-49
lines changed

1 file changed

+182
-49
lines changed
Lines changed: 182 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,236 @@
1+
#include <animaShapesReader.h>
2+
#include <animaReadWriteFunctions.h>
3+
#include <itkVectorImage.h>
4+
#include <itkImageIOFactory.h>
5+
#include <itkCastImageFilter.h>
6+
7+
#include <vtkVector.h>
8+
19
#include <tclap/CmdLine.h>
10+
#include <fstream>
11+
12+
13+
14+
/**
15+
* @brief Increment voxels between two consecutive track points.
16+
* @param index Index of the voxel of the current point
17+
* @param indexNext Index of the voxel of the following point
18+
* @param incrementTerm Term which will be added to each interpolate voxel (either 1, or 1/total_number_of_tracks if proportionArg is true)
19+
* @param outImage Pointer to the output image, used to add incrementTerm to each interpolate voxel
20+
* @param checkInterpolateIsInsideImage If true, we must check that interpolate voxels are inside image before adding incrementTerm
21+
* @details Add interpolation voxels if two consecutive track points are separated by at least one voxel. Then increment the number or proprtion of tracks
22+
* passing through these voxels.
23+
*/
24+
template <typename IndexType>
25+
void incrementInterpolateVoxels(IndexType index, IndexType indexNext, double incrementTerm, itk::Image <double, 3>::Pointer outImage,
26+
bool checkInterpolataleIsInsideImage = false)
27+
{
28+
IndexType indexTemp; //where the interpolate voxels' index will be stored
29+
itk::Image <double, 3>::RegionType region = outImage->GetLargestPossibleRegion(); //the whole image
30+
bool insideImage = true;
231

3-
#include <animaReadWriteFunctions.h>
4-
#include <animaShapesReader.h>
532

6-
#include <vtkSmartPointer.h>
7-
#include <vtkPoints.h>
8-
#include <vtkPointData.h>
9-
#include <vtkPolyData.h>
10-
#include <vtkCell.h>
33+
double dx = indexNext[0] - index[0]; //difference in x coordinate
34+
double dy = indexNext[1] - index[1]; //difference in y coordinate
35+
double dz = indexNext[2] - index[2]; //difference in z coordinate
36+
double maxStep = std::max(std::max(std::abs(dx), std::abs(dy)), std::abs(dz)); //maximum between the three absolute differences
37+
for (int l = 1; l < maxStep; l++)
38+
{
39+
//We enter this loop only if maxStep >= 2.
40+
//In that case, we create interpolation voxels, to try to modify all and only all the voxels through which the track passes between point and pointNext
41+
//We repeat maxStep - 1 times and not maxStep times, because otherwise, index = indexNext, and we would increment indexNext twice
42+
indexTemp[0] = index[0] + std::round(l*dx/maxStep);
43+
indexTemp[1] = index[1] + std::round(l*dy/maxStep);
44+
indexTemp[2] = index[2] + std::round(l*dz/maxStep);
45+
46+
if (checkInterpolataleIsInsideImage)
47+
{
48+
unsigned int k = 0;
49+
while (insideImage && k<3)
50+
{
51+
insideImage = (indexTemp[k] < 0) || (indexTemp[k] >= region.GetSize(k));
52+
++k;
53+
}
54+
//insideImage is true if and only if the index of the interpolate voxel is within the image
55+
}
1156

12-
#include <itkCastImageFilter.h>
57+
//increment the number or proportion of tracks passing through the voxel with the index indexTemp, if it is inside the image
58+
if (insideImage)
59+
{
60+
outImage->SetPixel(indexTemp, outImage->GetPixel(indexTemp) + incrementTerm);
61+
}
62+
}
63+
}
1364

65+
/**
66+
* @brief Convert an input track file (vtp or vtk) into a density map.
67+
* @details The density map is a 3D image giving for each voxel the number or proportion of tracks from the input image passing through it.
68+
*/
1469
int main(int argc, char **argv)
1570
{
16-
TCLAP::CmdLine cmd("Filters fibers from a vtp file using a label image and specifying with several -t and -f which labels should be touched or are forbidden for each fiber. INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION);
71+
TCLAP::CmdLine cmd("This binary converts an input tracks file (vtp or vtk) into a density map, giving for each voxel the number or proportion of tracks from the input image passing through it. \n Warning : Anima uses LPS convention; if input file uses another convention (for example RAS) one must convert to LPS convention first. \n For example to move from RAS to LPS convention, one must rotate the input tracks file by 180° around z-axis. \n INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION);
1772

18-
TCLAP::ValueArg<std::string> inArg("i","input","input tracks file",true,"","input tracks",cmd);
19-
TCLAP::ValueArg<std::string> outArg("o","output","output mask image",true,"","output mask image",cmd);
20-
TCLAP::ValueArg<std::string> geomArg("g","geometry","Geometry image",true,"","geometry image",cmd);
73+
//Define arguments
74+
TCLAP::ValueArg<std::string> inArg("i", "input", "Input tracks file (vtp, vtk)", true, "", "input tracks file (vtp, vtk)", cmd);
75+
TCLAP::ValueArg<std::string> outArg("o", "output", "Output density map", true, "", "output density map file", cmd);
76+
TCLAP::ValueArg<std::string> geomArg("g", "geometry", "Geometry image", true, "", "geometry image, file with ext .nii.gz or similar", cmd);
2177

22-
TCLAP::SwitchArg proportionArg("P","proportion","Output proportion of fibers going through each pixel",cmd,false);
78+
TCLAP::SwitchArg proportionArg("P", "proportion", "Output proportion of tracks going through each voxel", cmd, false);
79+
TCLAP::SwitchArg noInterpolationArg("N", "no-interpolatation", "No creation of interpolation voxels if two consecutive track points are separated by at least one voxel", cmd, false);
2380

81+
82+
//Try to parse
2483
try
2584
{
26-
cmd.parse(argc,argv);
85+
cmd.parse(argc, argv);
2786
}
2887
catch (TCLAP::ArgException& e)
2988
{
3089
std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl;
3190
return EXIT_FAILURE;
3291
}
3392

34-
typedef itk::Image <double, 3> OutputImageType;
35-
OutputImageType::Pointer outputImage = anima::readImage <OutputImageType> (geomArg.getValue());
36-
outputImage->FillBuffer(0.0);
93+
//Define types
94+
using ImageType = itk::Image <double, 3> ;
95+
using OutImageType = itk::Image <double, 3>;
96+
97+
//Read geometry image and allocate memory for output image
98+
ImageType::Pointer geomImage = anima::readImage <ImageType> (geomArg.getValue());
99+
OutImageType::Pointer outImage = OutImageType::New();
100+
101+
//Give to the output image the same properties as the geometry image
102+
outImage->Initialize();
103+
outImage->SetRegions(geomImage->GetLargestPossibleRegion());
104+
outImage->SetSpacing(geomImage->GetSpacing());
105+
outImage->SetOrigin(geomImage->GetOrigin());
106+
outImage->SetDirection(geomImage->GetDirection());
37107

108+
//Allocate the correct size for the output image and intialize it with 0 in each voxel
109+
outImage->Allocate();
110+
outImage->FillBuffer(0.0);
111+
112+
//Read input track file
38113
anima::ShapesReader trackReader;
39114
trackReader.SetFileName(inArg.getValue());
40115
trackReader.Update();
41116

42-
vtkSmartPointer <vtkPolyData> tracks = trackReader.GetOutput();
43-
117+
//Initialize a vtksmartpointer with the content of the track file, and store the total number of tracks (1 cell = 1 track)
118+
vtkSmartPointer<vtkPolyData> tracks = trackReader.GetOutput();
44119
vtkIdType nbCells = tracks->GetNumberOfCells();
45-
double incrementFactor = 1.0;
120+
121+
//Set the increment term (1 if proportion arg is not set, 1/total_number_of_tracks else)
122+
double incrementTerm = 1.0;
46123
if (proportionArg.isSet())
47-
incrementFactor /= nbCells;
124+
{
125+
incrementTerm /= nbCells;
126+
}
127+
128+
//Set interpolateVoxels (true if and only if we need to add interpolate voxels)
129+
bool interpolateVoxels = !noInterpolationArg.isSet();
48130

49-
double ptVals[3];
50-
OutputImageType::IndexType currentIndex;
51-
OutputImageType::PointType currentPoint;
52-
OutputImageType::RegionType region = outputImage->GetLargestPossibleRegion();
131+
//Initialize data objects for the main algorithm
132+
double ptVals[3], ptValsNext[3];
133+
OutImageType::IndexType index, indexNext, indexTemp;
134+
OutImageType::PointType point, pointNext;
53135

54-
// Explores individual fibers
55-
for (int j = 0;j < nbCells;++j)
136+
137+
//Main algorithm
138+
for (int j = 0; j < nbCells; ++j)
56139
{
140+
//Get track j, its points and its number of points
57141
vtkCell *cell = tracks->GetCell(j);
58142
vtkPoints *cellPts = cell->GetPoints();
59143
vtkIdType nbPts = cellPts->GetNumberOfPoints();
60144

61-
// Explores points in fibers
62-
for (int i = 0;i < nbPts;++i)
145+
//Iterate through the track j's points
146+
for (int i = 0; i < nbPts-1; ++i)
63147
{
64-
cellPts->GetPoint(i,ptVals);
65-
66-
for (unsigned int k = 0;k < 3;++k)
67-
currentPoint[k] = ptVals[k];
148+
//Get the current point and the following one, write their coordinates in point and pointNext
149+
cellPts->GetPoint(i, ptVals);
150+
cellPts->GetPoint(i+1, ptValsNext);
151+
for (unsigned int k = 0; k < 3; ++k)
152+
{
153+
point[k] = ptVals[k];
154+
pointNext[k] = ptValsNext[k];
155+
}
68156

69-
outputImage->TransformPhysicalPointToIndex(currentPoint,currentIndex);
70-
bool insideBuffer = true;
71-
for (unsigned int k = 0;k < 3;++k)
157+
//TransformPhysicalPointToIndex enables to move from point coordinates to voxel index inside the output image. It returns true if the point is inside the image and false otherwise (in theory it should not happen)
158+
if (outImage->TransformPhysicalPointToIndex(point, index))
72159
{
73-
if ((currentIndex[k] < 0)||(currentIndex[k] >= region.GetSize(k)))
160+
if (!outImage->TransformPhysicalPointToIndex(pointNext, indexNext))
161+
{
162+
//The current point is inside the image but not the following one
163+
outImage->SetPixel(index, outImage->GetPixel(index) + incrementTerm); //increment the current point's voxel
164+
if (interpolateVoxels)
165+
{
166+
//Increment voxels between index of the current point and index of the following one
167+
//As pointNext is not inside the image, we may create interpolate voxels which are not neither. So we put set last argument to true to check that interpolateVoxels are inside the image within the function
168+
incrementInterpolateVoxels(index, indexNext, incrementTerm, outImage, true);
169+
}
170+
}
171+
172+
//Both current point and following one are inside the image (common case)
173+
174+
//if index==indexNext, the current point and the following one are in the same voxel
175+
//so we don't increment the number or proportion of tracks passing through this voxel for now: we will do it in the next iteration
176+
//that allows not to count one track several times in a voxel
177+
if (index != indexNext)
74178
{
75-
insideBuffer = false;
76-
break;
179+
//increment the current point's voxel, and interpolation voxels if interpolateVoxels is true
180+
outImage->SetPixel(index, outImage->GetPixel(index) + incrementTerm);
181+
if (interpolateVoxels)
182+
{
183+
incrementInterpolateVoxels(index, indexNext, incrementTerm, outImage);
184+
}
77185
}
78186
}
187+
else
188+
{
189+
//Current point is not inside the image, but following point is
190+
if (outImage->TransformPhysicalPointToIndex(pointNext, indexNext))
191+
{
192+
if (interpolateVoxels)
193+
{
194+
//Increment interpolate voxels, but need to check if they are inside the image (so last argument set to true)
195+
incrementInterpolateVoxels(index, indexNext, incrementTerm, outImage, true);
196+
}
197+
}
198+
199+
//If neither current point, nor the following one are inside the image, we have nothing to do in this iteration.
79200

80-
if (!insideBuffer)
81-
continue;
201+
}
202+
}
82203

83-
double countIndex = outputImage->GetPixel(currentIndex) + incrementFactor;
84-
outputImage->SetPixel(currentIndex, countIndex);
204+
//Before moving to the next track, it remains to consider the last point of this track (its voxel has not been incremented yet)
205+
cellPts->GetPoint(nbPts-1, ptVals);
206+
for (unsigned int k = 0; k < 3; ++k)
207+
{
208+
point[k] = ptVals[k];
209+
}
210+
if (outImage->TransformPhysicalPointToIndex(point, index))
211+
{
212+
outImage->SetPixel(index, outImage->GetPixel(index) + incrementTerm);
85213
}
86214
}
87215

216+
217+
//Write output image and exit
88218
if (proportionArg.isSet())
89-
anima::writeImage <OutputImageType> (outArg.getValue(),outputImage);
219+
{
220+
anima::writeImage <OutImageType> (outArg.getValue(), outImage);
221+
}
90222
else
91223
{
92-
using MaskImageType = itk::Image <unsigned int, 3>;
93-
using CastFilterType = itk::CastImageFilter <OutputImageType, MaskImageType>;
224+
//We should convert the output image type from double to int first
225+
using UIntImageType = itk::Image <unsigned int, 3>;
226+
using CastFilterType = itk::CastImageFilter <OutImageType, UIntImageType>;
94227

95228
CastFilterType::Pointer castFilter = CastFilterType::New();
96-
castFilter->SetInput(outputImage);
229+
castFilter->SetInput(outImage);
97230
castFilter->Update();
98231

99-
anima::writeImage <MaskImageType> (outArg.getValue(), castFilter->GetOutput());
232+
anima::writeImage <UIntImageType> (outArg.getValue(), castFilter->GetOutput());
100233
}
101234

102235
return EXIT_SUCCESS;
103-
}
236+
}

0 commit comments

Comments
 (0)