Skip to content

Commit 870558c

Browse files
authored
Merge pull request #54 from ocommowi/lesion-evolution
Add lesion evolution detection tool
2 parents 96779d3 + bad4e34 commit 870558c

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
add_subdirectory(detected_components)
22
add_subdirectory(dice_measure)
3+
add_subdirectory(lesion_evolution_detection)
34
add_subdirectory(segmentation_performance_analyzer)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
if(BUILD_TOOLS)
2+
3+
project(animaLesionEvolutionDetection)
4+
5+
## #############################################################################
6+
## List Sources
7+
## #############################################################################
8+
9+
list_source_files(${PROJECT_NAME}
10+
${CMAKE_CURRENT_SOURCE_DIR}
11+
)
12+
13+
## #############################################################################
14+
## add executable
15+
## #############################################################################
16+
17+
add_executable(${PROJECT_NAME}
18+
${${PROJECT_NAME}_CFILES}
19+
)
20+
21+
22+
## #############################################################################
23+
## Link
24+
## #############################################################################
25+
26+
target_link_libraries(${PROJECT_NAME}
27+
${ITKIO_LIBRARIES}
28+
)
29+
30+
## #############################################################################
31+
## install
32+
## #############################################################################
33+
34+
set_exe_install_rules(${PROJECT_NAME})
35+
36+
endif()
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
#include <animaReadWriteFunctions.h>
2+
3+
#include <itkConnectedComponentImageFilter.h>
4+
#include <itkRelabelComponentImageFilter.h>
5+
#include <itkImageRegionIterator.h>
6+
#include <itkMultiThreader.h>
7+
#include <vnl/vnl_matrix.h>
8+
9+
#include <tclap/CmdLine.h>
10+
#include <fstream>
11+
#include <algorithm>
12+
13+
int main(int argc, char **argv)
14+
{
15+
TCLAP::CmdLine cmd("Computes from two segmentations the connected components that grew (L=3), were already there (L=1), or are new (L=2).\n INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION);
16+
17+
TCLAP::ValueArg <std::string> refArg("r", "ref", "Reference binary segmentation", true, "", "reference segmentation", cmd);
18+
TCLAP::ValueArg <std::string> testArg("t", "test", "Test binary segmentation", true, "", "test segmentation", cmd);
19+
TCLAP::ValueArg <std::string> outArg("o", "out", "output image with labeled lesions", true, "", "output labeled lesions", cmd);
20+
21+
TCLAP::ValueArg <double> alphaArg("a", "alpha", "Alpha threshold (in between 0 and 1, default: 0)", false, 0, "alpha threshold", cmd);
22+
TCLAP::ValueArg <double> gammaArg("g", "gamma", "Gamma threshold (*100 %, default: 0.5)", false, 0.5, "gamma threshold", cmd);
23+
TCLAP::ValueArg <double> betaArg("b", "beta", "Beta threshold (in between 0 and 1, default: 0.05)", false, 0.05, "beta threshold", cmd);
24+
25+
TCLAP::ValueArg <unsigned int> minVolumeArg("m", "min-vol", "Minimal volume for the component to be considered (default: 3 mm3)", false, 3, "minimal volume", cmd);
26+
TCLAP::ValueArg <unsigned int> nbpArg("T","numberofthreads","Number of threads to run on (default : all cores)",false,itk::MultiThreader::GetGlobalDefaultNumberOfThreads(),"number of threads",cmd);
27+
28+
TCLAP::SwitchArg fullConnectArg("F","full-connect","Use 26-connectivity instead of 6-connectivity",cmd,false);
29+
30+
try
31+
{
32+
cmd.parse(argc, argv);
33+
}
34+
catch (TCLAP::ArgException& e)
35+
{
36+
std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl;
37+
return EXIT_FAILURE;
38+
}
39+
40+
typedef itk::Image <unsigned short, 3> ImageType;
41+
typedef itk::ImageRegionIterator <ImageType> ImageIteratorType;
42+
43+
ImageType::Pointer refSegmentation = anima::readImage <ImageType> (refArg.getValue());
44+
ImageType::Pointer testSegmentation = anima::readImage <ImageType> (testArg.getValue());
45+
46+
typedef itk::ConnectedComponentImageFilter <ImageType, ImageType> CCFilterType;
47+
typedef itk::RelabelComponentImageFilter <ImageType, ImageType> RelabelComponentFilterType;
48+
49+
CCFilterType::Pointer refCCFilter = CCFilterType::New();
50+
refCCFilter->SetInput(refSegmentation);
51+
refCCFilter->SetFullyConnected(fullConnectArg.isSet());
52+
refCCFilter->SetNumberOfThreads(nbpArg.getValue());
53+
refCCFilter->Update();
54+
55+
ImageType::SpacingType spacing = refSegmentation->GetSpacing();
56+
ImageType::SpacingValueType spacingTot = spacing[0];
57+
for (unsigned int i = 1;i < 3;++i)
58+
spacingTot *= spacing[i];
59+
60+
// Compute minsize in voxels
61+
unsigned int minSizeInVoxel = static_cast <unsigned int> (std::ceil(minVolumeArg.getValue() / spacingTot));
62+
63+
// Remove too small reference objects
64+
RelabelComponentFilterType::Pointer relabelRefFilter = RelabelComponentFilterType::New();
65+
relabelRefFilter->SetInput(refCCFilter->GetOutput());
66+
relabelRefFilter->SetMinimumObjectSize(minSizeInVoxel);
67+
relabelRefFilter->SetNumberOfThreads(nbpArg.getValue());
68+
relabelRefFilter->Update();
69+
70+
// Reference segmentation is now labeled per connected objects
71+
refSegmentation = relabelRefFilter->GetOutput();
72+
refSegmentation->DisconnectPipeline();
73+
74+
CCFilterType::Pointer testCCFilter = CCFilterType::New();
75+
testCCFilter->SetInput(testSegmentation);
76+
testCCFilter->SetFullyConnected(fullConnectArg.isSet());
77+
testCCFilter->SetNumberOfThreads(nbpArg.getValue());
78+
testCCFilter->Update();
79+
80+
// Remove too small test objects
81+
RelabelComponentFilterType::Pointer relabelTestFilter = RelabelComponentFilterType::New();
82+
relabelTestFilter->SetInput(testCCFilter->GetOutput());
83+
relabelTestFilter->SetMinimumObjectSize(minSizeInVoxel);
84+
relabelTestFilter->SetNumberOfThreads(nbpArg.getValue());
85+
relabelTestFilter->Update();
86+
87+
// Test segmentation is now labeled per connected objects
88+
testSegmentation = relabelTestFilter->GetOutput();
89+
testSegmentation->DisconnectPipeline();
90+
91+
ImageIteratorType testItr(testSegmentation, testSegmentation->GetLargestPossibleRegion());
92+
93+
unsigned short maxTestLabel = 0;
94+
while (!testItr.IsAtEnd())
95+
{
96+
if (testItr.Get() > maxTestLabel)
97+
maxTestLabel = testItr.Get();
98+
99+
++testItr;
100+
}
101+
102+
++maxTestLabel;
103+
104+
ImageIteratorType refItr(refSegmentation, refSegmentation->GetLargestPossibleRegion());
105+
std::vector <unsigned int> labelsOverlap(maxTestLabel,0);
106+
std::vector <unsigned int> labelsNonOverlapping(maxTestLabel,0);
107+
std::vector <unsigned int> labelsSizes(maxTestLabel,0);
108+
109+
ImageType::Pointer subImage = ImageType::New();
110+
subImage->Initialize();
111+
subImage->SetRegions(testSegmentation->GetLargestPossibleRegion());
112+
subImage->SetSpacing (testSegmentation->GetSpacing());
113+
subImage->SetOrigin (testSegmentation->GetOrigin());
114+
subImage->SetDirection (testSegmentation->GetDirection());
115+
subImage->Allocate();
116+
ImageIteratorType subItr(subImage,testSegmentation->GetLargestPossibleRegion());
117+
118+
testItr.GoToBegin();
119+
while (!refItr.IsAtEnd())
120+
{
121+
if (refItr.Get() != 0)
122+
labelsOverlap[testItr.Get()]++;
123+
else
124+
labelsNonOverlapping[testItr.Get()]++;
125+
126+
labelsSizes[testItr.Get()]++;
127+
128+
++refItr;
129+
++testItr;
130+
}
131+
132+
std::cout << "Processing " << maxTestLabel << " lesions in second timepoint..." << std::endl;
133+
for (unsigned int i = 1;i < maxTestLabel;++i)
134+
{
135+
testItr.GoToBegin();
136+
double ratioNonOverlapOverlap = static_cast <double> (labelsNonOverlapping[i]) / labelsOverlap[i];
137+
// Shrinking or not enough change -> label = maxTestLabel + 1
138+
if ((labelsNonOverlapping[i] <= minSizeInVoxel) || (ratioNonOverlapOverlap <= betaArg.getValue()))
139+
{
140+
while (!testItr.IsAtEnd())
141+
{
142+
if (testItr.Get() == i)
143+
testItr.Set(maxTestLabel + 1);
144+
145+
++testItr;
146+
}
147+
148+
continue;
149+
}
150+
151+
// New lesion -> put it all as new -> label = maxTestLabel + 2
152+
double ratioOverlapSizes = static_cast <double> (labelsOverlap[i]) / labelsSizes[i];
153+
if (ratioOverlapSizes <= alphaArg.getValue())
154+
{
155+
while (!testItr.IsAtEnd())
156+
{
157+
if (testItr.Get() == i)
158+
testItr.Set(maxTestLabel + 2);
159+
160+
++testItr;
161+
}
162+
163+
continue;
164+
}
165+
166+
// Test for growing lesion too large
167+
if (ratioNonOverlapOverlap > gammaArg.getValue())
168+
{
169+
// New lesion -> label = maxTestLabel + 2
170+
while (!testItr.IsAtEnd())
171+
{
172+
if (testItr.Get() == i)
173+
testItr.Set(maxTestLabel + 2);
174+
175+
++testItr;
176+
}
177+
178+
continue;
179+
}
180+
181+
// We are looking at a growing lesion -> label = maxTestLabel + 3
182+
subImage->FillBuffer(0);
183+
refItr.GoToBegin();
184+
subItr.GoToBegin();
185+
while (!testItr.IsAtEnd())
186+
{
187+
if ((testItr.Get() == i)&&(refItr.Get() == 0))
188+
subItr.Set(1);
189+
190+
++testItr;
191+
++refItr;
192+
++subItr;
193+
}
194+
195+
CCFilterType::Pointer tmpCCFilter = CCFilterType::New();
196+
tmpCCFilter->SetInput(subImage);
197+
tmpCCFilter->SetFullyConnected(fullConnectArg.isSet());
198+
tmpCCFilter->SetNumberOfThreads(nbpArg.getValue());
199+
tmpCCFilter->Update();
200+
201+
// Remove too small test objects
202+
RelabelComponentFilterType::Pointer relabelTmpCCFilter = RelabelComponentFilterType::New();
203+
relabelTmpCCFilter->SetInput(tmpCCFilter->GetOutput());
204+
relabelTmpCCFilter->SetMinimumObjectSize(0);
205+
relabelTmpCCFilter->SetNumberOfThreads(nbpArg.getValue());
206+
relabelTmpCCFilter->Update();
207+
208+
ImageIteratorType subCCItr(relabelTmpCCFilter->GetOutput(),testSegmentation->GetLargestPossibleRegion());
209+
std::vector <unsigned int> numVoxelsCCSub(1);
210+
while (!subCCItr.IsAtEnd())
211+
{
212+
unsigned int currentSize = numVoxelsCCSub.size();
213+
unsigned int value = subCCItr.Get();
214+
215+
if (value >= currentSize)
216+
{
217+
numVoxelsCCSub.resize(value + 1);
218+
for (unsigned int j = currentSize;j <= value;++j)
219+
numVoxelsCCSub[j] = 0;
220+
}
221+
222+
numVoxelsCCSub[value]++;
223+
++subCCItr;
224+
}
225+
226+
unsigned int numSubLabels = numVoxelsCCSub.size() - 1;
227+
double ratioOverlap = static_cast <double> (numVoxelsCCSub[numSubLabels]) / labelsOverlap[i];
228+
bool okGrowing = (ratioOverlap > betaArg.getValue());
229+
230+
refItr.GoToBegin();
231+
testItr.GoToBegin();
232+
if (okGrowing)
233+
{
234+
while (!refItr.IsAtEnd())
235+
{
236+
if (testItr.Get() == i)
237+
{
238+
if (refItr.Get() == 0)
239+
testItr.Set(maxTestLabel + 3);
240+
else
241+
testItr.Set(maxTestLabel + 1);
242+
}
243+
244+
++refItr;
245+
++testItr;
246+
}
247+
}
248+
else
249+
{
250+
// Non growing lesion, setting to
251+
while (!testItr.IsAtEnd())
252+
{
253+
if (testItr.Get() == i)
254+
testItr.Set(maxTestLabel + 1);
255+
256+
++testItr;
257+
}
258+
}
259+
}
260+
261+
testItr.GoToBegin();
262+
while (!testItr.IsAtEnd())
263+
{
264+
unsigned int value = testItr.Get();
265+
if (value > 0)
266+
testItr.Set(value - maxTestLabel);
267+
268+
++testItr;
269+
}
270+
271+
std::cout << "Writing output to " << outArg.getValue() << std::endl;
272+
273+
anima::writeImage <ImageType> (outArg.getValue(),testSegmentation);
274+
return EXIT_SUCCESS;
275+
}
276+

superbuild/BinariesPackaging.cmake.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ set(BINARY_TOOLS_LIST
4141
animaInfluenceZones
4242
animaIsosurface
4343
animaKMeansClustering
44+
animaLesionEvolutionDetection
4445
animaLinearTransformArithmetic
4546
animaLinearTransformToSVF
4647
animaLocalPatchCovarianceDistance

0 commit comments

Comments
 (0)