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")) {
28 ExtractStation_8_SI_Limits(); // OK, validated
29 ExtractStation_8_Ant_Limits(); // OK, validated
30 ExtractStation_8_Left_Sup_Limits(); // OK, validated
31 ExtractStation_8_Left_Inf_Limits(); // OK, validated
32 ExtractStation_8_Single_CCL_Limits(); // OK, validated
33 ExtractStation_8_Remove_Structures(); // OK, validated
35 // Store image filenames into AFDB
36 writeImage<MaskImageType>(m_ListOfStations["8"], "seg/Station8.mhd");
37 GetAFDB()->SetImageFilename("Station8", "seg/Station8.mhd");
41 //--------------------------------------------------------------------
44 //--------------------------------------------------------------------
45 template <class ImageType>
47 clitk::ExtractLymphStationsFilter<ImageType>::
48 ExtractStation_8_SI_Limits()
51 Station 8: paraeosphageal nodes
53 "Superiorly, Station 8 begins at the level of the carina and,
54 therefore, abuts Station 3P (Fig. 3C). Inferiorly, the lower limit
55 of Station 8 was not specified in the Mountain and Dresler
56 classification (1). We delineate this volume until it reaches the
57 gastroesphogeal junction. "
59 => new classification IASCL 2009:
61 "Lower border: the diaphragm"
63 "Upper border: the upper border of the lower lobe bronchus on the
64 left; the lower border of the bronchus intermedius on the right"
67 StartNewStep("[Station8] Sup/Inf limits with LeftLower/RightMiddle Lobe and diaphragm");
69 /* -----------------------------------------------
70 NEW SUPERIOR LIMIT = LeftLowerLobeBronchus /
71 RightMiddleLobeBronchus See FindLineForS7S8Separation
72 -----------------------------------------------
76 FindLineForS7S8Separation(A, B);
78 // add one slice to be adjacent to Station7
79 B[2] += m_Working_Support->GetSpacing()[2];
80 A[2] += m_Working_Support->GetSpacing()[2];
82 // Use the line to remove the inferior part
84 SliceBySliceSetBackgroundFromSingleLine<MaskImageType>(m_Working_Support,
85 GetBackgroundValue(), A, B, 2, 0, true);
87 /* -----------------------------------------------
88 INFERIOR LIMIT = Diaphragm
89 -----------------------------------------------
92 // Found most inferior part of the lung
93 MaskImagePointer Lungs = GetAFDB()->template GetImage<MaskImageType>("Lungs");
94 // It should be already croped, so I took the origin and add 10mm above
95 m_DiaphragmInferiorLimit = Lungs->GetOrigin()[2]+10;
96 GetAFDB()->template ReleaseImage<MaskImageType>("Lungs");
99 clitk::CropImageAlongOneAxis<MaskImageType>(m_Working_Support, 2,
100 m_DiaphragmInferiorLimit,
102 GetBackgroundValue());
104 StopCurrentStep<MaskImageType>(m_Working_Support);
105 m_ListOfStations["8"] = m_Working_Support;
107 //--------------------------------------------------------------------
110 //--------------------------------------------------------------------
111 template <class ImageType>
113 clitk::ExtractLymphStationsFilter<ImageType>::
114 ExtractStation_8_Ant_Limits()
116 //--------------------------------------------------------------------
117 StartNewStep("[Station8] Ant part: not post to Esophagus");
119 Consider Esophagus, dilate it and remove ant part. It remains part
120 on L & R, than can be partly removed by cutting what remains at
121 right of vertebral body.
125 m_Esophagus = GetAFDB()->template GetImage<MaskImageType>("Esophagus");
127 // In images from the original article, Atlas – UM, the oesophagus
128 //was included in nodal stations 3p and 8. Having said that, in the
129 //description for station 8, it indicates that “the delineation of
130 //station 8 is limited to the soft tissues surrounding the
131 //oesophagus”. In the recent article, The IASLC Lung Cancer Staging
132 //Project (J Thorac Oncol 4:5, 568-77), the images drawn by
133 //Dr. Aletta Frasier exclude this structure. From an oncological
134 //prospective, the oesophagus should be excluded from these nodal
137 // Resize Esophagus like current support
139 clitk::ResizeImageLike<MaskImageType>(m_Esophagus, m_Working_Support, GetBackgroundValue()); // Needed ?
141 // Dilate to keep only not Anterior positions
142 MaskImagePointType radiusInMM = GetEsophagusDiltationForAnt();
143 m_Esophagus = clitk::Dilate<MaskImageType>(m_Esophagus,
145 GetBackgroundValue(),
146 GetForegroundValue(), true);
147 // Keep what is Posterior to Esophagus
148 typedef clitk::SliceBySliceRelativePositionFilter<MaskImageType> RelPosFilterType;
149 typename RelPosFilterType::Pointer relPosFilter = RelPosFilterType::New();
150 relPosFilter->VerboseStepFlagOff();
151 relPosFilter->WriteStepFlagOff();
152 relPosFilter->SetInput(m_Working_Support);
153 relPosFilter->SetInputObject(m_Esophagus);
154 relPosFilter->AddOrientationTypeString("PostTo");
155 // relPosFilter->InverseOrientationFlagOff();
156 relPosFilter->SetDirection(2); // Z axis
157 relPosFilter->UniqueConnectedComponentBySliceOff();
158 relPosFilter->SetIntermediateSpacing(3);
159 relPosFilter->IntermediateSpacingFlagOn();
160 relPosFilter->SetFuzzyThreshold(GetFuzzyThreshold("8", "Esophagus"));
161 relPosFilter->RemoveObjectFlagOff(); // Do not exclude here because it is dilated
162 relPosFilter->CombineWithOrFlagOff(); // NO !
163 relPosFilter->IgnoreEmptySliceObjectFlagOn();
164 relPosFilter->Update();
165 m_Working_Support = relPosFilter->GetOutput();
167 // AutoCrop (OR SbS ?)
168 m_Working_Support = clitk::AutoCrop<MaskImageType>(m_Working_Support, GetBackgroundValue());
170 StopCurrentStep<MaskImageType>(m_Working_Support);
171 m_ListOfStations["8"] = m_Working_Support;
173 //--------------------------------------------------------------------
176 //--------------------------------------------------------------------
177 template <class ImageType>
179 clitk::ExtractLymphStationsFilter<ImageType>::
180 ExtractStation_8_Left_Sup_Limits()
182 //--------------------------------------------------------------------
183 StartNewStep("[Station8] Left limits: remove Left to line from Aorta to PulmonaryTrunk");
186 We consider a line from Left part of Aorta to left part of
187 PulmonaryTrunk and remove what is at Left.
190 // Load the structures
191 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
192 MaskImagePointer PulmonaryTrunk = GetAFDB()->template GetImage<MaskImageType>("PulmonaryTrunk");
194 // Resize like the PT and define the slices
195 MaskImagePointType min, max;
196 clitk::GetMinMaxPointPosition<MaskImageType>(PulmonaryTrunk, min, max);
197 Aorta = clitk::CropImageAlongOneAxis<MaskImageType>(Aorta, 2, min[2], max[2], false, GetBackgroundValue());
198 std::vector<MaskSlicePointer> slices_aorta;
199 clitk::ExtractSlices<MaskImageType>(Aorta, 2, slices_aorta);
200 std::vector<MaskSlicePointer> slices_PT;
201 clitk::ExtractSlices<MaskImageType>(PulmonaryTrunk, 2, slices_PT);
203 // Find the points at left
204 std::vector<MaskImagePointType> p_A;
205 std::vector<MaskImagePointType> p_B;
206 MaskImagePointType pA;
207 MaskImagePointType pB;
208 for(uint i=0; i<slices_PT.size(); i++) {
209 typename MaskSliceType::PointType ps;
210 // In Aorta (assume a single CCL)
212 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices_aorta[i], GetBackgroundValue(), 0, false, ps);
213 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(ps, Aorta, i, pA);
216 // In PT : generally 2 CCL, we keep the most at left
218 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices_PT[i], GetBackgroundValue(), 0, false, ps);
219 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(ps, PulmonaryTrunk, i, pB);
227 clitk::WriteListOfLandmarks<MaskImageType>(p_A, "S8-Aorta-Left-points.txt");
228 clitk::WriteListOfLandmarks<MaskImageType>(p_B, "S8-PT-Left-points.txt");
230 // Remove part at Left
231 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
233 GetBackgroundValue(),
236 StopCurrentStep<MaskImageType>(m_Working_Support);
237 m_ListOfStations["8"] = m_Working_Support;
239 //--------------------------------------------------------------------
242 //--------------------------------------------------------------------
243 template <class ImageType>
245 clitk::ExtractLymphStationsFilter<ImageType>::
246 ExtractStation_8_Single_CCL_Limits()
248 //--------------------------------------------------------------------
249 StartNewStep("[Station8 or 3P] Slice by slice, keep a single CCL (the closest to VertebralBody)");
252 std::vector<typename MaskSliceType::Pointer> slices;
253 std::vector<typename MaskSliceType::Pointer> slices_vb;
254 clitk::ExtractSlices<MaskImageType>(m_Working_Support, 2, slices);
255 MaskImagePointer VertebralBody =
256 GetAFDB()->template GetImage <MaskImageType>("VertebralBody");
257 VertebralBody = clitk::ResizeImageLike<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
258 clitk::ExtractSlices<MaskImageType>(VertebralBody, 2, slices_vb);
260 for(uint i=0; i<slices.size(); i++) {
261 // Decompose in labels
262 slices[i] = Labelize<MaskSliceType>(slices[i], 0, true, 100);
263 // Compute centroids coordinate
264 std::vector<typename MaskSliceType::PointType> centroids;
265 std::vector<typename MaskSliceType::PointType> c;
266 clitk::ComputeCentroids<MaskSliceType>(slices[i], GetBackgroundValue(), centroids);
267 clitk::ComputeCentroids<MaskSliceType>(slices_vb[i], GetBackgroundValue(), c);
268 if ((c.size() > 1) && (centroids.size() > 1)) {
269 // keep the one which is the closer to vertebralbody centroid
270 double distance=1000000;
272 for(uint j=1; j<centroids.size(); j++) {
273 double d = centroids[j].EuclideanDistanceTo(c[1]);
279 // remove all others label
280 slices[i] = KeepLabels<MaskSliceType>(slices[i], GetBackgroundValue(),
281 GetForegroundValue(), index, index, true);
284 m_Working_Support = clitk::JoinSlices<MaskImageType>(slices, m_Working_Support, 2);
286 StopCurrentStep<MaskImageType>(m_Working_Support);
287 m_ListOfStations["8"] = m_Working_Support;
289 //--------------------------------------------------------------------
292 //--------------------------------------------------------------------
293 template <class ImageType>
295 clitk::ExtractLymphStationsFilter<ImageType>::
296 ExtractStation_8_Left_Inf_Limits()
298 //--------------------------------------------------------------------
299 StartNewStep("[Station8] Left limits around esophagus with lines from VertebralBody-Aorta-Esophagus");
301 // Estract slices for current support for slice by slice processing
302 std::vector<typename MaskSliceType::Pointer> slices;
303 clitk::ExtractSlices<MaskImageType>(m_Working_Support, 2, slices);
305 // Remove what is outside the mediastinum in this enlarged Esophagus -> it allows to select
306 // 'better' extrema points (not too post).
307 MaskImagePointer Lungs = GetAFDB()->template GetImage<MaskImageType>("Lungs");
308 clitk::AndNot<MaskImageType>(m_Esophagus, Lungs, GetBackgroundValue());
309 GetAFDB()->template ReleaseImage<MaskImageType>("Lungs");
311 // Estract slices of Esophagus (resize like support before to have the same set of slices)
312 MaskImagePointer EsophagusForSlice =
313 clitk::ResizeImageLike<MaskImageType>(m_Esophagus, m_Working_Support, GetBackgroundValue());
314 std::vector<typename MaskSliceType::Pointer> eso_slices;
315 clitk::ExtractSlices<MaskImageType>(EsophagusForSlice, 2, eso_slices);
317 // Estract slices of Vertebral (resize like support before to have the same set of slices)
318 MaskImagePointer VertebralBody = GetAFDB()->template GetImage<MaskImageType>("VertebralBody");
319 VertebralBody = clitk::ResizeImageLike<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
320 // Remove what is outside the support to not consider what is to
321 // posterior in the VertebralBody (post the horizontal line)
322 clitk::And<MaskImageType>(VertebralBody, m_Working_Support, GetBackgroundValue());
323 // writeImage<MaskImageType>(VertebralBody, "vb.mhd");
324 std::vector<typename MaskSliceType::Pointer> vert_slices;
325 clitk::ExtractSlices<MaskImageType>(VertebralBody, 2, vert_slices);
327 // Estract slices of Aorta (resize like support before to have the same set of slices)
328 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
329 Aorta = clitk::ResizeImageLike<MaskImageType>(Aorta, m_Working_Support, GetBackgroundValue());
330 std::vector<typename MaskSliceType::Pointer> aorta_slices;
331 clitk::ExtractSlices<MaskImageType>(Aorta, 2, aorta_slices);
333 // Extract slices of Mediastinum (resize like support before to have the same set of slices)
334 m_Mediastinum = GetAFDB()->template GetImage<MaskImageType>("Mediastinum");
335 m_Mediastinum = clitk::ResizeImageLike<MaskImageType>(m_Mediastinum, m_Working_Support, GetBackgroundValue());
336 std::vector<typename MaskSliceType::Pointer> mediast_slices;
337 clitk::ExtractSlices<MaskImageType>(m_Mediastinum, 2, mediast_slices);
340 std::vector<MaskImagePointType> p_MostLeftVertebralBody;
341 std::vector<MaskImagePointType> p_MostRightVertebralBody;
342 std::vector<MaskImagePointType> p_MostLeftAorta;
343 std::vector<MaskImagePointType> p_MostLeftEsophagus;
346 In the following, we search for the LeftRight limits. We search
347 for the most Right points in Esophagus and in VertebralBody and
348 consider a line between those to most right points. All points in
349 the support which are most right to this line are discarded. Same
350 for the left part. The underlying assumption is that the support
351 is concave between Eso/VertebralBody. Esophagus is a bit
352 dilatated. On VertebralBody we go right (or left) until we reach
353 the lung (but no more 20 mm).
356 // Temporary 3D point
357 MaskImagePointType p;
359 typename MaskSliceType::PointType minSlicePoint;
360 typename MaskSliceType::PointType maxSlicePoint;
361 clitk::GetMinMaxPointPosition<MaskSliceType>(vert_slices[0], minSlicePoint, maxSlicePoint);
364 for(uint i=0; i<slices.size() ; i++) {
365 // Declare all needed 2D points (sp = slice point)
366 // typename MaskSliceType::PointType sp_MostRightVertebralBody;
367 typename MaskSliceType::PointType sp_MostLeftVertebralBody;
368 typename MaskSliceType::PointType sp_MostLeftAorta;
369 typename MaskSliceType::PointType sp_temp;
370 typename MaskSliceType::PointType sp_MostLeftEsophagus;
372 /* -------------------------------------------------------------------------------------
373 Find first point not in mediastinum at LEFT
375 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(vert_slices[i], GetBackgroundValue(),
376 0, false, sp_MostLeftVertebralBody);
377 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
380 sp_MostLeftVertebralBody =
381 clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, false,
382 sp_MostLeftVertebralBody, GetBackgroundValue(), 30);
383 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
386 sp_MostLeftVertebralBody[0] += 2; // 2mm margin
387 // clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
390 // Convert 2D points into 3D
391 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftVertebralBody, VertebralBody, i, p);
392 p_MostLeftVertebralBody.push_back(p);
394 /* -------------------------------------------------------------------------------------
395 Find first point not in mediastinum at RIGHT. Not used yet.
396 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(vert_slices[i], GetBackgroundValue(),
397 0, true, sp_MostRightVertebralBody);
398 sp_MostRightVertebralBody =
399 clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, true,
400 sp_MostRightVertebralBody, GetBackgroundValue(),30);
401 sp_MostRightVertebralBody[0] -= 2; // 2 mm margin
403 // Convert 2D points into 3D
404 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostRightVertebralBody, VertebralBody, i, p);
405 p_MostRightVertebralBody.push_back(p);
409 /* -------------------------------------------------------------------------------------
410 Find most Left point in Esophagus
412 bool found = clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(eso_slices[i], GetBackgroundValue(), 0, false, sp_MostLeftEsophagus);
413 if (!found) { // No more Esophagus, I remove the previous point
414 //DD("no eso pop back");
415 p_MostLeftVertebralBody.pop_back();
418 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftEsophagus, EsophagusForSlice, i, p);
419 p_MostLeftEsophagus.push_back(p);
422 /* -------------------------------------------------------------------------------------
423 Find most Left point in Aorta
426 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(aorta_slices[i], GetBackgroundValue(), 0, false, sp_MostLeftAorta);
427 sp_MostLeftAorta = clitk::FindExtremaPointInAGivenLine<MaskSliceType>(mediast_slices[i], 0, false, sp_MostLeftAorta, GetBackgroundValue(), 10);
428 typename MaskSliceType::PointType temp=sp_MostLeftEsophagus;
430 if (clitk::IsOnTheSameLineSide(sp_MostLeftAorta, sp_MostLeftVertebralBody, sp_MostLeftEsophagus, temp)) {
431 // sp_MostLeftAorta is on the same side than temp (at Right) -> ignore Aorta
432 sp_MostLeftAorta = sp_MostLeftEsophagus;
434 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(sp_MostLeftAorta, Aorta, i, p);
435 p_MostLeftAorta.push_back(p);
438 } // End of slice loop
440 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftVertebralBody, "S8-MostLeft-VB-points.txt");
441 // clitk::WriteListOfLandmarks<MaskImageType>(p_MostRightVertebralBody, "S8-MostRight-VB-points.txt");
442 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftAorta, "S8-MostLeft-Aorta-points.txt");
443 clitk::WriteListOfLandmarks<MaskImageType>(p_MostLeftEsophagus, "S8-MostLeft-eso-points.txt");
445 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
446 p_MostLeftVertebralBody, p_MostLeftAorta,
447 GetBackgroundValue(), 0, -10);
449 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
450 p_MostLeftAorta, p_MostLeftEsophagus,
451 GetBackgroundValue(), 0, -10);
453 StopCurrentStep<MaskImageType>(m_Working_Support);
454 m_ListOfStations["8"] = m_Working_Support;
457 //--------------------------------------------------------------------
460 //--------------------------------------------------------------------
461 template <class ImageType>
463 clitk::ExtractLymphStationsFilter<ImageType>::
464 ExtractStation_8_Remove_Structures()
466 //--------------------------------------------------------------------
467 Remove_Structures("8", "Aorta");
468 Remove_Structures("8", "Esophagus");
469 Remove_Structures("8", "VertebralBody");
472 StopCurrentStep<MaskImageType>(m_Working_Support);
473 m_ListOfStations["8"] = m_Working_Support;
476 //--------------------------------------------------------------------
479 //--------------------------------------------------------------------
480 template <class TImageType>
482 clitk::ExtractLymphStationsFilter<TImageType>::
483 FindExtremaPointsInBronchus(MaskImagePointer input,
485 double distance_max_from_center_point,
486 ListOfPointsType & LR,
487 ListOfPointsType & Ant,
488 ListOfPointsType & Post)
491 // Other solution ==> with auto bounding box ! (but pb to prevent to
492 // be too distant from the center point
495 std::vector<typename MaskSliceType::Pointer> slices;
496 clitk::ExtractSlices<MaskImageType>(input, 2, slices);
500 for(uint i=0; i<slices.size(); i++) {
503 slices[i] = Labelize<MaskSliceType>(slices[i], 0, true, 10);
504 slices[i] = KeepLabels<MaskSliceType>(slices[i],
505 GetBackgroundValue(),
506 GetForegroundValue(), 1, 1, true);
509 // ------- Find rightmost or leftmost point -------
510 MaskSliceType::PointType LRMost;
512 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
513 GetBackgroundValue(),
515 (direction==0?false:true), // right or left according to direction
517 // ------- Find postmost point -------
518 MaskSliceType::PointType postMost;
520 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
521 GetBackgroundValue(),
523 distance_max_from_center_point,
525 // ------- Find antmost point -------
526 MaskSliceType::PointType antMost;
528 clitk::FindExtremaPointInAGivenDirection<MaskSliceType>(slices[i],
529 GetBackgroundValue(),
531 distance_max_from_center_point,
533 // Only add point if found
535 // ------- Convert 2D to 3D points --------
536 MaskImageType::PointType p;
537 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(LRMost, input, i, p);
539 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(antMost, input, i, p);
541 clitk::PointsUtils<MaskImageType>::Convert2DTo3D(postMost, input, i, p);
546 //--------------------------------------------------------------------