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"
27 #include "clitkCropLikeImageFilter.h"
28 #include "clitkFillMaskFilter.h"
31 #include "itkBinaryThresholdImageFilter.h"
32 #include "itkConnectedComponentImageFilter.h"
33 #include "itkRelabelComponentImageFilter.h"
34 #include "itkOtsuThresholdImageFilter.h"
35 #include "itkBinaryThinningImageFilter3D.h"
36 #include "itkImageIteratorWithIndex.h"
37 #include "itkBinaryMorphologicalOpeningImageFilter.h"
38 #include "itkBinaryMorphologicalClosingImageFilter.h"
40 //--------------------------------------------------------------------
41 template <class ImageType>
42 clitk::ExtractLungFilter<ImageType>::
45 clitk::FilterWithAnatomicalFeatureDatabaseManagement(),
46 itk::ImageToImageFilter<ImageType, MaskImageType>()
49 m_MaxSeedNumber = 500;
51 // Default global options
52 this->SetNumberOfRequiredInputs(1);
53 SetPatientMaskBackgroundValue(0);
54 SetBackgroundValue(0); // Must be zero
55 SetForegroundValue(1);
56 SetMinimalComponentSize(100);
58 // Step 1 default values
59 SetUpperThreshold(-300);
60 SetLowerThreshold(-1000);
61 UseLowerThresholdOff();
62 LabelParamType * p1 = new LabelParamType;
65 p1->AddLabelToRemove(2);
66 SetLabelizeParameters1(p1);
68 // Step 2 default values
69 SetUpperThresholdForTrachea(-900);
70 SetMultiplierForTrachea(5);
71 SetThresholdStepSizeForTrachea(64);
72 SetNumberOfSlicesToSkipBeforeSearchingSeed(0);
74 // Step 3 default values
75 SetNumberOfHistogramBins(500);
76 LabelParamType * p2 = new LabelParamType;
79 // p->AddLabelToRemove(2); // No label to remove by default
80 SetLabelizeParameters2(p2);
82 // Step 4 default values
83 SetRadiusForTrachea(1);
84 LabelParamType * p3 = new LabelParamType;
88 SetLabelizeParameters3(p3);
92 SetOpenCloseRadius(1);
97 //--------------------------------------------------------------------
100 //--------------------------------------------------------------------
101 template <class ImageType>
103 clitk::ExtractLungFilter<ImageType>::
104 SetInput(const ImageType * image)
106 this->SetNthInput(0, const_cast<ImageType *>(image));
108 //--------------------------------------------------------------------
111 //--------------------------------------------------------------------
112 template <class ImageType>
114 clitk::ExtractLungFilter<ImageType>::
115 AddSeed(InternalIndexType s)
117 m_Seeds.push_back(s);
119 //--------------------------------------------------------------------
122 //--------------------------------------------------------------------
123 template <class ImageType>
124 template<class ArgsInfoType>
126 clitk::ExtractLungFilter<ImageType>::
127 SetArgsInfo(ArgsInfoType mArgsInfo)
129 SetVerboseOption_GGO(mArgsInfo);
130 SetVerboseStep_GGO(mArgsInfo);
131 SetWriteStep_GGO(mArgsInfo);
132 SetVerboseWarningOff_GGO(mArgsInfo);
134 SetAFDBFilename_GGO(mArgsInfo);
135 SetOutputLungFilename_GGO(mArgsInfo);
136 SetOutputTracheaFilename_GGO(mArgsInfo);
138 SetUpperThreshold_GGO(mArgsInfo);
139 SetLowerThreshold_GGO(mArgsInfo);
140 SetNumberOfSlicesToSkipBeforeSearchingSeed_GGO(mArgsInfo);
141 SetLabelizeParameters1_GGO(mArgsInfo);
142 if (!mArgsInfo.remove1_given) {
143 GetLabelizeParameters1()->AddLabelToRemove(2);
144 if (GetVerboseOption()) VerboseOption("remove1", 2);
147 SetUpperThresholdForTrachea_GGO(mArgsInfo);
148 SetMultiplierForTrachea_GGO(mArgsInfo);
149 SetThresholdStepSizeForTrachea_GGO(mArgsInfo);
150 AddSeed_GGO(mArgsInfo);
152 SetMinimalComponentSize_GGO(mArgsInfo);
154 SetNumberOfHistogramBins_GGO(mArgsInfo);
155 SetLabelizeParameters2_GGO(mArgsInfo);
157 SetRadiusForTrachea_GGO(mArgsInfo);
158 SetLabelizeParameters3_GGO(mArgsInfo);
160 SetOpenCloseRadius_GGO(mArgsInfo);
161 SetOpenClose_GGO(mArgsInfo);
163 SetFillHoles_GGO(mArgsInfo);
165 //--------------------------------------------------------------------
168 //--------------------------------------------------------------------
169 template <class ImageType>
171 clitk::ExtractLungFilter<ImageType>::
172 GenerateOutputInformation()
174 Superclass::GenerateOutputInformation();
179 // Get input pointers
180 input = dynamic_cast<const ImageType*>(itk::ProcessObject::GetInput(0));
181 patient = GetAFDB()->template GetImage <MaskImageType>("patient");
183 //--------------------------------------------------------------------
184 //--------------------------------------------------------------------
185 // Crop input like patient image (must have the same spacing)
186 StartNewStep("Crop input image to 'patient' extends");
187 typedef clitk::CropLikeImageFilter<ImageType> CropImageFilter;
188 typename CropImageFilter::Pointer cropFilter = CropImageFilter::New();
189 cropFilter->SetInput(input);
190 cropFilter->SetCropLikeImage(patient);
191 cropFilter->Update();
192 working_input = cropFilter->GetOutput();
193 StopCurrentStep<ImageType>(working_input);
195 //--------------------------------------------------------------------
196 //--------------------------------------------------------------------
197 StartNewStep("Set background to initial image");
198 working_input = SetBackground<ImageType, MaskImageType>
199 (working_input, patient, GetPatientMaskBackgroundValue(), -1000);
200 StopCurrentStep<ImageType>(working_input);
202 //--------------------------------------------------------------------
203 //--------------------------------------------------------------------
204 StartNewStep("Remove Air");
206 if (m_UseLowerThreshold) {
207 if (m_LowerThreshold > m_UpperThreshold) {
208 clitkExceptionMacro("lower threshold cannot be greater than upper threshold.");
211 // Threshold to get air
212 typedef itk::BinaryThresholdImageFilter<ImageType, InternalImageType> InputBinarizeFilterType;
213 typename InputBinarizeFilterType::Pointer binarizeFilter=InputBinarizeFilterType::New();
214 binarizeFilter->SetInput(working_input);
215 if (m_UseLowerThreshold) binarizeFilter->SetLowerThreshold(m_LowerThreshold);
216 binarizeFilter->SetUpperThreshold(m_UpperThreshold);
217 binarizeFilter ->SetInsideValue(this->GetForegroundValue());
218 binarizeFilter ->SetOutsideValue(this->GetBackgroundValue());
219 binarizeFilter->Update();
220 working_image = binarizeFilter->GetOutput();
222 // Labelize and keep right labels
223 working_image = Labelize<InternalImageType>(working_image, GetBackgroundValue(), true, GetMinimalComponentSize());
225 working_image = RemoveLabels<InternalImageType>
226 (working_image, GetBackgroundValue(), GetLabelizeParameters1()->GetLabelsToRemove());
228 typename InternalImageType::Pointer air = KeepLabels<InternalImageType>
230 GetBackgroundValue(),
231 GetForegroundValue(),
232 GetLabelizeParameters1()->GetFirstKeep(),
233 GetLabelizeParameters1()->GetLastKeep(),
234 GetLabelizeParameters1()->GetUseLastKeep());
237 working_input = SetBackground<ImageType, InternalImageType>
238 (working_input, air, this->GetForegroundValue(), this->GetBackgroundValue());
241 StopCurrentStep<ImageType>(working_input);
243 //--------------------------------------------------------------------
244 //--------------------------------------------------------------------
245 StartNewStep("Search for the trachea");
248 //--------------------------------------------------------------------
249 //--------------------------------------------------------------------
250 StartNewStep("Extract the lung with Otsu filter");
251 // Automated Otsu thresholding and relabeling
252 typedef itk::OtsuThresholdImageFilter<ImageType,InternalImageType> OtsuThresholdImageFilterType;
253 typename OtsuThresholdImageFilterType::Pointer otsuFilter=OtsuThresholdImageFilterType::New();
254 otsuFilter->SetInput(working_input);
255 otsuFilter->SetNumberOfHistogramBins(GetNumberOfHistogramBins());
256 otsuFilter->SetInsideValue(this->GetForegroundValue());
257 otsuFilter->SetOutsideValue(this->GetBackgroundValue());
258 otsuFilter->Update();
259 working_image = otsuFilter->GetOutput();
262 StopCurrentStep<InternalImageType>(working_image);
264 //--------------------------------------------------------------------
265 //--------------------------------------------------------------------
266 StartNewStep("Select labels");
268 working_image = LabelizeAndSelectLabels<InternalImageType>
270 GetBackgroundValue(),
271 GetForegroundValue(),
273 GetMinimalComponentSize(),
274 GetLabelizeParameters2());
277 StopCurrentStep<InternalImageType>(working_image);
279 //--------------------------------------------------------------------
280 //--------------------------------------------------------------------
281 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
282 StartNewStep("Remove the trachea");
284 working_image = SetBackground<InternalImageType, InternalImageType>
285 (working_image, trachea_tmp, 1, -1);
287 // Dilate the trachea
288 static const unsigned int Dim = ImageType::ImageDimension;
289 typedef itk::BinaryBallStructuringElement<InternalPixelType, Dim> KernelType;
290 KernelType structuringElement;
291 structuringElement.SetRadius(GetRadiusForTrachea());
292 structuringElement.CreateStructuringElement();
293 typedef clitk::ConditionalBinaryDilateImageFilter<InternalImageType, InternalImageType, KernelType> ConditionalBinaryDilateImageFilterType;
294 typename ConditionalBinaryDilateImageFilterType::Pointer dilateFilter = ConditionalBinaryDilateImageFilterType::New();
295 dilateFilter->SetBoundaryToForeground(false);
296 dilateFilter->SetKernel(structuringElement);
297 dilateFilter->SetBackgroundValue (1);
298 dilateFilter->SetForegroundValue (-1);
299 dilateFilter->SetInput (working_image);
300 dilateFilter->Update();
301 working_image = dilateFilter->GetOutput();
303 // Set trachea with dilatation
304 trachea_tmp = SetBackground<InternalImageType, InternalImageType>
305 (trachea_tmp, working_image, -1, this->GetForegroundValue());
307 // Remove the trachea
308 working_image = SetBackground<InternalImageType, InternalImageType>
309 (working_image, working_image, -1, this->GetBackgroundValue());
312 working_image = LabelizeAndSelectLabels<InternalImageType>
314 GetBackgroundValue(),
315 GetForegroundValue(),
317 GetMinimalComponentSize(),
318 GetLabelizeParameters3());
321 StopCurrentStep<InternalImageType>(working_image);
324 //--------------------------------------------------------------------
325 //--------------------------------------------------------------------
326 typedef clitk::AutoCropFilter<InternalImageType> AutoCropFilterType;
327 typename AutoCropFilterType::Pointer autocropFilter = AutoCropFilterType::New();
328 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
329 StartNewStep("Cropping trachea");
330 autocropFilter->SetInput(trachea_tmp);
331 autocropFilter->Update(); // Needed
332 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
333 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
334 caster->SetInput(autocropFilter->GetOutput());
336 trachea = caster->GetOutput();
337 StopCurrentStep<MaskImageType>(trachea);
340 //--------------------------------------------------------------------
341 //--------------------------------------------------------------------
342 StartNewStep("Cropping lung");
343 typename AutoCropFilterType::Pointer autocropFilter2 = AutoCropFilterType::New(); // Needed to reset pipeline
344 autocropFilter2->SetInput(working_image);
345 autocropFilter2->Update();
346 working_image = autocropFilter2->GetOutput();
347 StopCurrentStep<InternalImageType>(working_image);
349 //--------------------------------------------------------------------
350 //--------------------------------------------------------------------
352 if (GetOpenClose()) {
353 StartNewStep("Open/Close");
355 // Structuring element
356 typedef itk::BinaryBallStructuringElement<InternalPixelType, ImageDimension> KernelType;
357 KernelType structuringElement;
358 structuringElement.SetRadius(GetOpenCloseRadius());
359 structuringElement.CreateStructuringElement();
362 typedef itk::BinaryMorphologicalOpeningImageFilter<InternalImageType, InternalImageType, KernelType> OpenFilterType;
363 typename OpenFilterType::Pointer openFilter = OpenFilterType::New();
364 openFilter->SetInput(working_image);
365 openFilter->SetBackgroundValue(GetBackgroundValue());
366 openFilter->SetForegroundValue(GetForegroundValue());
367 openFilter->SetKernel(structuringElement);
370 typedef itk::BinaryMorphologicalClosingImageFilter<InternalImageType, InternalImageType, KernelType> CloseFilterType;
371 typename CloseFilterType::Pointer closeFilter = CloseFilterType::New();
372 closeFilter->SetInput(openFilter->GetOutput());
373 closeFilter->SetSafeBorder(true);
374 closeFilter->SetForegroundValue(GetForegroundValue());
375 closeFilter->SetKernel(structuringElement);
376 closeFilter->Update();
377 working_image = closeFilter->GetOutput();
380 //--------------------------------------------------------------------
381 //--------------------------------------------------------------------
383 if (GetFillHoles()) {
384 StartNewStep("Fill Holes");
386 typename FillMaskFilterType::Pointer fillMaskFilter = FillMaskFilterType::New();
387 fillMaskFilter(working_image);
388 fillMaskFilter->Update();
389 working_image = fillMaskFilter->GetOutput();
390 StopCurrentStep<InternalImageType>(working_image);
394 //--------------------------------------------------------------------
395 //--------------------------------------------------------------------
396 StartNewStep("Separate Left/Right lungs");
398 working_image = Labelize<InternalImageType>(working_image,
399 GetBackgroundValue(),
401 GetMinimalComponentSize());
404 typedef itk::StatisticsImageFilter<InternalImageType> StatisticsImageFilterType;
405 typename StatisticsImageFilterType::Pointer statisticsImageFilter=StatisticsImageFilterType::New();
406 statisticsImageFilter->SetInput(working_image);
407 statisticsImageFilter->Update();
408 unsigned int initialNumberOfLabels = statisticsImageFilter->GetMaximum();
409 working_image = statisticsImageFilter->GetOutput();
411 // Decompose the first label
412 static const unsigned int Dim = ImageType::ImageDimension;
413 if (initialNumberOfLabels<2) {
414 // Structuring element radius
415 typename ImageType::SizeType radius;
416 for (unsigned int i=0;i<Dim;i++) radius[i]=1;
417 typedef clitk::DecomposeAndReconstructImageFilter<InternalImageType,InternalImageType> DecomposeAndReconstructFilterType;
418 typename DecomposeAndReconstructFilterType::Pointer decomposeAndReconstructFilter=DecomposeAndReconstructFilterType::New();
419 decomposeAndReconstructFilter->SetInput(working_image);
420 decomposeAndReconstructFilter->SetVerbose(false);
421 decomposeAndReconstructFilter->SetRadius(radius);
422 decomposeAndReconstructFilter->SetMaximumNumberOfLabels(2);
423 decomposeAndReconstructFilter->SetMinimumObjectSize(this->GetMinimalComponentSize());
424 decomposeAndReconstructFilter->SetMinimumNumberOfIterations(1);
425 decomposeAndReconstructFilter->SetBackgroundValue(this->GetBackgroundValue());
426 decomposeAndReconstructFilter->SetForegroundValue(this->GetForegroundValue());
427 decomposeAndReconstructFilter->SetFullyConnected(true);
428 decomposeAndReconstructFilter->SetNumberOfNewLabels(1);
429 decomposeAndReconstructFilter->Update();
430 working_image = decomposeAndReconstructFilter->GetOutput();
433 // Retain labels ('1' is largset lung, so right. '2' is left)
434 typedef itk::ThresholdImageFilter<InternalImageType> ThresholdImageFilterType;
435 typename ThresholdImageFilterType::Pointer thresholdFilter = ThresholdImageFilterType::New();
436 thresholdFilter->SetInput(working_image);
437 thresholdFilter->ThresholdAbove(2);
438 thresholdFilter->SetOutsideValue(this->GetBackgroundValue());
439 thresholdFilter->Update();
440 working_image = thresholdFilter->GetOutput();
441 StopCurrentStep<InternalImageType> (working_image);
444 StartNewStep("Cast the lung mask");
445 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
446 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
447 caster->SetInput(working_image);
449 output = caster->GetOutput();
451 // Update output info
452 this->GetOutput(0)->SetRegions(output->GetLargestPossibleRegion());
454 //--------------------------------------------------------------------
457 //--------------------------------------------------------------------
458 template <class ImageType>
460 clitk::ExtractLungFilter<ImageType>::
464 this->GraftOutput(output); // not SetNthOutput
465 // Store image filenames into AFDB
466 GetAFDB()->SetImageFilename("lungs", this->GetOutputLungFilename());
467 GetAFDB()->SetImageFilename("trachea", this->GetOutputTracheaFilename());
470 //--------------------------------------------------------------------
473 //--------------------------------------------------------------------
474 template <class ImageType>
476 clitk::ExtractLungFilter<ImageType>::
477 SearchForTracheaSeed(int skip)
479 if (m_Seeds.size() == 0) { // try to find seed (if not zero, it is given by user)
480 // Restart the search until a seed is found, skipping more and more slices
483 // Search seed (parameters = UpperThresholdForTrachea)
484 static const unsigned int Dim = ImageType::ImageDimension;
485 typename InternalImageType::RegionType sliceRegion = working_input->GetLargestPossibleRegion();
486 typename InternalImageType::SizeType sliceRegionSize = sliceRegion.GetSize();
487 typename InternalImageType::IndexType sliceRegionIndex = sliceRegion.GetIndex();
488 sliceRegionIndex[Dim-1]=sliceRegionSize[Dim-1]-skip-5;
489 sliceRegionSize[Dim-1]=5;
490 sliceRegion.SetSize(sliceRegionSize);
491 sliceRegion.SetIndex(sliceRegionIndex);
492 typedef itk::ImageRegionConstIterator<ImageType> IteratorType;
493 IteratorType it(working_input, sliceRegion);
495 while (!it.IsAtEnd()) {
496 if(it.Get() < GetUpperThresholdForTrachea() ) {
497 AddSeed(it.GetIndex());
498 // DD(it.GetIndex());
503 // if we do not found : restart
504 stop = (m_Seeds.size() != 0);
506 if (GetVerboseStep()) {
507 std::cout << "\t No seed found this time. I skip some slices and restart." << std::endl;
509 if (skip > 0.5 * working_input->GetLargestPossibleRegion().GetSize()[2]) {
510 // we want to skip more than a half of the image, it is probably a bug
511 std::cerr << "Number of slices to skip to find trachea to high = " << skip << std::endl;
518 return (m_Seeds.size() != 0);
520 //--------------------------------------------------------------------
523 //--------------------------------------------------------------------
524 template <class ImageType>
526 clitk::ExtractLungFilter<ImageType>::
527 TracheaRegionGrowing()
529 // Explosion controlled region growing
530 typedef clitk::ExplosionControlledThresholdConnectedImageFilter<ImageType, InternalImageType> ImageFilterType;
531 typename ImageFilterType::Pointer f= ImageFilterType::New();
532 f->SetInput(working_input);
533 f->SetVerbose(false);
535 f->SetUpper(GetUpperThresholdForTrachea());
536 f->SetMinimumLowerThreshold(-2000);
537 f->SetMaximumUpperThreshold(0);
538 f->SetAdaptLowerBorder(false);
539 f->SetAdaptUpperBorder(true);
540 f->SetMinimumSize(5000);
541 f->SetReplaceValue(1);
542 f->SetMultiplier(GetMultiplierForTrachea());
543 f->SetThresholdStepSize(GetThresholdStepSizeForTrachea());
544 f->SetMinimumThresholdStepSize(1);
545 for(unsigned int i=0; i<m_Seeds.size();i++) {
546 f->AddSeed(m_Seeds[i]);
551 // take first (main) connected component
552 trachea_tmp = Labelize<InternalImageType>(f->GetOutput(),
553 GetBackgroundValue(),
555 GetMinimalComponentSize());
556 trachea_tmp = KeepLabels<InternalImageType>(trachea_tmp,
557 GetBackgroundValue(),
558 GetForegroundValue(),
561 //--------------------------------------------------------------------
564 //--------------------------------------------------------------------
565 template <class ImageType>
567 clitk::ExtractLungFilter<ImageType>::
568 ComputeTracheaVolume()
570 typedef itk::ImageRegionConstIterator<InternalImageType> IteratorType;
571 IteratorType iter(trachea_tmp, trachea_tmp->GetLargestPossibleRegion());
574 while (!iter.IsAtEnd()) {
575 if (iter.Get() == this->GetForegroundValue()) volume++;
579 double voxelsize = trachea_tmp->GetSpacing()[0]*trachea_tmp->GetSpacing()[1]*trachea_tmp->GetSpacing()[2];
580 return volume*voxelsize;
582 //--------------------------------------------------------------------
585 //--------------------------------------------------------------------
586 template <class ImageType>
588 clitk::ExtractLungFilter<ImageType>::
591 // Search for seed among n slices, skip some slices before starting
592 // if not found -> skip more and restart
594 // when seed found : perform region growing
595 // compute trachea volume
596 // if volume not plausible -> skip more slices and restart
600 int skip = GetNumberOfSlicesToSkipBeforeSearchingSeed();
602 stop = SearchForTracheaSeed(skip);
604 TracheaRegionGrowing();
605 volume = ComputeTracheaVolume()/1000; // assume mm3, so divide by 1000 to get cc
606 if ((volume > 10) && (volume < 55 )) { // it is ok
607 // Typical volume 22.59 cm 3 (± 7.69 cm 3 ) [ Leader 2004 ]
608 if (GetVerboseStep()) {
609 std::cout << "\t Found trachea with volume " << volume << " cc." << std::endl;
614 if (GetVerboseStep()) {
615 std::cout << "\t The volume of the trachea (" << volume
616 << " cc) seems not correct. I skip some slices (" << skip << ") and restart to find seeds."
621 // empty the list of seed
629 StopCurrentStep<InternalImageType>(trachea_tmp);
631 else { // Trachea not found
632 this->SetWarning("* WARNING * No seed found for trachea.");
636 //--------------------------------------------------------------------
638 #endif //#define CLITKBOOLEANOPERATORLABELIMAGEFILTER_TXX