2 #include <itkBinaryDilateImageFilter.h>
3 #include <itkMirrorPadImageFilter.h>
5 //--------------------------------------------------------------------
6 template <class ImageType>
8 clitk::ExtractLymphStationsFilter<ImageType>::
9 ExtractStation_8_SetDefaultValues()
12 p[0] = 15; p[1] = 2; p[2] = 1;
13 SetEsophagusDiltationForAnt(p);
14 p[0] = 5; p[1] = 10; p[2] = 1;
15 SetEsophagusDiltationForRight(p);
16 SetFuzzyThreshold("8", "Esophagus", 0.5);
17 SetInjectedThresholdForS8(150);
19 //--------------------------------------------------------------------
21 //--------------------------------------------------------------------
22 template <class TImageType>
24 clitk::ExtractLymphStationsFilter<TImageType>::
27 if (!CheckForStation("8")) return;
29 StartNewStep("Station 8");
31 ExtractStation_8_SI_Limits(); // OK, validated
32 ExtractStation_8_Ant_Limits(); // OK, validated
33 ExtractStation_8_Left_Sup_Limits(); // OK, validated
34 ExtractStation_8_Left_Inf_Limits(); // OK, validated
35 ExtractStation_8_Single_CCL_Limits(); // OK, validated
36 ExtractStation_8_Remove_Structures(); // OK, validated
38 // Store image filenames into AFDB
39 writeImage<MaskImageType>(m_ListOfStations["8"], "seg/Station8.mhd");
40 GetAFDB()->SetImageFilename("Station8", "seg/Station8.mhd");
44 //--------------------------------------------------------------------
47 //--------------------------------------------------------------------
48 template <class ImageType>
50 clitk::ExtractLymphStationsFilter<ImageType>::
51 ExtractStation_8_SI_Limits()
54 Station 8: paraeosphageal nodes
56 "Superiorly, Station 8 begins at the level of the carina and,
57 therefore, abuts Station 3P (Fig. 3C). Inferiorly, the lower limit
58 of Station 8 was not specified in the Mountain and Dresler
59 classification (1). We delineate this volume until it reaches the
60 gastroesphogeal junction. "
62 => new classification IASCL 2009:
64 "Lower border: the diaphragm"
66 "Upper border: the upper border of the lower lobe bronchus on the
67 left; the lower border of the bronchus intermedius on the right"
70 StartNewStep("[Station8] Sup/Inf limits with LeftLower/RightMiddle Lobe and diaphragm");
72 /* -----------------------------------------------
73 NEW SUPERIOR LIMIT = LeftLowerLobeBronchus /
74 RightMiddleLobeBronchus See FindLineForS7S8Separation
75 -----------------------------------------------
79 FindLineForS7S8Separation(A, B);
81 // add one slice to be adjacent to Station7
82 B[2] += m_Working_Support->GetSpacing()[2];
83 A[2] += m_Working_Support->GetSpacing()[2];
85 // Use the line to remove the inferior part
87 SliceBySliceSetBackgroundFromSingleLine<MaskImageType>(m_Working_Support,
88 GetBackgroundValue(), A, B, 2, 0, true);
90 /* -----------------------------------------------
91 INFERIOR LIMIT = Diaphragm
92 -----------------------------------------------
95 // Found most inferior part of the lung
96 MaskImagePointer Lungs = GetAFDB()->template GetImage<MaskImageType>("Lungs");
97 // It should be already croped, so I took the origin and add 10mm above
98 m_DiaphragmInferiorLimit = Lungs->GetOrigin()[2]+10;
99 GetAFDB()->template ReleaseImage<MaskImageType>("Lungs");
102 clitk::CropImageAlongOneAxis<MaskImageType>(m_Working_Support, 2,
103 m_DiaphragmInferiorLimit,
105 GetBackgroundValue());
107 StopCurrentStep<MaskImageType>(m_Working_Support);
108 m_ListOfStations["8"] = m_Working_Support;
110 //--------------------------------------------------------------------
113 //--------------------------------------------------------------------
114 template <class ImageType>
116 clitk::ExtractLymphStationsFilter<ImageType>::
117 ExtractStation_8_Ant_Limits()
119 //--------------------------------------------------------------------
120 StartNewStep("[Station8] Ant part: not post to Esophagus");
122 Consider Esophagus, dilate it and remove ant part. It remains part
123 on L & R, than can be partly removed by cutting what remains at
124 right of vertebral body.
128 m_Esophagus = GetAFDB()->template GetImage<MaskImageType>("Esophagus");
130 // In images from the original article, Atlas – UM, the oesophagus
131 //was included in nodal stations 3p and 8. Having said that, in the
132 //description for station 8, it indicates that “the delineation of
133 //station 8 is limited to the soft tissues surrounding the
134 //oesophagus”. In the recent article, The IASLC Lung Cancer Staging
135 //Project (J Thorac Oncol 4:5, 568-77), the images drawn by
136 //Dr. Aletta Frasier exclude this structure. From an oncological
137 //prospective, the oesophagus should be excluded from these nodal
140 // Resize Esophagus like current support
142 clitk::ResizeImageLike<MaskImageType>(m_Esophagus, m_Working_Support, GetBackgroundValue()); // Needed ?
144 // Dilate to keep only not Anterior positions
145 MaskImagePointType radiusInMM = GetEsophagusDiltationForAnt();
146 m_Esophagus = clitk::Dilate<MaskImageType>(m_Esophagus,
148 GetBackgroundValue(),
149 GetForegroundValue(), true);
150 // Keep what is Posterior to Esophagus
151 typedef clitk::SliceBySliceRelativePositionFilter<MaskImageType> RelPosFilterType;
152 typename RelPosFilterType::Pointer relPosFilter = RelPosFilterType::New();
153 relPosFilter->VerboseStepFlagOff();
154 relPosFilter->WriteStepFlagOff();
155 relPosFilter->SetInput(m_Working_Support);
156 relPosFilter->SetInputObject(m_Esophagus);
157 relPosFilter->AddOrientationTypeString("PostTo");
158 // relPosFilter->InverseOrientationFlagOff();
159 relPosFilter->SetDirection(2); // Z axis
160 relPosFilter->UniqueConnectedComponentBySliceOff();
161 relPosFilter->SetIntermediateSpacing(3);
162 relPosFilter->IntermediateSpacingFlagOn();
163 relPosFilter->SetFuzzyThreshold(GetFuzzyThreshold("8", "Esophagus"));
164 relPosFilter->RemoveObjectFlagOff(); // Do not exclude here because it is dilated
165 relPosFilter->CombineWithOrFlagOff(); // NO !
166 relPosFilter->IgnoreEmptySliceObjectFlagOn();
167 relPosFilter->Update();
168 m_Working_Support = relPosFilter->GetOutput();
170 // AutoCrop (OR SbS ?)
171 m_Working_Support = clitk::AutoCrop<MaskImageType>(m_Working_Support, GetBackgroundValue());
173 StopCurrentStep<MaskImageType>(m_Working_Support);
174 m_ListOfStations["8"] = m_Working_Support;
176 //--------------------------------------------------------------------
179 //--------------------------------------------------------------------
180 template <class ImageType>
182 clitk::ExtractLymphStationsFilter<ImageType>::
183 ExtractStation_8_Left_Sup_Limits()
185 //--------------------------------------------------------------------
186 StartNewStep("[Station8] Left limits: remove Left to line from Aorta to PulmonaryTrunk");
189 We consider a line from Left part of Aorta to left part of
190 PulmonaryTrunk and remove what is at Left.
193 // Load the structures
194 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
195 MaskImagePointer PulmonaryTrunk = GetAFDB()->template GetImage<MaskImageType>("PulmonaryTrunk");
197 // Resize like the PT and define the slices
198 MaskImagePointType min, max;
199 clitk::GetMinMaxPointPosition<MaskImageType>(PulmonaryTrunk, min, max);
200 Aorta = clitk::CropImageAlongOneAxis<MaskImageType>(Aorta, 2, min[2], max[2], false, GetBackgroundValue());
201 std::vector<MaskSlicePointer> slices_aorta;
202 clitk::ExtractSlices<MaskImageType>(Aorta, 2, slices_aorta);
203 std::vector<MaskSlicePointer> slices_PT;
204 clitk::ExtractSlices<MaskImageType>(PulmonaryTrunk, 2, slices_PT);
206 // Find the points at left
207 std::vector<MaskImagePointType> p_A;
208 std::vector<MaskImagePointType> p_B;
209 MaskImagePointType pA;
210 MaskImagePointType pB;
211 for(uint i=0; i<slices_PT.size(); i++) {
212 typename MaskSliceType::PointType ps;
213 // In Aorta (assume a single CCL)
215 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices_aorta[i], GetBackgroundValue(), 0, false, ps);
216 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(ps, Aorta, i, pA);
219 // In PT : generally 2 CCL, we keep the most at left
221 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices_PT[i], GetBackgroundValue(), 0, false, ps);
222 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(ps, PulmonaryTrunk, i, pB);
230 clitk::WriteListOfLandmarks<MaskImageType>(p_A, "S8-Aorta-Left-points.txt");
231 clitk::WriteListOfLandmarks<MaskImageType>(p_B, "S8-PT-Left-points.txt");
233 // Remove part at Left
234 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
236 GetBackgroundValue(),
239 StopCurrentStep<MaskImageType>(m_Working_Support);
240 m_ListOfStations["8"] = m_Working_Support;
242 //--------------------------------------------------------------------
245 //--------------------------------------------------------------------
246 template <class ImageType>
248 clitk::ExtractLymphStationsFilter<ImageType>::
249 ExtractStation_8_Single_CCL_Limits()
251 //--------------------------------------------------------------------
252 StartNewStep("[Station8 or 3P] Slice by slice, keep a single CCL (the closest to VertebralBody)");
255 std::vector<typename MaskSliceType::Pointer> slices;
256 std::vector<typename MaskSliceType::Pointer> slices_vb;
257 clitk::ExtractSlices<MaskImageType>(m_Working_Support, 2, slices);
258 MaskImagePointer VertebralBody =
259 GetAFDB()->template GetImage <MaskImageType>("VertebralBody");
260 VertebralBody = clitk::ResizeImageLike<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
261 clitk::ExtractSlices<MaskImageType>(VertebralBody, 2, slices_vb);
263 for(uint i=0; i<slices.size(); i++) {
264 // Decompose in labels
265 slices[i] = Labelize<MaskSliceType>(slices[i], 0, true, 100);
266 // Compute centroids coordinate
267 std::vector<typename MaskSliceType::PointType> centroids;
268 std::vector<typename MaskSliceType::PointType> c;
269 clitk::ComputeCentroids<MaskSliceType>(slices[i], GetBackgroundValue(), centroids);
270 clitk::ComputeCentroids<MaskSliceType>(slices_vb[i], GetBackgroundValue(), c);
271 if ((c.size() > 1) && (centroids.size() > 1)) {
272 // keep the one which is the closer to vertebralbody centroid
273 double distance=1000000;
275 for(uint j=1; j<centroids.size(); j++) {
276 double d = centroids[j].EuclideanDistanceTo(c[1]);
282 // remove all others label
283 slices[i] = KeepLabels<MaskSliceType>(slices[i], GetBackgroundValue(),
284 GetForegroundValue(), index, index, true);
287 m_Working_Support = clitk::JoinSlices<MaskImageType>(slices, m_Working_Support, 2);
289 StopCurrentStep<MaskImageType>(m_Working_Support);
290 m_ListOfStations["8"] = m_Working_Support;
292 //--------------------------------------------------------------------
295 //--------------------------------------------------------------------
296 template <class ImageType>
298 clitk::ExtractLymphStationsFilter<ImageType>::
299 ExtractStation_8_Left_Inf_Limits()
301 //--------------------------------------------------------------------
302 StartNewStep("[Station8] Left limits around esophagus with lines from VertebralBody-Aorta-Esophagus");
304 // Estract slices for current support for slice by slice processing
305 std::vector<typename MaskSliceType::Pointer> slices;
306 clitk::ExtractSlices<MaskImageType>(m_Working_Support, 2, slices);
308 // Remove what is outside the mediastinum in this enlarged Esophagus -> it allows to select
309 // 'better' extrema points (not too post).
310 MaskImagePointer Lungs = GetAFDB()->template GetImage<MaskImageType>("Lungs");
311 clitk::AndNot<MaskImageType>(m_Esophagus, Lungs, GetBackgroundValue());
312 GetAFDB()->template ReleaseImage<MaskImageType>("Lungs");
314 // Estract slices of Esophagus (resize like support before to have the same set of slices)
315 MaskImagePointer EsophagusForSlice =
316 clitk::ResizeImageLike<MaskImageType>(m_Esophagus, m_Working_Support, GetBackgroundValue());
317 std::vector<typename MaskSliceType::Pointer> eso_slices;
318 clitk::ExtractSlices<MaskImageType>(EsophagusForSlice, 2, eso_slices);
320 // Estract slices of Vertebral (resize like support before to have the same set of slices)
321 MaskImagePointer VertebralBody = GetAFDB()->template GetImage<MaskImageType>("VertebralBody");
322 VertebralBody = clitk::ResizeImageLike<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
323 // Remove what is outside the support to not consider what is to
324 // posterior in the VertebralBody (post the horizontal line)
325 clitk::And<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
326 // writeImage<MaskImageType>(VertebralBody, "vb.mhd");
327 std::vector<typename MaskSliceType::Pointer> vert_slices;
328 clitk::ExtractSlices<MaskImageType>(VertebralBody, 2, vert_slices);
330 // Estract slices of Aorta (resize like support before to have the same set of slices)
331 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
332 Aorta = clitk::ResizeImageLike<MaskImageType>(Aorta, m_Working_Support, GetBackgroundValue());
333 std::vector<typename MaskSliceType::Pointer> aorta_slices;
334 clitk::ExtractSlices<MaskImageType>(Aorta, 2, aorta_slices);
336 // Extract slices of Mediastinum (resize like support before to have the same set of slices)
337 m_Mediastinum = GetAFDB()->template GetImage<MaskImageType>("Mediastinum");
338 m_Mediastinum = clitk::ResizeImageLike<MaskImageType>(m_Mediastinum, m_Working_Support, GetBackgroundValue());
339 std::vector<typename MaskSliceType::Pointer> mediast_slices;
340 clitk::ExtractSlices<MaskImageType>(m_Mediastinum, 2, mediast_slices);
343 std::vector<MaskImagePointType> p_MostLeftVertebralBody;
344 std::vector<MaskImagePointType> p_MostRightVertebralBody;
345 std::vector<MaskImagePointType> p_MostLeftAorta;
346 std::vector<MaskImagePointType> p_MostLeftEsophagus;
349 In the following, we search for the LeftRight limits. We search
350 for the most Right points in Esophagus and in VertebralBody and
351 consider a line between those to most right points. All points in
352 the support which are most right to this line are discarded. Same
353 for the left part. The underlying assumption is that the support
354 is concave between Eso/VertebralBody. Esophagus is a bit
355 dilatated. On VertebralBody we go right (or left) until we reach
356 the lung (but no more 20 mm).
359 // Temporary 3D point
360 MaskImagePointType p;
362 typename MaskSliceType::PointType minSlicePoint;
363 typename MaskSliceType::PointType maxSlicePoint;
364 clitk::GetMinMaxPointPosition<MaskSliceType>(vert_slices[0], minSlicePoint, maxSlicePoint);
367 for(uint i=0; i<slices.size() ; i++) {
368 // Declare all needed 2D points (sp = slice point)
369 // typename MaskSliceType::PointType sp_MostRightVertebralBody;
370 typename MaskSliceType::PointType sp_MostLeftVertebralBody;
371 typename MaskSliceType::PointType sp_MostLeftAorta;
372 typename MaskSliceType::PointType sp_temp;
373 typename MaskSliceType::PointType sp_MostLeftEsophagus;
375 /* -------------------------------------------------------------------------------------
376 Find first point not in mediastinum at LEFT
378 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(vert_slices[i], GetBackgroundValue(),
379 0, false, sp_MostLeftVertebralBody);
380 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
383 sp_MostLeftVertebralBody =
384 clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, false,
385 sp_MostLeftVertebralBody, GetBackgroundValue(), 30);
386 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
389 sp_MostLeftVertebralBody[0] += 2; // 2mm margin
390 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
393 // Convert 2D points into 3D
394 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
395 p_MostLeftVertebralBody.push_back(p);
397 /* -------------------------------------------------------------------------------------
398 Find first point not in mediastinum at RIGHT. Not used yet.
399 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(vert_slices[i], GetBackgroundValue(),
400 0, true, sp_MostRightVertebralBody);
401 sp_MostRightVertebralBody =
402 clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, true,
403 sp_MostRightVertebralBody, GetBackgroundValue(),30);
404 sp_MostRightVertebralBody[0] -= 2; // 2 mm margin
406 // Convert 2D points into 3D
407 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostRightVertebralBody, VertebralBody, i, p);
408 p_MostRightVertebralBody.push_back(p);
412 /* -------------------------------------------------------------------------------------
413 Find most Left point in Esophagus
415 bool found = clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(eso_slices[i], GetBackgroundValue(), 0, false, sp_MostLeftEsophagus);
416 if (!found) { // No more Esophagus, I remove the previous point
417 //DD("no eso pop back");
418 p_MostLeftVertebralBody.pop_back();
421 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftEsophagus, EsophagusForSlice, i, p);
422 p_MostLeftEsophagus.push_back(p);
425 /* -------------------------------------------------------------------------------------
426 Find most Left point in Aorta
429 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(aorta_slices[i], GetBackgroundValue(), 0, false, sp_MostLeftAorta);
430 sp_MostLeftAorta = clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, false, sp_MostLeftAorta, GetBackgroundValue(), 10);
431 typename MaskSliceType::PointType temp=sp_MostLeftEsophagus;
433 if (clitk::IsOnTheSameLineSide(sp_MostLeftAorta, sp_MostLeftVertebralBody, sp_MostLeftEsophagus, temp)) {
434 // sp_MostLeftAorta is on the same side than temp (at Right) -> ignore Aorta
435 sp_MostLeftAorta = sp_MostLeftEsophagus;
437 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftAorta, Aorta, i, p);
438 p_MostLeftAorta.push_back(p);
441 } // End of slice loop
443 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftVertebralBody, "S8-MostLeft-VB-points.txt");
444 // clitk::WriteListOfLandmarks<MaskImageType>(p_MostRightVertebralBody, "S8-MostRight-VB-points.txt");
445 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftAorta, "S8-MostLeft-Aorta-points.txt");
446 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftEsophagus, "S8-MostLeft-eso-points.txt");
448 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
449 p_MostLeftVertebralBody, p_MostLeftAorta,
450 GetBackgroundValue(), 0, -10);
452 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
453 p_MostLeftAorta, p_MostLeftEsophagus,
454 GetBackgroundValue(), 0, -10);
456 StopCurrentStep<MaskImageType>(m_Working_Support);
457 m_ListOfStations["8"] = m_Working_Support;
460 //--------------------------------------------------------------------
463 //--------------------------------------------------------------------
464 template <class ImageType>
466 clitk::ExtractLymphStationsFilter<ImageType>::
467 ExtractStation_8_Remove_Structures()
469 //--------------------------------------------------------------------
470 Remove_Structures("8", "Aorta");
471 Remove_Structures("8", "Esophagus");
472 Remove_Structures("8", "VertebralBody");
475 StopCurrentStep<MaskImageType>(m_Working_Support);
476 m_ListOfStations["8"] = m_Working_Support;
479 //--------------------------------------------------------------------
482 //--------------------------------------------------------------------
483 template <class TImageType>
485 clitk::ExtractLymphStationsFilter<TImageType>::
486 FindExtremaPointsInBronchus(MaskImagePointer input,
488 double distance_max_from_center_point,
489 ListOfPointsType & LR,
490 ListOfPointsType & Ant,
491 ListOfPointsType & Post)
494 // Other solution ==> with auto bounding box ! (but pb to prevent to
495 // be too distant from the center point
498 std::vector<typename MaskSliceType::Pointer> slices;
499 clitk::ExtractSlices<MaskImageType>(input, 2, slices);
503 for(uint i=0; i<slices.size(); i++) {
506 slices[i] = Labelize<MaskSliceType>(slices[i], 0, true, 10);
507 slices[i] = KeepLabels<MaskSliceType>(slices[i],
508 GetBackgroundValue(),
509 GetForegroundValue(), 1, 1, true);
512 // ------- Find rightmost or leftmost point -------
513 MaskSliceType::PointType LRMost;
515 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
516 GetBackgroundValue(),
518 (direction==0?false:true), // right or left according to direction
520 // ------- Find postmost point -------
521 MaskSliceType::PointType postMost;
523 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
524 GetBackgroundValue(),
526 distance_max_from_center_point,
528 // ------- Find antmost point -------
529 MaskSliceType::PointType antMost;
531 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
532 GetBackgroundValue(),
534 distance_max_from_center_point,
536 // Only add point if found
538 // ------- Convert 2D to 3D points --------
539 MaskImageType::PointType p;
540 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(LRMost, input, i, p);
542 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(antMost, input, i, p);
544 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(postMost, input, i, p);
549 //--------------------------------------------------------------------