4 #include <vtkAppendPolyData.h>
5 #include <vtkPolyDataWriter.h>
6 #include <vtkCellArray.h>
9 #include "clitkMeshToBinaryImageFilter.h"
12 #include <itkImageDuplicator.h>
14 //--------------------------------------------------------------------
15 template <class ImageType>
17 clitk::ExtractLymphStationsFilter<ImageType>::
18 ExtractStation_2RL_SetDefaultValues()
20 SetFuzzyThreshold("2RL", "CommonCarotidArtery", 0.7);
21 SetFuzzyThreshold("2RL", "BrachioCephalicArtery", 0.7);
22 SetFuzzyThreshold("2RL", "BrachioCephalicVein", 0.3);
23 SetFuzzyThreshold("2RL", "Aorta", 0.7);
24 SetFuzzyThreshold("2RL", "SubclavianArteryRight", 0.5);
25 SetFuzzyThreshold("2RL", "SubclavianArteryLeft", 0.8);
27 //--------------------------------------------------------------------
30 //--------------------------------------------------------------------
31 template <class TImageType>
33 clitk::ExtractLymphStationsFilter<TImageType>::
36 if ((!CheckForStation("2R")) && (!CheckForStation("2L"))) return;
38 ExtractStation_2RL_SI_Limits();
39 ExtractStation_2RL_Post_Limits();
41 ExtractStation_2RL_Ant_Limits2();
42 //ExtractStation_2RL_Ant_Limits();
44 ExtractStation_2RL_LR_Limits();
45 ExtractStation_2RL_Remove_Structures();
46 ExtractStation_2RL_SeparateRL();
48 // Store image filenames into AFDB
49 writeImage<MaskImageType>(m_ListOfStations["2R"], "seg/Station2R.mhd");
50 writeImage<MaskImageType>(m_ListOfStations["2L"], "seg/Station2L.mhd");
51 GetAFDB()->SetImageFilename("Station2R", "seg/Station2R.mhd");
52 GetAFDB()->SetImageFilename("Station2L", "seg/Station2L.mhd");
56 //--------------------------------------------------------------------
59 //--------------------------------------------------------------------
60 template <class ImageType>
62 clitk::ExtractLymphStationsFilter<ImageType>::
63 ExtractStation_2RL_SI_Limits()
65 // Apex of the chest or Sternum & Carina.
66 StartNewStep("[Station 2RL] Inf/Sup limits with Sternum and TopOfAorticArch/CaudalMarginOfLeftBrachiocephalicVein");
68 /* Rod says: "For the inferior border, unlike in Atlas – UM, there
69 is now a difference between 2R and 2L. 2R stops at the
70 intersection of the caudal margin of the innominate vein with the
71 trachea. 2L extends less inferiorly to the superior border of the
74 /* Get TopOfAorticArch and CaudalMarginOfLeftBrachiocephalicVein
75 - TopOfAorticArch -> can be obtain from Aorta -> most sup part.
76 - CaudalMarginOfLeftBrachiocephalicVein -> must inf part of BrachicephalicVein
78 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
79 MaskImagePointType p = Aorta->GetOrigin(); // initialise to avoid warning
80 clitk::FindExtremaPointInAGivenDirection<MaskImageType>(Aorta, GetBackgroundValue(), 2, false, p);
81 double TopOfAorticArchZ=p[2];
82 GetAFDB()->SetDouble("TopOfAorticArchZ", TopOfAorticArchZ);
84 MaskImagePointer BrachioCephalicVein = GetAFDB()->template GetImage<MaskImageType>("BrachioCephalicVein");
85 clitk::FindExtremaPointInAGivenDirection<MaskImageType>(BrachioCephalicVein, GetBackgroundValue(), 2, true, p);
86 double CaudalMarginOfLeftBrachiocephalicVeinZ=p[2];
87 GetAFDB()->SetDouble("CaudalMarginOfLeftBrachiocephalicVeinZ", CaudalMarginOfLeftBrachiocephalicVeinZ);
89 // First, cut on the most inferior part. Add one slice because this
90 // inf slice should not be included.
91 double inf = std::min(CaudalMarginOfLeftBrachiocephalicVeinZ, TopOfAorticArchZ) + m_Working_Support->GetSpacing()[2];
93 // Get Sternum and search for the upper position
94 MaskImagePointer Sternum = GetAFDB()->template GetImage<MaskImageType>("Sternum");
96 // Search most sup point
97 clitk::FindExtremaPointInAGivenDirection<MaskImageType>(Sternum, GetBackgroundValue(), 2, false, p);
98 double m_SternumZ = p[2];
99 // +Sternum->GetSpacing()[2]; // One more slice, because it is below this point // NOT HERE ?? WHY DIFFERENT FROM 3A ??
103 clitk::CropImageAlongOneAxis<MaskImageType>(m_Working_Support, 2,
104 inf, m_SternumZ, true,
105 GetBackgroundValue());
107 StopCurrentStep<MaskImageType>(m_Working_Support);
108 m_ListOfStations["2R"] = m_Working_Support;
109 m_ListOfStations["2L"] = m_Working_Support;
111 //--------------------------------------------------------------------
114 //--------------------------------------------------------------------
115 template <class ImageType>
117 clitk::ExtractLymphStationsFilter<ImageType>::
118 ExtractStation_2RL_Ant_Limits()
120 // -----------------------------------------------------
121 /* Rod says: "The anterior border, as with the Atlas – UM, is
122 posterior to the vessels (right subclavian vein, left
123 brachiocephalic vein, right brachiocephalic vein, left subclavian
124 artery, left common carotid artery and brachiocephalic trunk).
125 These vessels are not included in the nodal station. The anterior
126 border is drawn to the midpoint of the vessel and an imaginary
127 line joins the middle of these vessels. Between the vessels,
128 station 2 is in contact with station 3a." */
130 // -----------------------------------------------------
131 StartNewStep("[Station 2RL] Ant limits with CommonCarotidArtery");
133 // Get CommonCarotidArtery
134 MaskImagePointer CommonCarotidArtery = GetAFDB()->template GetImage<MaskImageType>("CommonCarotidArtery");
136 // Remove Ant to CommonCarotidArtery
137 typedef SliceBySliceRelativePositionFilter<MaskImageType> SliceRelPosFilterType;
138 typename SliceRelPosFilterType::Pointer sliceRelPosFilter = SliceRelPosFilterType::New();
139 sliceRelPosFilter->VerboseStepFlagOff();
140 sliceRelPosFilter->WriteStepFlagOff();
141 sliceRelPosFilter->SetInput(m_Working_Support);
142 sliceRelPosFilter->SetInputObject(CommonCarotidArtery);
143 sliceRelPosFilter->SetDirection(2);
144 sliceRelPosFilter->SetFuzzyThreshold(GetFuzzyThreshold("2RL", "CommonCarotidArtery"));
145 sliceRelPosFilter->AddOrientationTypeString("NotAntTo");
146 sliceRelPosFilter->IntermediateSpacingFlagOn();
147 sliceRelPosFilter->SetIntermediateSpacing(2);
148 sliceRelPosFilter->UniqueConnectedComponentBySliceOff();
149 sliceRelPosFilter->UseASingleObjectConnectedComponentBySliceFlagOff();
150 sliceRelPosFilter->AutoCropFlagOn();
151 sliceRelPosFilter->IgnoreEmptySliceObjectFlagOn();
152 sliceRelPosFilter->RemoveObjectFlagOff();
153 sliceRelPosFilter->Update();
154 m_Working_Support = sliceRelPosFilter->GetOutput();
157 StopCurrentStep<MaskImageType>(m_Working_Support);
158 m_ListOfStations["2R"] = m_Working_Support;
159 m_ListOfStations["2L"] = m_Working_Support;
161 // -----------------------------------------------------
162 // Remove Ant to H line from the Ant most part of the
163 // CommonCarotidArtery until we reach the first slice of
164 // BrachioCephalicArtery
165 StartNewStep("[Station 2RL] Ant limits with CommonCarotidArtery, H line");
167 // First, find the first slice of BrachioCephalicArtery
168 MaskImagePointer BrachioCephalicArtery = GetAFDB()->template GetImage<MaskImageType>("BrachioCephalicArtery");
169 MaskImagePointType p = BrachioCephalicArtery->GetOrigin(); // initialise to avoid warning
170 clitk::FindExtremaPointInAGivenDirection<MaskImageType>(BrachioCephalicArtery, GetBackgroundValue(), 2, false, p);
171 double TopOfBrachioCephalicArteryZ=p[2] + BrachioCephalicArtery->GetSpacing()[2]; // Add one slice
173 // Remove CommonCarotidArtery below this point
174 CommonCarotidArtery = clitk::CropImageRemoveLowerThan<MaskImageType>(CommonCarotidArtery, 2, TopOfBrachioCephalicArteryZ, true, GetBackgroundValue());
176 // Find most Ant points
177 std::vector<MaskImagePointType> ccaAntPositionA;
178 std::vector<MaskImagePointType> ccaAntPositionB;
179 clitk::SliceBySliceBuildLineSegmentAccordingToExtremaPosition<MaskImageType>(CommonCarotidArtery,
180 GetBackgroundValue(), 2,
182 0, // Horizontal line
186 // Cut ant to this line
187 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
190 GetBackgroundValue(), 1, 10);
193 StopCurrentStep<MaskImageType>(m_Working_Support);
194 m_ListOfStations["2R"] = m_Working_Support;
195 m_ListOfStations["2L"] = m_Working_Support;
197 // -----------------------------------------------------
198 // Ant limit with the BrachioCephalicArtery
199 StartNewStep("[Station 2RL] Ant limits with BrachioCephalicArtery line");
201 // Remove Ant to BrachioCephalicArtery
203 clitk::SliceBySliceRelativePosition<MaskImageType>(m_Working_Support, BrachioCephalicArtery, 2,
204 GetFuzzyThreshold("2RL", "BrachioCephalicArtery"), "NotAntTo", false, 2, true, false);
206 StopCurrentStep<MaskImageType>(m_Working_Support);
207 m_ListOfStations["2R"] = m_Working_Support;
208 m_ListOfStations["2L"] = m_Working_Support;
210 // -----------------------------------------------------
211 // Ant limit with the BrachioCephalicArtery H line
212 StartNewStep("[Station 2RL] Ant limits with BrachioCephalicArtery, Horizontal line");
214 // Find most Ant points
215 std::vector<MaskImagePointType> bctAntPositionA;
216 std::vector<MaskImagePointType> bctAntPositionB;
217 clitk::SliceBySliceBuildLineSegmentAccordingToExtremaPosition<MaskImageType>(BrachioCephalicArtery,
218 GetBackgroundValue(), 2,
220 0, // Horizontal line
224 // Cut ant to this line
225 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
228 GetBackgroundValue(), 1, 10);
230 StopCurrentStep<MaskImageType>(m_Working_Support);
231 m_ListOfStations["2R"] = m_Working_Support;
232 m_ListOfStations["2L"] = m_Working_Support;
234 // -----------------------------------------------------
235 // Ant limit with the BrachioCephalicVein
236 StartNewStep("[Station 2RL] Ant limits with BrachioCephalicVein");
238 // Remove Ant to BrachioCephalicVein
239 MaskImagePointer BrachioCephalicVein = GetAFDB()->template GetImage<MaskImageType>("BrachioCephalicVein");
241 clitk::SliceBySliceRelativePosition<MaskImageType>(m_Working_Support, BrachioCephalicVein, 2,
242 GetFuzzyThreshold("2RL", "BrachioCephalicVein"), "NotAntTo", false, 2, true, false);
244 StopCurrentStep<MaskImageType>(m_Working_Support);
245 m_ListOfStations["2R"] = m_Working_Support;
246 m_ListOfStations["2L"] = m_Working_Support;
248 //--------------------------------------------------------------------
251 //--------------------------------------------------------------------
252 template <class ImageType>
254 clitk::ExtractLymphStationsFilter<ImageType>::
255 ExtractStation_2RL_Ant_Limits2()
257 // -----------------------------------------------------
258 StartNewStep("[Station 2RL] Ant limits with vessels centroids");
260 // WARNING, as I used "And" after, empty slice in binarizedContour
261 // lead to remove part of the support, although we want to keep
262 // unchanged. So we decide to ResizeImageLike but pad with
263 // ForegroundValue instead of BG
265 // Get or compute the binary mask that separate Ant/Post part
266 // according to vessels
267 MaskImagePointer binarizedContour = FindAntPostVessels2();
268 binarizedContour = clitk::ResizeImageLike<MaskImageType>(binarizedContour,
270 GetForegroundValue());
272 // remove from support
273 typedef clitk::BooleanOperatorLabelImageFilter<MaskImageType> BoolFilterType;
274 typename BoolFilterType::Pointer boolFilter = BoolFilterType::New();
275 boolFilter->InPlaceOn();
276 boolFilter->SetInput1(m_Working_Support);
277 boolFilter->SetInput2(binarizedContour);
278 boolFilter->SetBackgroundValue1(GetBackgroundValue());
279 boolFilter->SetBackgroundValue2(GetBackgroundValue());
280 boolFilter->SetOperationType(BoolFilterType::And);
281 boolFilter->Update();
282 m_Working_Support = boolFilter->GetOutput();
285 StopCurrentStep<MaskImageType>(m_Working_Support);
286 m_ListOfStations["2R"] = m_Working_Support;
287 m_ListOfStations["2L"] = m_Working_Support;
289 //--------------------------------------------------------------------
292 //--------------------------------------------------------------------
293 template <class ImageType>
295 clitk::ExtractLymphStationsFilter<ImageType>::
296 ExtractStation_2RL_Post_Limits()
298 StartNewStep("[Station 2RL] Post limits with post wall of Trachea");
301 MaskImagePointer Trachea = GetAFDB()->template GetImage<MaskImageType>("Trachea");
303 // Resize like the current support (to have the same number of slices)
304 Trachea = clitk::ResizeImageLike<MaskImageType>(Trachea, m_Working_Support, GetBackgroundValue());
306 // Find extrema post positions
307 std::vector<MaskImagePointType> tracheaPostPositionsA;
308 std::vector<MaskImagePointType> tracheaPostPositionsB;
309 clitk::SliceBySliceBuildLineSegmentAccordingToExtremaPosition<MaskImageType>(Trachea,
310 GetBackgroundValue(), 2,
312 0, // Horizontal line
314 tracheaPostPositionsA,
315 tracheaPostPositionsB);
316 // Cut post to this line
317 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
318 tracheaPostPositionsA,
319 tracheaPostPositionsB,
320 GetBackgroundValue(), 1, -10);
322 StopCurrentStep<MaskImageType>(m_Working_Support);
323 m_ListOfStations["2R"] = m_Working_Support;
324 m_ListOfStations["2L"] = m_Working_Support;
326 //--------------------------------------------------------------------
329 //--------------------------------------------------------------------
330 // Build a vtk mesh from a list of slice number/closed-contours
331 template <class ImageType>
332 vtkSmartPointer<vtkPolyData>
333 clitk::ExtractLymphStationsFilter<ImageType>::
334 Build3DMeshFrom2DContour(const std::vector<ImagePointType> & points)
336 // create a contour, polydata.
337 vtkSmartPointer<vtkPolyData> mesh = vtkSmartPointer<vtkPolyData>::New();
338 mesh->Allocate(); //for cell structures
339 mesh->SetPoints(vtkPoints::New());
341 int point_number = points.size();
342 for (unsigned int i=0; i<points.size(); i++) {
343 mesh->GetPoints()->InsertNextPoint(points[i][0],points[i][1],points[i][2]);
345 ids[1]=(ids[0]+1)%point_number; //0-1,1-2,...,n-1-0
346 mesh->GetLines()->InsertNextCell(2,ids);
351 //--------------------------------------------------------------------
354 //--------------------------------------------------------------------
355 template <class ImageType>
357 clitk::ExtractLymphStationsFilter<ImageType>::
358 ExtractStation_2RL_LR_Limits()
360 // ---------------------------------------------------------------------------
361 StartNewStep("[Station 2RL] Left/Right limits with Aorta");
362 MaskImagePointer Aorta = GetAFDB()->template GetImage<MaskImageType>("Aorta");
363 // DD(GetFuzzyThreshold("2RL", "BrachioCephalicVein"));
365 clitk::SliceBySliceRelativePosition<MaskImageType>(m_Working_Support, Aorta, 2,
366 GetFuzzyThreshold("2RL", "Aorta"),
367 "RightTo", false, 2, true, false);
369 StopCurrentStep<MaskImageType>(m_Working_Support);
370 m_ListOfStations["2R"] = m_Working_Support;
371 m_ListOfStations["2L"] = m_Working_Support;
373 // ---------------------------------------------------------------------------
374 StartNewStep("[Station 2RL] Left/Right limits with SubclavianArtery (Right)");
376 // SliceBySliceRelativePosition + select CCL most at Right
377 MaskImagePointer SubclavianArtery = GetAFDB()->template GetImage<MaskImageType>("SubclavianArtery");
378 typedef SliceBySliceRelativePositionFilter<MaskImageType> SliceRelPosFilterType;
379 typename SliceRelPosFilterType::Pointer sliceRelPosFilter = SliceRelPosFilterType::New();
380 sliceRelPosFilter->VerboseStepFlagOff();
381 sliceRelPosFilter->WriteStepFlagOff();
382 sliceRelPosFilter->SetInput(m_Working_Support);
383 sliceRelPosFilter->SetInputObject(SubclavianArtery);
384 sliceRelPosFilter->SetDirection(2);
385 sliceRelPosFilter->SetFuzzyThreshold(GetFuzzyThreshold("2RL", "SubclavianArteryRight"));
386 sliceRelPosFilter->AddOrientationTypeString("NotRightTo");
387 sliceRelPosFilter->IntermediateSpacingFlagOn();
388 sliceRelPosFilter->SetIntermediateSpacing(2);
389 sliceRelPosFilter->UniqueConnectedComponentBySliceOff();
390 sliceRelPosFilter->UseASingleObjectConnectedComponentBySliceFlagOff();
392 sliceRelPosFilter->CCLSelectionFlagOn(); // select one CCL by slice
393 sliceRelPosFilter->SetCCLSelectionDimension(0); // select according to X (0) axis
394 sliceRelPosFilter->SetCCLSelectionDirection(-1); // select most at Right
395 sliceRelPosFilter->CCLSelectionIgnoreSingleCCLFlagOn(); // ignore if only one CCL
397 sliceRelPosFilter->AutoCropFlagOn();
398 sliceRelPosFilter->IgnoreEmptySliceObjectFlagOn();
399 sliceRelPosFilter->RemoveObjectFlagOff();
400 sliceRelPosFilter->Update();
401 m_Working_Support = sliceRelPosFilter->GetOutput();
404 StopCurrentStep<MaskImageType>(m_Working_Support);
405 m_ListOfStations["2R"] = m_Working_Support;
406 m_ListOfStations["2L"] = m_Working_Support;
409 // ---------------------------------------------------------------------------
410 StartNewStep("[Station 2RL] Left/Right limits with SubclavianArtery (Left)");
412 // SliceBySliceRelativePosition + select CCL most at Right
413 sliceRelPosFilter = SliceRelPosFilterType::New();
414 sliceRelPosFilter->VerboseStepFlagOff();
415 sliceRelPosFilter->WriteStepFlagOff();
416 sliceRelPosFilter->SetInput(m_Working_Support);
417 sliceRelPosFilter->SetInputObject(SubclavianArtery);
418 sliceRelPosFilter->SetDirection(2);
419 sliceRelPosFilter->SetFuzzyThreshold(GetFuzzyThreshold("2RL", "SubclavianArteryLeft"));
420 sliceRelPosFilter->AddOrientationTypeString("NotLeftTo");
421 sliceRelPosFilter->IntermediateSpacingFlagOn();
422 sliceRelPosFilter->SetIntermediateSpacing(2);
423 sliceRelPosFilter->UniqueConnectedComponentBySliceOff();
424 sliceRelPosFilter->UseASingleObjectConnectedComponentBySliceFlagOff();
426 sliceRelPosFilter->CCLSelectionFlagOn(); // select one CCL by slice
427 sliceRelPosFilter->SetCCLSelectionDimension(0); // select according to X (0) axis
428 sliceRelPosFilter->SetCCLSelectionDirection(+1); // select most at Left
429 sliceRelPosFilter->CCLSelectionIgnoreSingleCCLFlagOff(); // do not ignore if only one CCL
431 sliceRelPosFilter->AutoCropFlagOn();
432 sliceRelPosFilter->IgnoreEmptySliceObjectFlagOn();
433 sliceRelPosFilter->RemoveObjectFlagOff();
434 sliceRelPosFilter->Update();
435 m_Working_Support = sliceRelPosFilter->GetOutput();
438 StopCurrentStep<MaskImageType>(m_Working_Support);
439 m_ListOfStations["2R"] = m_Working_Support;
440 m_ListOfStations["2L"] = m_Working_Support;
442 //--------------------------------------------------------------------
444 //--------------------------------------------------------------------
445 template <class ImageType>
447 clitk::ExtractLymphStationsFilter<ImageType>::
448 ExtractStation_2RL_Remove_Structures()
450 Remove_Structures("2RL", "BrachioCephalicVein");
451 Remove_Structures("2RL", "CommonCarotidArtery");
452 Remove_Structures("2RL", "SubclavianArtery");
453 Remove_Structures("2RL", "Thyroid");
454 Remove_Structures("2RL", "Aorta");
457 StopCurrentStep<MaskImageType>(m_Working_Support);
458 m_ListOfStations["2R"] = m_Working_Support;
459 m_ListOfStations["2L"] = m_Working_Support;
461 //--------------------------------------------------------------------
464 //--------------------------------------------------------------------
465 template <class ImageType>
467 clitk::ExtractLymphStationsFilter<ImageType>::
468 ExtractStation_2RL_SeparateRL()
470 // ---------------------------------------------------------------------------
471 StartNewStep("[Station 2RL] Separate 2R/2L according to Trachea");
475 "For station 2 there is a shift, dividing 2R from 2L, from midline
476 to the left paratracheal border."
479 - Consider Trachea SliceBySlice
480 - find extrema at Left
481 - add margins towards Right
482 - remove what is at Left of this line
486 MaskImagePointer Trachea = GetAFDB()->template GetImage<MaskImageType>("Trachea");
488 // Resize like the current support (to have the same number of slices)
489 Trachea = clitk::ResizeImageLike<MaskImageType>(Trachea, m_Working_Support, GetBackgroundValue());
491 // Find extrema post positions
492 std::vector<MaskImagePointType> tracheaLeftPositionsA;
493 std::vector<MaskImagePointType> tracheaLeftPositionsB;
494 clitk::SliceBySliceBuildLineSegmentAccordingToExtremaPosition<MaskImageType>(Trachea,
495 GetBackgroundValue(), 2,
499 tracheaLeftPositionsA,
500 tracheaLeftPositionsB);
501 // Copy support for R and L
502 typedef itk::ImageDuplicator<MaskImageType> DuplicatorType;
503 DuplicatorType::Pointer duplicator = DuplicatorType::New();
504 duplicator->SetInputImage(m_Working_Support);
505 duplicator->Update();
506 MaskImageType::Pointer m_Working_Support2 = duplicator->GetOutput();
508 // Cut post to this line for Right part
509 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support,
510 tracheaLeftPositionsA,
511 tracheaLeftPositionsB,
512 GetBackgroundValue(), 0, -10);
513 writeImage<MaskImageType>(m_Working_Support, "R.mhd");
515 // Cut post to this line for Left part
516 clitk::SliceBySliceSetBackgroundFromLineSeparation<MaskImageType>(m_Working_Support2,
517 tracheaLeftPositionsA,
518 tracheaLeftPositionsB,
519 GetBackgroundValue(), 0, +10);
520 writeImage<MaskImageType>(m_Working_Support2, "L.mhd");
523 StopCurrentStep<MaskImageType>(m_Working_Support);
524 m_ListOfStations["2R"] = m_Working_Support;
525 m_ListOfStations["2L"] = m_Working_Support2;
527 //--------------------------------------------------------------------