2 #include <itkBinaryDilateImageFilter.h>
3 #include <itkMirrorPadImageFilter.h>
5 //--------------------------------------------------------------------
6 template <class ImageType>
8 clitk::ExtractLymphStationsFilter<ImageType>::
9 ExtractStation_8_SI_Limits()
12 Station 8: paraeosphageal nodes
14 "Superiorly, Station 8 begins at the level of the carina and,
15 therefore, abuts Station 3P (Fig. 3C). Inferiorly, the lower limit
16 of Station 8 was not specified in the Mountain and Dresler
17 classification (1). We delineate this volume until it reaches the
18 gastroesphogeal junction. "
20 => new classification IASCL 2009:
22 "Lower border: the diaphragm"
24 "Upper border: the upper border of the lower lobe bronchus on the
25 left; the lower border of the bronchus intermedius on the right"
28 StartNewStep("[Station8] Inf/Sup mediastinum limits with carina/diaphragm junction");
30 // Get Carina Z position
31 MaskImagePointer Carina = GetAFDB()->template GetImage<MaskImageType>("Carina");
32 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read Carina");
33 std::vector<MaskImagePointType> centroids;
34 clitk::ComputeCentroids<MaskImageType>(Carina, GetBackgroundValue(), centroids);
36 m_CarinaZ = centroids[1][2];
38 // add one slice to include carina ?
39 m_CarinaZ += m_Mediastinum->GetSpacing()[2];
41 // We dont need Carina structure from now
43 clitk::PrintMemory(GetVerboseMemoryFlag(), "after delete Carina");
45 // Get left lower lobe bronchus (L), take the upper border
46 // Get right bronchus intermedius (RML), take the lower border
49 double m_CarinaZ = GetAFDB()->GetPoint3D("Carina", 2) +
50 m_Mediastinum->GetSpacing()[2]; // add one slice to include carina
51 // double GOjunctionZ = GetAFDB()->GetPoint3D("GOjunction", 2);
54 // Found most inferior part of the lung
55 MaskImagePointer Lungs = GetAFDB()->template GetImage<MaskImageType>("Lungs");
56 // It should be already croped, so I took the origin and add 10mm above
57 m_DiaphragmInferiorLimit = Lungs->GetOrigin()[2]+10;
58 DD(m_DiaphragmInferiorLimit);
59 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read Lung");
60 Lungs->Delete(); // we don't need it, release memory
61 clitk::PrintMemory(GetVerboseMemoryFlag(), "after release Lung");
64 Superior limit = carina
65 Inferior limit = DiaphragmInferiorLimit (old=Gastroesphogeal junction) */
67 clitk::CropImageAlongOneAxis<MaskImageType>(m_Working_Support, 2,
68 m_DiaphragmInferiorLimit, //GOjunctionZ,
70 GetBackgroundValue());
72 // Remove some structures that we know are excluded
73 MaskImagePointer VertebralBody =
74 GetAFDB()->template GetImage <MaskImageType>("VertebralBody");
75 MaskImagePointer Aorta =
76 GetAFDB()->template GetImage <MaskImageType>("Aorta");
78 typedef clitk::BooleanOperatorLabelImageFilter<MaskImageType> BoolFilterType;
79 typename BoolFilterType::Pointer boolFilter = BoolFilterType::New();
80 boolFilter->InPlaceOn();
81 boolFilter->SetInput1(m_Working_Support);
82 boolFilter->SetInput2(VertebralBody);
83 boolFilter->SetOperationType(BoolFilterType::AndNot);
85 boolFilter->SetInput1(boolFilter->GetOutput());
86 boolFilter->SetInput2(Aorta);
87 boolFilter->SetOperationType(BoolFilterType::AndNot);
89 m_Working_Support = boolFilter->GetOutput();
92 StopCurrentStep<MaskImageType>(m_Working_Support);
93 m_ListOfStations["8"] = m_Working_Support;
95 //--------------------------------------------------------------------
98 //--------------------------------------------------------------------
99 template <class ImageType>
101 clitk::ExtractLymphStationsFilter<ImageType>::
102 ExtractStation_8_Post_Limits()
105 Station 8: paraeosphageal nodes
107 Anteriorly, it is in contact with Station 7 and the
108 left main stem bronchus in its superior aspect (Fig. 3C–H) and
109 with the heart more inferiorly. Posteriorly, Station 8 abuts the
110 descending aorta and the anterior aspect of the vertebral body
111 until an imaginary horizontal line running 1 cm posterior to the
112 anterior border of the vertebral body (Fig. 3C).
114 New classification IASCL 2009 :
116 "Nodes lying adjacent to the wall of the esophagus and to the
117 right or left of the midline, excluding subcarinal nodes (S7)
122 StartNewStep("[Station8] Post limits with vertebral body");
123 MaskImagePointer VertebralBody =
124 GetAFDB()->template GetImage <MaskImageType>("VertebralBody");
126 // Consider vertebral body slice by slice
127 typedef clitk::ExtractSliceFilter<MaskImageType> ExtractSliceFilterType;
128 typename ExtractSliceFilterType::Pointer extractSliceFilter = ExtractSliceFilterType::New();
129 typedef typename ExtractSliceFilterType::SliceType SliceType;
130 std::vector<typename SliceType::Pointer> vertebralSlices;
131 extractSliceFilter->SetInput(VertebralBody);
132 extractSliceFilter->SetDirection(2);
133 extractSliceFilter->Update();
134 extractSliceFilter->GetOutputSlices(vertebralSlices);
136 // For each slice, compute the most anterior point
137 std::map<int, typename SliceType::PointType> vertebralAntPositionBySlice;
138 for(uint i=0; i<vertebralSlices.size(); i++) {
139 typename SliceType::PointType p;
140 bool found = clitk::FindExtremaPointInAGivenDirection<SliceType>(vertebralSlices[i],
141 GetBackgroundValue(),
144 vertebralAntPositionBySlice[i] = p;
147 // no Foreground in this slice (it appends generally at the
148 // beginning and the end of the volume). Do nothing in this
153 // Convert 2D points in slice into 3D points
154 std::vector<MaskImagePointType> vertebralAntPositions;
155 clitk::PointsUtils<MaskImageType>::Convert2DTo3DList(vertebralAntPositionBySlice,
157 vertebralAntPositions);
159 // DEBUG : write list of points
160 clitk::WriteListOfLandmarks<MaskImageType>(vertebralAntPositions,
161 "vertebralMostAntPositions.txt");
163 // Cut support posteriorly 1cm the most anterior point of the
164 // VertebralBody. Do nothing below and above the VertebralBody. To
165 // do that compute several region, slice by slice and fill.
166 MaskImageRegionType region;
167 MaskImageSizeType size;
168 MaskImageIndexType start;
169 size[2] = 1; // a single slice
170 start[0] = m_Working_Support->GetLargestPossibleRegion().GetIndex()[0];
171 size[0] = m_Working_Support->GetLargestPossibleRegion().GetSize()[0];
172 for(uint i=0; i<vertebralAntPositions.size(); i++) {
173 typedef typename itk::ImageRegionIterator<MaskImageType> IteratorType;
175 IteratorType(m_Working_Support, m_Working_Support->GetLargestPossibleRegion());
176 MaskImageIndexType index;
177 // Consider some cm posterior to most anterior positions (usually
179 vertebralAntPositions[i][1] += GetDistanceMaxToAnteriorPartOfTheSpine();
180 // Get index of this point
181 m_Working_Support->TransformPhysicalPointToIndex(vertebralAntPositions[i], index);
182 // Compute region (a single slice)
184 start[1] = m_Working_Support->GetLargestPossibleRegion().GetIndex()[1]+index[1];
185 size[1] = m_Working_Support->GetLargestPossibleRegion().GetSize()[1]-start[1];
186 region.SetSize(size);
187 region.SetIndex(start);
189 if (m_Working_Support->GetLargestPossibleRegion().IsInside(start)) {
190 itk::ImageRegionIterator<MaskImageType> it(m_Working_Support, region);
192 while (!it.IsAtEnd()) {
193 it.Set(GetBackgroundValue());
199 StopCurrentStep<MaskImageType>(m_Working_Support);
200 m_ListOfStations["8"] = m_Working_Support;
202 //--------------------------------------------------------------------
205 //--------------------------------------------------------------------
206 template <class ImageType>
208 clitk::ExtractLymphStationsFilter<ImageType>::
209 ExtractStation_8_Ant_Limits()
211 //--------------------------------------------------------------------
212 StartNewStep("[Station8] Ant limits with S7 above Carina");
214 Anteriorly, it is in contact with Station 7 and the
215 left main stem bronchus in its superior aspect (Fig. 3C–H) and
216 with the heart more inferiorly.
218 1) line post wall bronchi (S7), till originRMLB
219 - LUL bronchus : to detect in trachea. But not needed here ??
220 2) heart ! -> not delineated.
221 ==> below S7, crop CT not to far away (ant), then try with threshold
223 1) ==> how to share with S7 ? Prepare both support at the same time !
224 NEED vector of initial support for each station ? No -> map if it exist before, take it !!
228 // Ant limit from carina (start) to end of S7 = originRMLB
230 MaskImagePointer Trachea = GetAFDB()->template GetImage<MaskImageType>("Trachea");
231 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read Trachea");
233 MaskImagePointer m_Working_Trachea =
234 clitk::CropImageAbove<MaskImageType>(Trachea, 2, m_CarinaZ, true, // AutoCrop
235 GetBackgroundValue());
236 clitk::PrintMemory(GetVerboseMemoryFlag(), "after crop");
238 // Seprate into two main bronchi
239 MaskImagePointer LeftBronchus;
240 MaskImagePointer RightBronchus;
242 // Labelize and consider the two first (main) labels
243 m_Working_Trachea = Labelize<MaskImageType>(m_Working_Trachea, 0, true, 1);
244 clitk::PrintMemory(GetVerboseMemoryFlag(), "after labelize");
246 // Carina position must at the first slice that separate the two
247 // main bronchus (not superiorly). We thus first check that the
248 // upper slice is composed of at least two labels
249 typedef itk::ImageSliceIteratorWithIndex<MaskImageType> SliceIteratorType;
250 SliceIteratorType iter(m_Working_Trachea, m_Working_Trachea->GetLargestPossibleRegion());
251 iter.SetFirstDirection(0);
252 iter.SetSecondDirection(1);
253 iter.GoToReverseBegin(); // Start from the end (because image is IS not SI)
255 while (!iter.IsAtReverseEndOfSlice()) {
256 while (!iter.IsAtReverseEndOfLine()) {
257 if (iter.Get() > maxLabel) maxLabel = iter.Get();
263 clitkExceptionMacro("First slice form Carina does not seems to seperate the two main bronchus. Abort");
266 // Compute 3D centroids of both parts to identify the left from the
268 std::vector<ImagePointType> c;
269 clitk::ComputeCentroids<MaskImageType>(m_Working_Trachea, GetBackgroundValue(), c);
270 ImagePointType C1 = c[1];
271 ImagePointType C2 = c[2];
273 ImagePixelType leftLabel;
274 ImagePixelType rightLabel;
275 if (C1[0] < C2[0]) { leftLabel = 1; rightLabel = 2; }
276 else { leftLabel = 2; rightLabel = 1; }
278 // Select LeftLabel (set one label to Backgroundvalue)
280 SetBackground<MaskImageType, MaskImageType>(m_Working_Trachea, m_Working_Trachea,
281 rightLabel, GetBackgroundValue(), false);
283 SetBackground<MaskImageType, MaskImageType>(m_Working_Trachea, m_Working_Trachea,
284 leftLabel, GetBackgroundValue(), false);
286 LeftBronchus = clitk::AutoCrop<MaskImageType>(LeftBronchus, GetBackgroundValue());
287 RightBronchus = clitk::AutoCrop<MaskImageType>(RightBronchus, GetBackgroundValue());
289 // Insert int AFDB if need after
290 GetAFDB()->template SetImage <MaskImageType>("LeftBronchus", "seg/leftBronchus.mhd",
292 GetAFDB()->template SetImage <MaskImageType>("RightBronchus", "seg/rightBronchus.mhd",
293 RightBronchus, true);
295 // Now crop below OriginOfRightMiddleLobeBronchusZ
296 // It is not done before to keep entire bronchi.
298 MaskImagePointer OriginOfRightMiddleLobeBronchus =
299 GetAFDB()->template GetImage<MaskImageType>("OriginOfRightMiddleLobeBronchus");
300 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read OriginOfRightMiddleLobeBronchus");
301 std::vector<MaskImagePointType> centroids;
302 clitk::ComputeCentroids<MaskImageType>(OriginOfRightMiddleLobeBronchus, GetBackgroundValue(), centroids);
303 DD(centroids.size());
304 DD(centroids[0]); // BG
306 m_OriginOfRightMiddleLobeBronchusZ = centroids[1][2];
307 DD(m_OriginOfRightMiddleLobeBronchusZ);
308 // add one slice to include carina ?
309 m_OriginOfRightMiddleLobeBronchusZ += LeftBronchus->GetSpacing()[2];
310 DD(m_OriginOfRightMiddleLobeBronchusZ);
311 DD(OriginOfRightMiddleLobeBronchus->GetReferenceCount());
312 // We dont need Carina structure from now
313 OriginOfRightMiddleLobeBronchus->Delete();
314 clitk::PrintMemory(GetVerboseMemoryFlag(), "after delete OriginOfRightMiddleLobeBronchus");
317 clitk::CropImageBelow<MaskImageType>(LeftBronchus, 2,
318 m_OriginOfRightMiddleLobeBronchusZ,
320 GetBackgroundValue());
322 clitk::CropImageBelow<MaskImageType>(RightBronchus, 2,
323 m_OriginOfRightMiddleLobeBronchusZ,
325 GetBackgroundValue());
327 // Search for points that are the most left/post/ant and most
328 // right/post/ant of the left and right bronchus
329 // 15 = not 15 mm more distance than the middle point.
330 FindExtremaPointsInBronchus(LeftBronchus, 0, 10, m_RightMostInLeftBronchus,
331 m_AntMostInLeftBronchus, m_PostMostInLeftBronchus);
332 FindExtremaPointsInBronchus(RightBronchus, 1, 10, m_LeftMostInRightBronchus,
333 m_AntMostInRightBronchus, m_PostMostInRightBronchus);
335 // DEBUG : write the list of points
337 v.reserve(m_RightMostInLeftBronchus.size()+m_AntMostInLeftBronchus.size()+
338 m_PostMostInLeftBronchus.size());
339 v.insert(v.end(), m_RightMostInLeftBronchus.begin(), m_RightMostInLeftBronchus.end() );
340 v.insert(v.end(), m_AntMostInLeftBronchus.begin(), m_AntMostInLeftBronchus.end() );
341 v.insert(v.end(), m_PostMostInLeftBronchus.begin(), m_PostMostInLeftBronchus.end() );
342 clitk::WriteListOfLandmarks<MaskImageType>(v, "LeftBronchusPoints.txt");
345 v.reserve(m_LeftMostInRightBronchus.size()+m_AntMostInRightBronchus.size()+
346 m_PostMostInRightBronchus.size());
347 v.insert(v.end(), m_LeftMostInRightBronchus.begin(), m_LeftMostInRightBronchus.end() );
348 v.insert(v.end(), m_AntMostInRightBronchus.begin(), m_AntMostInRightBronchus.end() );
349 v.insert(v.end(), m_PostMostInRightBronchus.begin(), m_PostMostInRightBronchus.end() );
350 clitk::WriteListOfLandmarks<MaskImageType>(v, "RightBronchusPoints.txt");
353 // Now uses these points to limit, slice by slice
354 // line is mainly horizontal, so mainDirection=1
355 writeImage<MaskImageType>(m_Working_Support, "before.mhd");
356 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
357 m_PostMostInLeftBronchus,
358 m_PostMostInRightBronchus,
359 GetBackgroundValue(), 1, 10);
360 writeImage<MaskImageType>(m_Working_Support, "after.mhd");
364 // Keep main 3D CCL :
365 m_Working_Support = Labelize<MaskImageType>(m_Working_Support, 0, false, 10);
366 m_Working_Support = KeepLabels<MaskImageType>(m_Working_Support,
367 GetBackgroundValue(),
368 GetForegroundValue(), 1, 1, true);
371 m_Working_Support = clitk::AutoCrop<MaskImageType>(m_Working_Support, GetBackgroundValue());
374 StopCurrentStep<MaskImageType>(m_Working_Support);
375 // m_ListOfStations["8"] = m_Working_Support;
379 //--------------------------------------------------------------------
380 template <class ImageType>
382 clitk::ExtractLymphStationsFilter<ImageType>::
383 ExtractStation_8_LR_Limits()
386 //--------------------------------------------------------------------
387 StartNewStep("[Station8] Left and Right limits arround esophagus (below Carina)");
389 Consider Esophagus, dilate it and remove ant part. It remains part
390 on L & R, than can be partly removed by cutting what remains at
391 right of vertebral body.
395 MaskImagePointer Esophagus = GetAFDB()->template GetImage<MaskImageType>("Esophagus");
396 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read Esophagus");
398 // Crop Esophagus : keep only below the OriginOfRightMiddleLobeBronchusZ
400 clitk::CropImageAbove<MaskImageType>(Esophagus, 2,
401 m_OriginOfRightMiddleLobeBronchusZ,
403 GetBackgroundValue());
405 // Dilate to keep only not Anterior positions
406 MaskImagePointType radiusInMM = GetEsophagusDiltationForAnt();
407 Esophagus = EnlargeEsophagusDilatationRadiusInferiorly(Esophagus);
408 Esophagus = clitk::Dilate<MaskImageType>(Esophagus,
410 GetBackgroundValue(),
411 GetForegroundValue(), true);
412 clitk::PrintMemory(GetVerboseMemoryFlag(), "after dilate Esophagus");
413 writeImage<MaskImageType>(Esophagus, "enlarged-eso.mhd");
415 // Remove Anterior part according to this dilatated esophagus
416 typedef clitk::SliceBySliceRelativePositionFilter<MaskImageType> RelPosFilterType;
417 typename RelPosFilterType::Pointer relPosFilter = RelPosFilterType::New();
418 relPosFilter->VerboseStepFlagOff();
419 relPosFilter->SetInput(m_Working_Support);
420 relPosFilter->SetInputObject(Esophagus);
421 relPosFilter->AddOrientationTypeString("P");
422 relPosFilter->InverseOrientationFlagOff();
423 relPosFilter->SetDirection(2); // Z axis
424 relPosFilter->UniqueConnectedComponentBySliceOff();
425 relPosFilter->SetIntermediateSpacing(3);
426 relPosFilter->ResampleBeforeRelativePositionFilterOn();
427 relPosFilter->SetFuzzyThreshold(GetFuzzyThresholdForS8());
428 relPosFilter->RemoveObjectFlagOff();
429 relPosFilter->Update();
430 m_Working_Support = relPosFilter->GetOutput();
431 clitk::PrintMemory(GetVerboseMemoryFlag(), "after SbS Rel pos Post");
433 // AutoCrop (OR SbS ?)
434 m_Working_Support = clitk::AutoCrop<MaskImageType>(m_Working_Support, GetBackgroundValue());
436 writeImage<MaskImageType>(m_Working_Support, "step1.4.1.mhd");
438 clitk::PrintMemory(GetVerboseMemoryFlag(), "after autocrop");
440 // Estract slices for current support for slice by slice processing
441 std::vector<typename MaskSliceType::Pointer> slices;
442 clitk::ExtractSlices<MaskImageType>(m_Working_Support, 2, slices);
443 clitk::PrintMemory(GetVerboseMemoryFlag(), "after support slices");
445 // Estract slices of Esophagus (resize like support before to have the same set of slices)
446 MaskImagePointer EsophagusForSlice = clitk::ResizeImageLike<MaskImageType>(Esophagus, m_Working_Support, GetBackgroundValue());
447 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Eso resize");
448 std::vector<typename MaskSliceType::Pointer> eso_slices;
449 clitk::ExtractSlices<MaskImageType>(EsophagusForSlice, 2, eso_slices);
450 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Eso slices");
452 // Estract slices of Vertebral (resize like support before to have the same set of slices)
453 MaskImagePointer VertebralBody = GetAFDB()->template GetImage<MaskImageType>("VertebralBody");
454 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Read VertebralBody");
455 VertebralBody = clitk::ResizeImageLike<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
456 clitk::PrintMemory(GetVerboseMemoryFlag(), "after VertebralBody Resize");
457 std::vector<typename MaskSliceType::Pointer> vert_slices;
458 clitk::ExtractSlices<MaskImageType>(VertebralBody, 2, vert_slices);
459 clitk::PrintMemory(GetVerboseMemoryFlag(), "after VertebralBody slices");
461 // Estract slices of Aorta (resize like support before to have the same set of slices)
462 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
463 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Read Aorta");
464 Aorta = clitk::ResizeImageLike<MaskImageType>(Aorta, m_Working_Support, GetBackgroundValue());
465 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Aorta Resize");
466 std::vector<typename MaskSliceType::Pointer> aorta_slices;
467 clitk::ExtractSlices<MaskImageType>(Aorta, 2, aorta_slices);
468 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Aorta slices");
470 // Extract slices of Mediastinum (resize like support before to have the same set of slices)
471 m_Mediastinum = GetAFDB()->template GetImage<MaskImageType>("Mediastinum");
472 clitk::PrintMemory(GetVerboseMemoryFlag(), "after read Mediastinum");
473 m_Mediastinum = clitk::ResizeImageLike<MaskImageType>(m_Mediastinum, m_Working_Support, GetBackgroundValue());
474 clitk::PrintMemory(GetVerboseMemoryFlag(), "after resize Mediastinum");
475 std::vector<typename MaskSliceType::Pointer> mediast_slices;
476 clitk::ExtractSlices<MaskImageType>(m_Mediastinum, 2, mediast_slices);
477 clitk::PrintMemory(GetVerboseMemoryFlag(), "after Mediastinum slices");
479 writeImage<MaskImageType>(EsophagusForSlice, "slices_eso.mhd");
480 writeImage<MaskImageType>(VertebralBody, "slices_vert.mhd");
481 writeImage<MaskImageType>(Aorta, "slices_aorta.mhd");
482 writeImage<MaskImageType>(m_Mediastinum, "slices_medias.mhd");
483 writeImage<MaskImageType>(m_Working_Support, "slices_support.mhd");
487 std::vector<MaskImagePointType> p_RightMostAnt;
488 std::vector<MaskImagePointType> p_RightMostPost;
489 std::vector<MaskImagePointType> p_LeftMostAnt;
490 std::vector<MaskImagePointType> p_LeftMostPost;
491 std::vector<MaskImagePointType> p_AllPoints;
494 In the following, we search for the LeftRight limits. We search
495 for the most Right points in Esophagus and in VertebralBody and
496 consider a line between those to most right points. All points in
497 the support which are most right to this line are discarded. Same
498 for the left part. The underlying assumption is that the support
499 is concave between Eso/VertebralBody. Esophagus is a bit
500 dilatated. On VertebralBody we go right (or left) until we reach
501 the lung (but no more 20 mm).
505 MaskImagePointType p;
506 MaskImagePointType pp;
507 for(uint i=0; i<slices.size() ; i++) {
508 // Declare all needed points (sp = slice point)
509 typename MaskSliceType::PointType sp_maxRight_Eso;
510 typename MaskSliceType::PointType sp_maxRight_Aorta;
511 typename MaskSliceType::PointType sp_maxRight_Vertebra;
512 typename MaskSliceType::PointType sp_maxLeft_Eso;
513 typename MaskSliceType::PointType sp_maxLeft_Aorta;
514 typename MaskSliceType::PointType sp_maxLeft_Vertebra;
516 // Right is at left on screen, coordinate decrease
517 // Left is at right on screen, coordinate increase
519 // Find right limit of Esophagus and Aorta
520 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(eso_slices[i], GetBackgroundValue(), 0, true, sp_maxRight_Eso);
521 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(aorta_slices[i], GetBackgroundValue(), 0, true, sp_maxRight_Aorta);
522 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxRight_Eso, EsophagusForSlice, i, p);
523 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxRight_Aorta, Aorta, i, pp);
524 pp[0] -= 2; // Add a margin of 2 mm to include the 'wall'
525 p_AllPoints.push_back(p);
526 p_AllPoints.push_back(pp);
527 if (p[0]<pp[0]) p_RightMostAnt.push_back(p); // Insert point most at right
528 else p_RightMostAnt.push_back(pp);
530 // Find limit of Vertebral -> only at most Post part of current
531 // slice support. First found most ant point in VertebralBody
532 typedef MaskSliceType SliceType;
533 typename SliceType::PointType p_slice_ant;
534 bool found = clitk::FindExtremaPointInAGivenDirection<SliceType>(vert_slices[i], GetBackgroundValue(), 1, true, p_slice_ant);
536 // It should not happen ! But sometimes, a contour is missing or
537 // the VertebralBody is not delineated enough inferiorly ... in
538 // those cases, we consider the first found slice.
539 std::cerr << "No foreground pixels in this VertebralBody slices !?? I try with the previous/next slice" << std::endl;
544 found = clitk::FindExtremaPointInAGivenDirection<SliceType>(vert_slices[j], GetBackgroundValue(), 1, true, p_slice_ant);
545 //clitkExceptionMacro("No foreground pixels in this VertebralBody slices ??");
550 p_slice_ant[1] += GetDistanceMaxToAnteriorPartOfTheSpine(); // Consider offset
552 // The, find most Right and Left points on that AP position
553 typename SliceType::IndexType indexR;
554 typename SliceType::IndexType indexL;
555 vert_slices[i]->TransformPhysicalPointToIndex(p_slice_ant, indexR);
557 // Check that is inside the mask
558 indexR[1] = std::min(indexR[1], (long)vert_slices[i]->GetLargestPossibleRegion().GetSize()[1]-1);
559 indexL[1] = indexR[1];
560 while (vert_slices[i]->GetPixel(indexR) != GetBackgroundValue()) {
561 indexR[0] --; // Go to the right
563 while (vert_slices[i]->GetPixel(indexL) != GetBackgroundValue()) {
564 indexL[0] ++; // Go to the left
566 vert_slices[i]->TransformIndexToPhysicalPoint(indexR, sp_maxRight_Vertebra);
567 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxRight_Vertebra, VertebralBody, i, p);
568 p_AllPoints.push_back(p);
569 vert_slices[i]->TransformIndexToPhysicalPoint(indexL, sp_maxLeft_Vertebra);
570 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxLeft_Vertebra, VertebralBody, i, p);
571 p_AllPoints.push_back(p);
573 // Find last point out of the mediastinum on this line, Right :
574 mediast_slices[i]->TransformPhysicalPointToIndex(sp_maxRight_Vertebra, indexR);
575 double distance = 0.0;
576 while (mediast_slices[i]->GetPixel(indexR) != GetBackgroundValue()) {
578 distance += mediast_slices[i]->GetSpacing()[0];
580 if (distance < 30) { // Ok in this case, we found limit with lung
581 mediast_slices[i]->TransformIndexToPhysicalPoint(indexR, sp_maxRight_Vertebra);
582 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxRight_Vertebra, m_Mediastinum, i, p);
584 else { // in that case, we are probably below the diaphragm, so we
585 // add aribtrarly few mm
586 sp_maxRight_Vertebra[0] -= 2; // Leave 2 mm around the VertebralBody
588 p_RightMostPost.push_back(p);
589 p_AllPoints.push_back(p);
591 // Find last point out of the mediastinum on this line, Left :
592 mediast_slices[i]->TransformPhysicalPointToIndex(sp_maxLeft_Vertebra, indexL);
594 while (mediast_slices[i]->GetPixel(indexL) != GetBackgroundValue()) {
596 distance += mediast_slices[i]->GetSpacing()[0];
598 if (distance < 30) { // Ok in this case, we found limit with lung
599 mediast_slices[i]->TransformIndexToPhysicalPoint(indexL, sp_maxLeft_Vertebra);
600 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxLeft_Vertebra, m_Mediastinum, i, p);
602 else { // in that case, we are probably below the diaphragm, so we
603 // add aribtrarly few mm
604 sp_maxLeft_Vertebra[0] += 2; // Leave 2 mm around the VertebralBody
606 p_LeftMostPost.push_back(p);
607 p_AllPoints.push_back(p);
609 // --------------------------------------------------------------------------
610 // Find the limit on the Left: most left point between Eso and
611 // Vertebra. (Left is left on screen, coordinate increase)
613 // Find left limit of Esophagus
614 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(eso_slices[i], GetBackgroundValue(), 0, false, sp_maxLeft_Eso);
615 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(aorta_slices[i], GetBackgroundValue(), 0, false, sp_maxLeft_Aorta);
616 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxLeft_Eso, EsophagusForSlice, i, p);
617 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_maxLeft_Aorta, Aorta, i, pp);
618 p_AllPoints.push_back(p);
619 pp[0] += 2; // Add a margin of 2 mm to include the 'wall'
620 p_AllPoints.push_back(pp);
621 if (p[0]>pp[0]) p_LeftMostAnt.push_back(p); // Insert point most at right
622 else p_LeftMostAnt.push_back(pp);
623 } // End of slice loop
625 clitk::WriteListOfLandmarks<MaskImageType>(p_AllPoints, "LR-Eso-Vert.txt");
627 // Now uses these points to limit, slice by slice
628 // Line is mainly vertical, so mainDirection=0
629 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
630 p_RightMostAnt, p_RightMostPost,
631 GetBackgroundValue(), 0, 10);
632 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
633 p_LeftMostAnt, p_LeftMostPost,
634 GetBackgroundValue(), 0, -10);
636 m_ListOfStations["8"] = m_Working_Support;
639 //--------------------------------------------------------------------
642 //--------------------------------------------------------------------
643 template <class ImageType>
644 typename clitk::ExtractLymphStationsFilter<ImageType>::MaskImagePointer
645 clitk::ExtractLymphStationsFilter<ImageType>::
646 EnlargeEsophagusDilatationRadiusInferiorly(MaskImagePointer & Esophagus)
648 // Check if Esophagus is delineated at least until GOjunction. Now,
649 // because we use AutoCrop, Origin[2] gives this max inferior
651 // double GOjunctionZ = GetAFDB()->GetPoint3D("GOjunction", 2);
653 if (Esophagus->GetOrigin()[2] > m_DiaphragmInferiorLimit) {
654 std::cout << "Warning Esophagus is not delineated until Diaphragm. I mirror-pad it."
656 double extraSize = Esophagus->GetOrigin()[2]-m_DiaphragmInferiorLimit;
658 // Pad with few more slices
659 typedef itk::MirrorPadImageFilter<MaskImageType, MaskImageType> PadFilterType;
660 typename PadFilterType::Pointer padFilter = PadFilterType::New();
661 padFilter->SetInput(Esophagus);
663 b[0] = 0; b[1] = 0; b[2] = (uint)ceil(extraSize/Esophagus->GetSpacing()[2])+1;
664 padFilter->SetPadLowerBound(b);
666 Esophagus = padFilter->GetOutput();
670 //--------------------------------------------------------------------
673 //--------------------------------------------------------------------
674 template <class ImageType>
676 clitk::ExtractLymphStationsFilter<ImageType>::
677 ExtractStation_8_LR_Limits_old()
680 Station 8: paraeosphageal nodes
682 Laterally, it is within the pleural envelope and again abuts the
683 descending aorta on the left. Reasonably, the delineation of
684 Station 8 is limited to the soft tissue surrounding the esophagus
688 StartNewStep("[Station8] Right limits (around esophagus)");
690 MaskImagePointer Esophagus = GetAFDB()->template GetImage<MaskImageType>("Esophagus");
692 // Autocrop to get first slice with starting Esophagus
693 Esophagus = clitk::AutoCrop<MaskImageType>(Esophagus, GetBackgroundValue());
696 // LR dilatation -> large to keep point inside
697 // AP dilatation -> few mm
698 // SI dilatation -> enough to cover Diaphragm (old=GOjunctionZ)
699 MaskImagePointType radiusInMM = GetEsophagusDiltationForRight();
700 Esophagus = EnlargeEsophagusDilatationRadiusInferiorly(Esophagus);
701 Esophagus = clitk::Dilate<MaskImageType>(Esophagus,
703 GetBackgroundValue(),
704 GetForegroundValue(), true);
705 writeImage<MaskImageType>(Esophagus, "dilateEso2.mhd");
707 writeImage<MaskImageType>(m_Working_Support, "before-relpos2.mhd");
709 // Remove Right (Left on the screen) part according to this
710 // dilatated esophagus
711 typedef clitk::SliceBySliceRelativePositionFilter<MaskImageType> RelPosFilterType;
712 typename RelPosFilterType::Pointer relPosFilter = RelPosFilterType::New();
713 relPosFilter->VerboseStepFlagOff();
714 relPosFilter->WriteStepFlagOff();
715 relPosFilter->SetInput(m_Working_Support);
716 relPosFilter->SetInputObject(Esophagus);
717 relPosFilter->AddOrientationTypeString("L");
718 relPosFilter->InverseOrientationFlagOn(); // Not Left to
719 relPosFilter->SetDirection(2); // Z axis
720 relPosFilter->UniqueConnectedComponentBySliceOff(); // important
721 relPosFilter->SetIntermediateSpacing(4);
722 relPosFilter->ResampleBeforeRelativePositionFilterOn();
723 relPosFilter->SetFuzzyThreshold(0.9); // remove few part only
724 relPosFilter->RemoveObjectFlagOff();
725 relPosFilter->Update();
726 m_Working_Support = relPosFilter->GetOutput();
728 // Get a single 3D CCL
729 m_Working_Support = Labelize<MaskImageType>(m_Working_Support, 0, false, 10);
730 m_Working_Support = KeepLabels<MaskImageType>(m_Working_Support,
731 GetBackgroundValue(),
732 GetForegroundValue(), 1, 1, true);
736 // Re-Add post to Esophagus -> sometimes previous relpos remove some
737 // correct part below esophagus.
738 MaskImagePointer Esophagus = GetAFDB()->template GetImage<MaskImageType>("Esophagus");
739 EnlargeEsophagusDilatationRadiusInferiorly(Esophagus);
740 writeImage<MaskImageType>(Esophagus, "e-again.mhd");
741 typedef clitk::SliceBySliceRelativePositionFilter<MaskImageType> RelPosFilterType;
742 typename RelPosFilterType::Pointer relPosFilter = RelPosFilterType::New();
743 relPosFilter->VerboseStepOff();
744 relPosFilter->WriteStepOff();
745 relPosFilter->SetInput(m_Working_Support);
746 relPosFilter->SetInputObject(Esophagus);
747 relPosFilter->SetOrientationTypeString("P");
748 relPosFilter->InverseOrientationFlagOff();
749 relPosFilter->SetDirection(2); // Z axis
750 relPosFilter->UniqueConnectedComponentBySliceOff(); // important
751 relPosFilter->SetIntermediateSpacing(4);
752 relPosFilter->ResampleBeforeRelativePositionFilterOn();
753 relPosFilter->CombineWithOrFlagOn();
754 relPosFilter->SetFuzzyThreshold(0.9); // remove few part only
755 relPosFilter->RemoveObjectFlagOff();
756 relPosFilter->Update();
757 m_Working_Support = relPosFilter->GetOutput();
760 StopCurrentStep<MaskImageType>(m_Working_Support);
761 m_ListOfStations["8"] = m_Working_Support;
763 //--------------------------------------------------------------------