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 DD(working_input->GetLargestPossibleRegion());
194 StopCurrentStep<ImageType>(working_input);
196 //--------------------------------------------------------------------
197 //--------------------------------------------------------------------
198 StartNewStep("Set background to initial image");
199 working_input = SetBackground<ImageType, MaskImageType>
200 (working_input, patient, GetPatientMaskBackgroundValue(), -1000);
201 StopCurrentStep<ImageType>(working_input);
203 //--------------------------------------------------------------------
204 //--------------------------------------------------------------------
205 StartNewStep("Remove Air");
207 if (m_UseLowerThreshold) {
208 if (m_LowerThreshold > m_UpperThreshold) {
209 clitkExceptionMacro("lower threshold cannot be greater than upper threshold.");
212 // Threshold to get air
213 typedef itk::BinaryThresholdImageFilter<ImageType, InternalImageType> InputBinarizeFilterType;
214 typename InputBinarizeFilterType::Pointer binarizeFilter=InputBinarizeFilterType::New();
215 binarizeFilter->SetInput(working_input);
216 if (m_UseLowerThreshold) binarizeFilter->SetLowerThreshold(m_LowerThreshold);
217 binarizeFilter->SetUpperThreshold(m_UpperThreshold);
218 binarizeFilter ->SetInsideValue(this->GetForegroundValue());
219 binarizeFilter ->SetOutsideValue(this->GetBackgroundValue());
220 binarizeFilter->Update();
221 working_image = binarizeFilter->GetOutput();
223 // Labelize and keep right labels
224 working_image = Labelize<InternalImageType>(working_image, GetBackgroundValue(), true, GetMinimalComponentSize());
226 working_image = RemoveLabels<InternalImageType>
227 (working_image, GetBackgroundValue(), GetLabelizeParameters1()->GetLabelsToRemove());
229 typename InternalImageType::Pointer air = KeepLabels<InternalImageType>
231 GetBackgroundValue(),
232 GetForegroundValue(),
233 GetLabelizeParameters1()->GetFirstKeep(),
234 GetLabelizeParameters1()->GetLastKeep(),
235 GetLabelizeParameters1()->GetUseLastKeep());
238 working_input = SetBackground<ImageType, InternalImageType>
239 (working_input, air, this->GetForegroundValue(), this->GetBackgroundValue());
242 StopCurrentStep<ImageType>(working_input);
244 //--------------------------------------------------------------------
245 //--------------------------------------------------------------------
246 StartNewStep("Search for the trachea");
249 //--------------------------------------------------------------------
250 //--------------------------------------------------------------------
251 StartNewStep("Extract the lung with Otsu filter");
252 // Automated Otsu thresholding and relabeling
253 typedef itk::OtsuThresholdImageFilter<ImageType,InternalImageType> OtsuThresholdImageFilterType;
254 typename OtsuThresholdImageFilterType::Pointer otsuFilter=OtsuThresholdImageFilterType::New();
255 otsuFilter->SetInput(working_input);
256 otsuFilter->SetNumberOfHistogramBins(GetNumberOfHistogramBins());
257 otsuFilter->SetInsideValue(this->GetForegroundValue());
258 otsuFilter->SetOutsideValue(this->GetBackgroundValue());
259 otsuFilter->Update();
260 working_image = otsuFilter->GetOutput();
263 StopCurrentStep<InternalImageType>(working_image);
265 //--------------------------------------------------------------------
266 //--------------------------------------------------------------------
267 StartNewStep("Select labels");
269 working_image = LabelizeAndSelectLabels<InternalImageType>
271 GetBackgroundValue(),
272 GetForegroundValue(),
274 GetMinimalComponentSize(),
275 GetLabelizeParameters2());
278 StopCurrentStep<InternalImageType>(working_image);
280 //--------------------------------------------------------------------
281 //--------------------------------------------------------------------
282 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
283 StartNewStep("Remove the trachea");
285 working_image = SetBackground<InternalImageType, InternalImageType>
286 (working_image, trachea_tmp, 1, -1);
288 // Dilate the trachea
289 static const unsigned int Dim = ImageType::ImageDimension;
290 typedef itk::BinaryBallStructuringElement<InternalPixelType, Dim> KernelType;
291 KernelType structuringElement;
292 structuringElement.SetRadius(GetRadiusForTrachea());
293 structuringElement.CreateStructuringElement();
294 typedef clitk::ConditionalBinaryDilateImageFilter<InternalImageType, InternalImageType, KernelType> ConditionalBinaryDilateImageFilterType;
295 typename ConditionalBinaryDilateImageFilterType::Pointer dilateFilter = ConditionalBinaryDilateImageFilterType::New();
296 dilateFilter->SetBoundaryToForeground(false);
297 dilateFilter->SetKernel(structuringElement);
298 dilateFilter->SetBackgroundValue (1);
299 dilateFilter->SetForegroundValue (-1);
300 dilateFilter->SetInput (working_image);
301 dilateFilter->Update();
302 working_image = dilateFilter->GetOutput();
304 // Set trachea with dilatation
305 trachea_tmp = SetBackground<InternalImageType, InternalImageType>
306 (trachea_tmp, working_image, -1, this->GetForegroundValue());
308 // Remove the trachea
309 working_image = SetBackground<InternalImageType, InternalImageType>
310 (working_image, working_image, -1, this->GetBackgroundValue());
313 working_image = LabelizeAndSelectLabels<InternalImageType>
315 GetBackgroundValue(),
316 GetForegroundValue(),
318 GetMinimalComponentSize(),
319 GetLabelizeParameters3());
322 StopCurrentStep<InternalImageType>(working_image);
325 //--------------------------------------------------------------------
326 //--------------------------------------------------------------------
327 typedef clitk::AutoCropFilter<InternalImageType> AutoCropFilterType;
328 typename AutoCropFilterType::Pointer autocropFilter = AutoCropFilterType::New();
329 if (m_Seeds.size() != 0) { // if ==0 ->no trachea found
330 StartNewStep("Cropping trachea");
331 autocropFilter->SetInput(trachea_tmp);
332 autocropFilter->Update(); // Needed
333 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
334 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
335 caster->SetInput(autocropFilter->GetOutput());
337 trachea = caster->GetOutput();
338 StopCurrentStep<MaskImageType>(trachea);
341 //--------------------------------------------------------------------
342 //--------------------------------------------------------------------
343 StartNewStep("Cropping lung");
344 typename AutoCropFilterType::Pointer autocropFilter2 = AutoCropFilterType::New(); // Needed to reset pipeline
345 autocropFilter2->SetInput(working_image);
346 autocropFilter2->Update();
347 working_image = autocropFilter2->GetOutput();
348 DD(working_image->GetLargestPossibleRegion());
349 StopCurrentStep<InternalImageType>(working_image);
351 //--------------------------------------------------------------------
352 //--------------------------------------------------------------------
354 if (GetOpenClose()) {
355 StartNewStep("Open/Close");
357 // Structuring element
358 typedef itk::BinaryBallStructuringElement<InternalPixelType, ImageDimension> KernelType;
359 KernelType structuringElement;
360 structuringElement.SetRadius(GetOpenCloseRadius());
361 structuringElement.CreateStructuringElement();
364 typedef itk::BinaryMorphologicalOpeningImageFilter<InternalImageType, InternalImageType, KernelType> OpenFilterType;
365 typename OpenFilterType::Pointer openFilter = OpenFilterType::New();
366 openFilter->SetInput(working_image);
367 openFilter->SetBackgroundValue(GetBackgroundValue());
368 openFilter->SetForegroundValue(GetForegroundValue());
369 openFilter->SetKernel(structuringElement);
372 typedef itk::BinaryMorphologicalClosingImageFilter<InternalImageType, InternalImageType, KernelType> CloseFilterType;
373 typename CloseFilterType::Pointer closeFilter = CloseFilterType::New();
374 closeFilter->SetInput(openFilter->GetOutput());
375 closeFilter->SetSafeBorder(true);
376 closeFilter->SetForegroundValue(GetForegroundValue());
377 closeFilter->SetKernel(structuringElement);
378 closeFilter->Update();
379 working_image = closeFilter->GetOutput();
382 //--------------------------------------------------------------------
383 //--------------------------------------------------------------------
385 if (GetFillHoles()) {
386 StartNewStep("Fill Holes");
387 typedef clitk::FillMaskFilter<InternalImageType> FillMaskFilterType;
388 typename FillMaskFilterType::Pointer fillMaskFilter = FillMaskFilterType::New();
389 fillMaskFilter->SetInput(working_image);
390 fillMaskFilter->AddDirection(2);
391 //fillMaskFilter->AddDirection(1);
392 fillMaskFilter->Update();
393 working_image = fillMaskFilter->GetOutput();
394 StopCurrentStep<InternalImageType>(working_image);
397 //--------------------------------------------------------------------
398 //--------------------------------------------------------------------
399 StartNewStep("Separate Left/Right lungs");
401 working_image = Labelize<InternalImageType>(working_image,
402 GetBackgroundValue(),
404 GetMinimalComponentSize());
407 typedef itk::StatisticsImageFilter<InternalImageType> StatisticsImageFilterType;
408 typename StatisticsImageFilterType::Pointer statisticsImageFilter=StatisticsImageFilterType::New();
409 statisticsImageFilter->SetInput(working_image);
410 statisticsImageFilter->Update();
411 unsigned int initialNumberOfLabels = statisticsImageFilter->GetMaximum();
412 working_image = statisticsImageFilter->GetOutput();
414 // Decompose the first label
415 static const unsigned int Dim = ImageType::ImageDimension;
416 if (initialNumberOfLabels<2) {
417 DD(initialNumberOfLabels);
418 // Structuring element radius
419 typename ImageType::SizeType radius;
420 for (unsigned int i=0;i<Dim;i++) radius[i]=1;
421 typedef clitk::DecomposeAndReconstructImageFilter<InternalImageType,InternalImageType> DecomposeAndReconstructFilterType;
422 typename DecomposeAndReconstructFilterType::Pointer decomposeAndReconstructFilter=DecomposeAndReconstructFilterType::New();
423 decomposeAndReconstructFilter->SetInput(working_image);
424 decomposeAndReconstructFilter->SetVerbose(true);
425 decomposeAndReconstructFilter->SetRadius(radius);
426 decomposeAndReconstructFilter->SetMaximumNumberOfLabels(2);
427 decomposeAndReconstructFilter->SetMinimumObjectSize(this->GetMinimalComponentSize());
428 decomposeAndReconstructFilter->SetMinimumNumberOfIterations(1);
429 decomposeAndReconstructFilter->SetBackgroundValue(this->GetBackgroundValue());
430 decomposeAndReconstructFilter->SetForegroundValue(this->GetForegroundValue());
431 decomposeAndReconstructFilter->SetFullyConnected(true);
432 decomposeAndReconstructFilter->SetNumberOfNewLabels(1);
433 decomposeAndReconstructFilter->Update();
434 working_image = decomposeAndReconstructFilter->GetOutput();
437 // Retain labels ('1' is largset lung, so right. '2' is left)
438 typedef itk::ThresholdImageFilter<InternalImageType> ThresholdImageFilterType;
439 typename ThresholdImageFilterType::Pointer thresholdFilter = ThresholdImageFilterType::New();
440 thresholdFilter->SetInput(working_image);
441 thresholdFilter->ThresholdAbove(2);
442 thresholdFilter->SetOutsideValue(this->GetBackgroundValue());
443 thresholdFilter->Update();
444 working_image = thresholdFilter->GetOutput();
445 StopCurrentStep<InternalImageType> (working_image);
448 StartNewStep("Cast the lung mask");
449 typedef itk::CastImageFilter<InternalImageType, MaskImageType> CastImageFilterType;
450 typename CastImageFilterType::Pointer caster= CastImageFilterType::New();
451 caster->SetInput(working_image);
453 output = caster->GetOutput();
455 // Update output info
456 this->GetOutput(0)->SetRegions(output->GetLargestPossibleRegion());
458 //--------------------------------------------------------------------
461 //--------------------------------------------------------------------
462 template <class ImageType>
464 clitk::ExtractLungFilter<ImageType>::
468 this->GraftOutput(output); // not SetNthOutput
469 // Store image filenames into AFDB
470 GetAFDB()->SetImageFilename("lungs", this->GetOutputLungFilename());
471 GetAFDB()->SetImageFilename("trachea", this->GetOutputTracheaFilename());
474 //--------------------------------------------------------------------
477 //--------------------------------------------------------------------
478 template <class ImageType>
480 clitk::ExtractLungFilter<ImageType>::
481 SearchForTracheaSeed(int skip)
483 if (m_Seeds.size() == 0) { // try to find seed (if not zero, it is given by user)
484 // Restart the search until a seed is found, skipping more and more slices
487 // Search seed (parameters = UpperThresholdForTrachea)
488 static const unsigned int Dim = ImageType::ImageDimension;
489 typename InternalImageType::RegionType sliceRegion = working_input->GetLargestPossibleRegion();
490 typename InternalImageType::SizeType sliceRegionSize = sliceRegion.GetSize();
491 typename InternalImageType::IndexType sliceRegionIndex = sliceRegion.GetIndex();
492 sliceRegionIndex[Dim-1]=sliceRegionSize[Dim-1]-skip-5;
493 sliceRegionSize[Dim-1]=5;
494 sliceRegion.SetSize(sliceRegionSize);
495 sliceRegion.SetIndex(sliceRegionIndex);
496 typedef itk::ImageRegionConstIterator<ImageType> IteratorType;
497 IteratorType it(working_input, sliceRegion);
499 while (!it.IsAtEnd()) {
500 if(it.Get() < GetUpperThresholdForTrachea() ) {
501 AddSeed(it.GetIndex());
502 // DD(it.GetIndex());
507 // if we do not found : restart
508 stop = (m_Seeds.size() != 0);
510 if (GetVerboseStep()) {
511 std::cout << "\t No seed found this time. I skip some slices and restart." << std::endl;
513 if (skip > 0.5 * working_input->GetLargestPossibleRegion().GetSize()[2]) {
514 // we want to skip more than a half of the image, it is probably a bug
515 std::cerr << "Number of slices to skip to find trachea to high = " << skip << std::endl;
522 return (m_Seeds.size() != 0);
524 //--------------------------------------------------------------------
527 //--------------------------------------------------------------------
528 template <class ImageType>
530 clitk::ExtractLungFilter<ImageType>::
531 TracheaRegionGrowing()
533 // Explosion controlled region growing
534 typedef clitk::ExplosionControlledThresholdConnectedImageFilter<ImageType, InternalImageType> ImageFilterType;
535 typename ImageFilterType::Pointer f= ImageFilterType::New();
536 f->SetInput(working_input);
537 f->SetVerbose(false);
539 f->SetUpper(GetUpperThresholdForTrachea());
540 f->SetMinimumLowerThreshold(-2000);
541 // f->SetMaximumUpperThreshold(0); // MAYBE TO CHANGE ???
542 f->SetMaximumUpperThreshold(-800); // MAYBE TO CHANGE ???
543 f->SetAdaptLowerBorder(false);
544 f->SetAdaptUpperBorder(true);
545 f->SetMinimumSize(5000);
546 f->SetReplaceValue(1);
547 f->SetMultiplier(GetMultiplierForTrachea());
548 f->SetThresholdStepSize(GetThresholdStepSizeForTrachea());
549 f->SetMinimumThresholdStepSize(1);
551 for(unsigned int i=0; i<m_Seeds.size();i++) {
552 f->AddSeed(m_Seeds[i]);
557 writeImage<InternalImageType>(f->GetOutput(), "trg.mhd");
559 // take first (main) connected component
560 trachea_tmp = Labelize<InternalImageType>(f->GetOutput(),
561 GetBackgroundValue(),
563 GetMinimalComponentSize());
564 trachea_tmp = KeepLabels<InternalImageType>(trachea_tmp,
565 GetBackgroundValue(),
566 GetForegroundValue(),
569 //--------------------------------------------------------------------
572 //--------------------------------------------------------------------
573 template <class ImageType>
575 clitk::ExtractLungFilter<ImageType>::
576 ComputeTracheaVolume()
578 typedef itk::ImageRegionConstIterator<InternalImageType> IteratorType;
579 IteratorType iter(trachea_tmp, trachea_tmp->GetLargestPossibleRegion());
582 while (!iter.IsAtEnd()) {
583 if (iter.Get() == this->GetForegroundValue()) volume++;
587 double voxelsize = trachea_tmp->GetSpacing()[0]*trachea_tmp->GetSpacing()[1]*trachea_tmp->GetSpacing()[2];
588 return volume*voxelsize;
590 //--------------------------------------------------------------------
593 //--------------------------------------------------------------------
594 template <class ImageType>
596 clitk::ExtractLungFilter<ImageType>::
599 // Search for seed among n slices, skip some slices before starting
600 // if not found -> skip more and restart
602 // when seed found : perform region growing
603 // compute trachea volume
604 // if volume not plausible -> skip more slices and restart
608 int skip = GetNumberOfSlicesToSkipBeforeSearchingSeed();
610 stop = SearchForTracheaSeed(skip);
612 TracheaRegionGrowing();
613 volume = ComputeTracheaVolume()/1000; // assume mm3, so divide by 1000 to get cc
614 if ((volume > 10) && (volume < 55 )) { // it is ok
615 // Typical volume 22.59 cm 3 (± 7.69 cm 3 ) [ Leader 2004 ]
616 if (GetVerboseStep()) {
617 std::cout << "\t Found trachea with volume " << volume << " cc." << std::endl;
622 if (GetVerboseStep()) {
623 std::cout << "\t The volume of the trachea (" << volume
624 << " cc) seems not correct. I skip some slices (" << skip << ") and restart to find seeds."
629 // empty the list of seed
637 StopCurrentStep<InternalImageType>(trachea_tmp);
639 else { // Trachea not found
640 this->SetWarning("* WARNING * No seed found for trachea.");
644 //--------------------------------------------------------------------
646 #endif //#define CLITKBOOLEANOPERATORLABELIMAGEFILTER_TXX