1 /*=========================================================================
2 Program: vv http://www.creatis.insa-lyon.fr/rio/vv
5 - University of LYON http://www.universite-lyon.fr/
6 - Léon Bérard cancer center http://oncora1.lyon.fnclcc.fr
7 - CREATIS CNRS laboratory http://www.creatis.insa-lyon.fr
9 This software is distributed WITHOUT ANY WARRANTY; without even
10 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11 PURPOSE. See the copyright notices for more information.
13 It is distributed under dual licence
15 - BSD See included LICENSE.txt file
16 - CeCILL-B http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
17 ======================================================================-====*/
19 #ifndef CLITKEXTRACTLUNGSFILTER_TXX
20 #define CLITKEXTRACTLUNGSFILTER_TXX
23 #include "clitkImageCommon.h"
24 #include "clitkSetBackgroundImageFilter.h"
25 #include "clitkSegmentationUtils.h"
26 #include "clitkAutoCropFilter.h"
29 #include "itkBinaryThresholdImageFilter.h"
30 #include "itkConnectedComponentImageFilter.h"
31 #include "itkRelabelComponentImageFilter.h"
32 #include "itkOtsuThresholdImageFilter.h"
33 #include "itkBinaryThinningImageFilter3D.h"
34 #include "itkImageIteratorWithIndex.h"
35 #include "itkBinaryMorphologicalOpeningImageFilter.h"
36 #include "itkBinaryMorphologicalClosingImageFilter.h"
38 //--------------------------------------------------------------------
39 template <class ImageType, class MaskImageType>
40 clitk::ExtractLungFilter<ImageType, MaskImageType>::
43 itk::ImageToImageFilter<ImageType, MaskImageType>()
46 m_MaxSeedNumber = 500;
48 // Default global options
49 this->SetNumberOfRequiredInputs(2);
50 SetPatientMaskBackgroundValue(0);
51 SetBackgroundValue(0); // Must be zero
52 SetForegroundValue(1);
53 SetMinimalComponentSize(100);
55 // Step 1 default values
56 SetUpperThreshold(-300);
57 SetLowerThreshold(-1000);
58 UseLowerThresholdOff();
59 LabelParamType * p1 = new LabelParamType;
62 p1->AddLabelToRemove(2);
63 SetLabelizeParameters1(p1);
65 // Step 2 default values
66 SetUpperThresholdForTrachea(-900);
67 SetMultiplierForTrachea(5);
68 SetThresholdStepSizeForTrachea(64);
69 SetNumberOfSlicesToSkipBeforeSearchingSeed(0);
71 // Step 3 default values
72 SetNumberOfHistogramBins(500);
73 LabelParamType * p2 = new LabelParamType;
76 // p->AddLabelToRemove(2); // No label to remove by default
77 SetLabelizeParameters2(p2);
79 // Step 4 default values
80 SetRadiusForTrachea(1);
81 LabelParamType * p3 = new LabelParamType;
85 SetLabelizeParameters3(p3);
89 SetFinalOpenCloseRadius(1);
91 //--------------------------------------------------------------------
94 //--------------------------------------------------------------------
95 template <class ImageType, class MaskImageType>
97 clitk::ExtractLungFilter<ImageType, MaskImageType>::
98 SetInput(const ImageType * image)
100 this->SetNthInput(0, const_cast<ImageType *>(image));
102 //--------------------------------------------------------------------
105 //--------------------------------------------------------------------
106 template <class ImageType, class MaskImageType>
108 clitk::ExtractLungFilter<ImageType, MaskImageType>::
109 SetInputPatientMask(MaskImageType * image, MaskImagePixelType bg )
111 this->SetNthInput(1, const_cast<MaskImageType *>(image));
112 SetPatientMaskBackgroundValue(bg);
114 //--------------------------------------------------------------------
117 //--------------------------------------------------------------------
118 template <class ImageType, class MaskImageType>
120 clitk::ExtractLungFilter<ImageType, MaskImageType>::
121 AddSeed(InternalIndexType s)
123 m_Seeds.push_back(s);
125 //--------------------------------------------------------------------
128 //--------------------------------------------------------------------
129 template <class ImageType, class MaskImageType>
130 template<class ArgsInfoType>
132 clitk::ExtractLungFilter<ImageType, MaskImageType>::
133 SetArgsInfo(ArgsInfoType mArgsInfo)
135 SetVerboseOption_GGO(mArgsInfo);
136 SetVerboseStep_GGO(mArgsInfo);
137 SetWriteStep_GGO(mArgsInfo);
138 SetVerboseWarningOff_GGO(mArgsInfo);
140 SetUpperThreshold_GGO(mArgsInfo);
141 SetLowerThreshold_GGO(mArgsInfo);
142 SetNumberOfSlicesToSkipBeforeSearchingSeed_GGO(mArgsInfo);
143 SetLabelizeParameters1_GGO(mArgsInfo);
144 if (!mArgsInfo.remove1_given) {
145 GetLabelizeParameters1()->AddLabelToRemove(2);
146 if (GetVerboseOption()) VerboseOption("remove1", 2);
149 SetUpperThresholdForTrachea_GGO(mArgsInfo);
150 SetMultiplierForTrachea_GGO(mArgsInfo);
151 SetThresholdStepSizeForTrachea_GGO(mArgsInfo);
152 AddSeed_GGO(mArgsInfo);
154 SetMinimalComponentSize_GGO(mArgsInfo);
156 SetNumberOfHistogramBins_GGO(mArgsInfo);
157 SetLabelizeParameters2_GGO(mArgsInfo);
159 SetRadiusForTrachea_GGO(mArgsInfo);
160 SetLabelizeParameters3_GGO(mArgsInfo);
162 SetFinalOpenCloseRadius_GGO(mArgsInfo);
163 SetFinalOpenClose_GGO(mArgsInfo);
165 //--------------------------------------------------------------------
168 //--------------------------------------------------------------------
169 template <class ImageType, class MaskImageType>
171 clitk::ExtractLungFilter<ImageType, MaskImageType>::
172 GenerateOutputInformation()
174 Superclass::GenerateOutputInformation();
176 // Get input pointers
177 patient = dynamic_cast<const MaskImageType*>(itk::ProcessObject::GetInput(1));
178 input = dynamic_cast<const ImageType*>(itk::ProcessObject::GetInput(0));
181 if (!HaveSameSizeAndSpacing<ImageType, MaskImageType>(input, patient)) {
182 clitkExceptionMacro("the 'input' and 'patient' masks must have the same size & spacing.");
185 //--------------------------------------------------------------------
186 //--------------------------------------------------------------------
187 StartNewStep("Set background to initial image");
188 working_input = SetBackground<ImageType, MaskImageType>
189 (input, patient, GetPatientMaskBackgroundValue(), -1000);
190 StopCurrentStep<ImageType>(working_input);
192 //--------------------------------------------------------------------
193 //--------------------------------------------------------------------
194 StartNewStep("Remove Air");
196 if (m_UseLowerThreshold) {
197 if (m_LowerThreshold > m_UpperThreshold) {
198 clitkExceptionMacro("lower threshold cannot be greater than upper threshold.");
201 // Threshold to get air
202 typedef itk::BinaryThresholdImageFilter<ImageType, InternalImageType> InputBinarizeFilterType;
203 typename InputBinarizeFilterType::Pointer binarizeFilter=InputBinarizeFilterType::New();
204 binarizeFilter->SetInput(working_input);
205 if (m_UseLowerThreshold) binarizeFilter->SetLowerThreshold(m_LowerThreshold);
206 binarizeFilter->SetUpperThreshold(m_UpperThreshold);
207 binarizeFilter ->SetInsideValue(this->GetForegroundValue());
208 binarizeFilter ->SetOutsideValue(this->GetBackgroundValue());
209 binarizeFilter->Update();
210 working_image = binarizeFilter->GetOutput();
212 // Labelize and keep right labels
213 working_image = Labelize<InternalImageType>(working_image, GetBackgroundValue(), true, GetMinimalComponentSize());
215 working_image = RemoveLabels<InternalImageType>
216 (working_image, GetBackgroundValue(), GetLabelizeParameters1()->GetLabelsToRemove());
218 typename InternalImageType::Pointer air = KeepLabels<InternalImageType>
220 GetBackgroundValue(),
221 GetForegroundValue(),
222 GetLabelizeParameters1()->GetFirstKeep(),
223 GetLabelizeParameters1()->GetLastKeep(),
224 GetLabelizeParameters1()->GetUseLastKeep());
227 working_input = SetBackground<ImageType, InternalImageType>
228 (working_input, air, this->GetForegroundValue(), this->GetBackgroundValue());
231 StopCurrentStep<ImageType>(working_input);
233 //--------------------------------------------------------------------
234 //--------------------------------------------------------------------
235 StartNewStep("Search for the trachea");
238 //--------------------------------------------------------------------
239 //--------------------------------------------------------------------
240 StartNewStep("Extract the lung with Otsu filter");
241 // Automated Otsu thresholding and relabeling
242 typedef itk::OtsuThresholdImageFilter<ImageType,InternalImageType> OtsuThresholdImageFilterType;
243 typename OtsuThresholdImageFilterType::Pointer otsuFilter=OtsuThresholdImageFilterType::New();
244 otsuFilter->SetInput(working_input);
245 otsuFilter->SetNumberOfHistogramBins(GetNumberOfHistogramBins());
246 otsuFilter->SetInsideValue(this->GetForegroundValue());
247 otsuFilter->SetOutsideValue(this->GetBackgroundValue());
248 otsuFilter->Update();
249 working_image = otsuFilter->GetOutput();
252 StopCurrentStep<InternalImageType>(working_image);
254 //--------------------------------------------------------------------
255 //--------------------------------------------------------------------
256 StartNewStep("Select labels");
258 working_image = LabelizeAndSelectLabels<InternalImageType>
260 GetBackgroundValue(),
261 GetForegroundValue(),
263 GetMinimalComponentSize(),
264 GetLabelizeParameters2());
267 StopCurrentStep<InternalImageType>(working_image);
269 //--------------------------------------------------------------------
270 //--------------------------------------------------------------------
271 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
272 StartNewStep("Remove the trachea");
274 working_image = SetBackground<InternalImageType, InternalImageType>
275 (working_image, trachea_tmp, 1, -1);
277 // Dilate the trachea
278 static const unsigned int Dim = ImageType::ImageDimension;
279 typedef itk::BinaryBallStructuringElement<InternalPixelType, Dim> KernelType;
280 KernelType structuringElement;
281 structuringElement.SetRadius(GetRadiusForTrachea());
282 structuringElement.CreateStructuringElement();
283 typedef clitk::ConditionalBinaryDilateImageFilter<InternalImageType, InternalImageType, KernelType> ConditionalBinaryDilateImageFilterType;
284 typename ConditionalBinaryDilateImageFilterType::Pointer dilateFilter = ConditionalBinaryDilateImageFilterType::New();
285 dilateFilter->SetBoundaryToForeground(false);
286 dilateFilter->SetKernel(structuringElement);
287 dilateFilter->SetBackgroundValue (1);
288 dilateFilter->SetForegroundValue (-1);
289 dilateFilter->SetInput (working_image);
290 dilateFilter->Update();
291 working_image = dilateFilter->GetOutput();
293 // Set trachea with dilatation
294 trachea_tmp = SetBackground<InternalImageType, InternalImageType>
295 (trachea_tmp, working_image, -1, this->GetForegroundValue());
297 // Remove the trachea
298 working_image = SetBackground<InternalImageType, InternalImageType>
299 (working_image, working_image, -1, this->GetBackgroundValue());
302 working_image = LabelizeAndSelectLabels<InternalImageType>
304 GetBackgroundValue(),
305 GetForegroundValue(),
307 GetMinimalComponentSize(),
308 GetLabelizeParameters3());
311 StopCurrentStep<InternalImageType>(working_image);
314 //--------------------------------------------------------------------
315 //--------------------------------------------------------------------
316 typedef clitk::AutoCropFilter<InternalImageType> CropFilterType;
317 typename CropFilterType::Pointer cropFilter = CropFilterType::New();
318 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
319 StartNewStep("Croping trachea");
320 cropFilter->SetInput(trachea_tmp);
321 cropFilter->Update(); // Needed
322 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
323 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
324 caster->SetInput(cropFilter->GetOutput());
326 trachea = caster->GetOutput();
327 StopCurrentStep<MaskImageType>(trachea);
330 //--------------------------------------------------------------------
331 //--------------------------------------------------------------------
332 StartNewStep("Croping lung");
333 typename CropFilterType::Pointer cropFilter2 = CropFilterType::New(); // Needed to reset pipeline
334 cropFilter2->SetInput(working_image);
335 cropFilter2->Update();
336 working_image = cropFilter2->GetOutput();
337 StopCurrentStep<InternalImageType>(working_image);
339 //--------------------------------------------------------------------
340 //--------------------------------------------------------------------
342 if (GetFinalOpenClose()) {
343 StartNewStep("Open/Close");
345 // Structuring element
346 typedef itk::BinaryBallStructuringElement<InternalPixelType, ImageDimension> KernelType;
347 KernelType structuringElement;
348 structuringElement.SetRadius(GetFinalOpenCloseRadius());
349 structuringElement.CreateStructuringElement();
352 typedef itk::BinaryMorphologicalOpeningImageFilter<InternalImageType, InternalImageType, KernelType> OpenFilterType;
353 typename OpenFilterType::Pointer openFilter = OpenFilterType::New();
354 openFilter->SetInput(working_image);
355 openFilter->SetBackgroundValue(GetBackgroundValue());
356 openFilter->SetForegroundValue(GetForegroundValue());
357 openFilter->SetKernel(structuringElement);
360 typedef itk::BinaryMorphologicalClosingImageFilter<InternalImageType, InternalImageType, KernelType> CloseFilterType;
361 typename CloseFilterType::Pointer closeFilter = CloseFilterType::New();
362 closeFilter->SetInput(openFilter->GetOutput());
363 closeFilter->SetSafeBorder(true);
364 closeFilter->SetForegroundValue(GetForegroundValue());
365 closeFilter->SetKernel(structuringElement);
366 closeFilter->Update();
367 working_image = closeFilter->GetOutput();
370 //--------------------------------------------------------------------
371 //--------------------------------------------------------------------
372 StartNewStep("Separate Left/Right lungs");
374 working_image = Labelize<InternalImageType>(working_image,
375 GetBackgroundValue(),
377 GetMinimalComponentSize());
380 typedef itk::StatisticsImageFilter<InternalImageType> StatisticsImageFilterType;
381 typename StatisticsImageFilterType::Pointer statisticsImageFilter=StatisticsImageFilterType::New();
382 statisticsImageFilter->SetInput(working_image);
383 statisticsImageFilter->Update();
384 unsigned int initialNumberOfLabels = statisticsImageFilter->GetMaximum();
385 working_image = statisticsImageFilter->GetOutput();
387 // Decompose the first label
388 static const unsigned int Dim = ImageType::ImageDimension;
389 if (initialNumberOfLabels<2) {
390 // Structuring element radius
391 typename ImageType::SizeType radius;
392 for (unsigned int i=0;i<Dim;i++) radius[i]=1;
393 typedef clitk::DecomposeAndReconstructImageFilter<InternalImageType,InternalImageType> DecomposeAndReconstructFilterType;
394 typename DecomposeAndReconstructFilterType::Pointer decomposeAndReconstructFilter=DecomposeAndReconstructFilterType::New();
395 decomposeAndReconstructFilter->SetInput(working_image);
396 decomposeAndReconstructFilter->SetVerbose(false);
397 decomposeAndReconstructFilter->SetRadius(radius);
398 decomposeAndReconstructFilter->SetMaximumNumberOfLabels(2);
399 decomposeAndReconstructFilter->SetMinimumObjectSize(this->GetMinimalComponentSize());
400 decomposeAndReconstructFilter->SetMinimumNumberOfIterations(1);
401 decomposeAndReconstructFilter->SetBackgroundValue(this->GetBackgroundValue());
402 decomposeAndReconstructFilter->SetForegroundValue(this->GetForegroundValue());
403 decomposeAndReconstructFilter->SetFullyConnected(true);
404 decomposeAndReconstructFilter->SetNumberOfNewLabels(1);
405 decomposeAndReconstructFilter->Update();
406 working_image = decomposeAndReconstructFilter->GetOutput();
409 // Retain labels ('1' is largset lung, so right. '2' is left)
410 typedef itk::ThresholdImageFilter<InternalImageType> ThresholdImageFilterType;
411 typename ThresholdImageFilterType::Pointer thresholdFilter = ThresholdImageFilterType::New();
412 thresholdFilter->SetInput(working_image);
413 thresholdFilter->ThresholdAbove(2);
414 thresholdFilter->SetOutsideValue(this->GetBackgroundValue());
415 thresholdFilter->Update();
416 working_image = thresholdFilter->GetOutput();
417 StopCurrentStep<InternalImageType> (working_image);
420 StartNewStep("Cast the lung mask");
421 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
422 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
423 caster->SetInput(working_image);
425 output = caster->GetOutput();
427 // Update output info
428 this->GetOutput(0)->SetRegions(output->GetLargestPossibleRegion());
432 //--------------------------------------------------------------------
435 //--------------------------------------------------------------------
436 template <class ImageType, class MaskImageType>
438 clitk::ExtractLungFilter<ImageType, MaskImageType>::
442 this->GraftOutput(output); // not SetNthOutput
444 //--------------------------------------------------------------------
447 //--------------------------------------------------------------------
448 template <class ImageType, class MaskImageType>
450 clitk::ExtractLungFilter<ImageType, MaskImageType>::
451 SearchForTracheaSeed(int skip)
453 if (m_Seeds.size() == 0) { // try to find seed (if not zero, it is given by user)
454 // Restart the search until a seed is found, skipping more and more slices
457 // Search seed (parameters = UpperThresholdForTrachea)
458 static const unsigned int Dim = ImageType::ImageDimension;
459 typename InternalImageType::RegionType sliceRegion = working_input->GetLargestPossibleRegion();
460 typename InternalImageType::SizeType sliceRegionSize = sliceRegion.GetSize();
461 typename InternalImageType::IndexType sliceRegionIndex = sliceRegion.GetIndex();
462 sliceRegionIndex[Dim-1]=sliceRegionSize[Dim-1]-skip-5;
463 sliceRegionSize[Dim-1]=5;
464 sliceRegion.SetSize(sliceRegionSize);
465 sliceRegion.SetIndex(sliceRegionIndex);
466 typedef itk::ImageRegionConstIterator<ImageType> IteratorType;
467 IteratorType it(working_input, sliceRegion);
469 while (!it.IsAtEnd()) {
470 if(it.Get() < GetUpperThresholdForTrachea() ) {
471 AddSeed(it.GetIndex());
472 // DD(it.GetIndex());
477 // if we do not found : restart
478 stop = (m_Seeds.size() != 0);
480 if (GetVerboseStep()) {
481 std::cout << "\t No seed found this time. I skip some slices and restart." << std::endl;
483 if (skip > 0.5 * working_input->GetLargestPossibleRegion().GetSize()[2]) {
484 // we want to skip more than a half of the image, it is probably a bug
485 std::cerr << "Number of slices to skip to find trachea to high = " << skip << std::endl;
492 return (m_Seeds.size() != 0);
494 //--------------------------------------------------------------------
497 //--------------------------------------------------------------------
498 template <class ImageType, class MaskImageType>
500 clitk::ExtractLungFilter<ImageType, MaskImageType>::
501 TracheaRegionGrowing()
503 // Explosion controlled region growing
504 typedef clitk::ExplosionControlledThresholdConnectedImageFilter<ImageType, InternalImageType> ImageFilterType;
505 typename ImageFilterType::Pointer f= ImageFilterType::New();
506 f->SetInput(working_input);
507 f->SetVerbose(false);
509 f->SetUpper(GetUpperThresholdForTrachea());
510 f->SetMinimumLowerThreshold(-2000);
511 f->SetMaximumUpperThreshold(0);
512 f->SetAdaptLowerBorder(false);
513 f->SetAdaptUpperBorder(true);
514 f->SetMinimumSize(5000);
515 f->SetReplaceValue(1);
516 f->SetMultiplier(GetMultiplierForTrachea());
517 f->SetThresholdStepSize(GetThresholdStepSizeForTrachea());
518 f->SetMinimumThresholdStepSize(1);
519 for(unsigned int i=0; i<m_Seeds.size();i++) {
520 f->AddSeed(m_Seeds[i]);
525 // take first (main) connected component
526 trachea_tmp = Labelize<InternalImageType>(f->GetOutput(),
527 GetBackgroundValue(),
529 GetMinimalComponentSize());
530 trachea_tmp = KeepLabels<InternalImageType>(trachea_tmp,
531 GetBackgroundValue(),
532 GetForegroundValue(),
535 //--------------------------------------------------------------------
538 //--------------------------------------------------------------------
539 template <class ImageType, class MaskImageType>
541 clitk::ExtractLungFilter<ImageType, MaskImageType>::
542 ComputeTracheaVolume()
544 typedef itk::ImageRegionConstIterator<InternalImageType> IteratorType;
545 IteratorType iter(trachea_tmp, trachea_tmp->GetLargestPossibleRegion());
548 while (!iter.IsAtEnd()) {
549 if (iter.Get() == this->GetForegroundValue()) volume++;
553 double voxelsize = trachea_tmp->GetSpacing()[0]*trachea_tmp->GetSpacing()[1]*trachea_tmp->GetSpacing()[2];
554 return volume*voxelsize;
556 //--------------------------------------------------------------------
559 //--------------------------------------------------------------------
560 template <class ImageType, class MaskImageType>
562 clitk::ExtractLungFilter<ImageType, MaskImageType>::
565 // Search for seed among n slices, skip some slices before starting
566 // if not found -> skip more and restart
568 // when seed found : perform region growing
569 // compute trachea volume
570 // if volume not plausible -> skip more slices and restart
574 int skip = GetNumberOfSlicesToSkipBeforeSearchingSeed();
576 stop = SearchForTracheaSeed(skip);
578 TracheaRegionGrowing();
579 volume = ComputeTracheaVolume()/1000; // assume mm3, so divide by 1000 to get cc
580 if ((volume > 10) && (volume < 55 )) { // it is ok
581 // Typical volume 22.59 cm 3 (± 7.69 cm 3 ) [ Leader 2004 ]
582 if (GetVerboseStep()) {
583 std::cout << "\t Found trachea with volume " << volume << " cc." << std::endl;
588 if (GetVerboseStep()) {
589 std::cout << "\t The volume of the trachea (" << volume
590 << " cc) seems not correct. I skip some slices (" << skip << ") and restart to find seeds."
595 // empty the list of seed
603 StopCurrentStep<InternalImageType>(trachea_tmp);
605 else { // Trachea not found
606 this->SetWarning("* WARNING * No seed found for trachea.");
610 //--------------------------------------------------------------------
612 #endif //#define CLITKBOOLEANOPERATORLABELIMAGEFILTER_TXX