PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusLineSegmentationAlgo.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2 Program: Plus
3 Copyright (c) Laboratory for Percutaneous Surgery. All rights reserved.
4 See License.txt for details.
5 =========================================================Plus=header=end*/
6 
7 // Local includes
8 #include "PlusConfigure.h"
9 #include "igsioTrackedFrame.h"
10 #include "vtkIGSIOTrackedFrameList.h"
11 
12 // Utility includes
13 #include <PlaneParametersEstimator.h>
14 #include <RANSAC.h>
15 
16 // ITK includes
17 #include <itkBinaryThresholdImageFilter.h>
18 #include <itkImageDuplicator.h>
19 #include <itkImageFileWriter.h>
20 #include <itkImageRegionIterator.h>
21 #include <itkLineIterator.h>
22 #include <itkOtsuThresholdImageFilter.h>
23 #include <itkRGBPixel.h>
24 #include <itkResampleImageFilter.h>
25 #include <itkRescaleIntensityImageFilter.h>
26 
27 // VTK includes
28 #include <vtkChartXY.h>
29 #include <vtkContextScene.h>
30 #ifdef PLUS_RENDERING_ENABLED
31 #include <vtkContextView.h>
32 #endif
33 #include <vtkDoubleArray.h>
34 #include <vtkIntArray.h>
35 #include <vtkObjectFactory.h>
36 #include <vtkPen.h>
37 #include <vtkPlot.h>
39 #include <vtkRenderWindow.h>
40 #include <vtkRenderWindowInteractor.h>
41 #include <vtkRenderer.h>
42 #include <vtkTable.h>
43 
44 static const double INTESNITY_THRESHOLD_PERCENTAGE_OF_PEAK = 0.5; // threshold (as the percentage of the peak intensity along a scanline) for COG
45 static const double MAX_CONSECUTIVE_INVALID_VIDEO_FRAMES = 10; // the maximum number of consecutive invalid frames before warning message issued
46 static const double MAX_PERCENTAGE_OF_INVALID_VIDEO_FRAMES = 0.1; // the maximum percentage of the invalid frames before warning message issued
47 static const double MIN_X_SLOPE_COMPONENT_FOR_DETECTED_LINE = 0.01; // if the detected line's slope's x-component is less than this (i.e. almost vertical), skip frame
48 static const int MINIMUM_NUMBER_OF_VALID_SCANLINES = 5; // minimum number of valid scanlines to compute line position
49 static const int NUMBER_OF_SCANLINES = 40; // number of scan-lines for line detection
50 static const unsigned int DIMENSION = 2; // dimension of video frames (used for Ransac plane)
51 static const double EXPECTED_LINE_SEGMENTATION_SUCCESS_RATE = 0.5; // log a warning if the actual line segmentation success rate (fraction of frames where the line segmentation was successful) is below this threshold
52 
54 {
57 };
59 
61 
62 //----------------------------------------------------------------------------
63 void vtkPlusLineSegmentationAlgo::PrintSelf(ostream& os, vtkIndent indent)
64 {
65  this->Superclass::PrintSelf(os, indent);
66 }
67 
68 //----------------------------------------------------------------------------
70  : m_TrackedFrameList(vtkSmartPointer<vtkIGSIOTrackedFrameList>::New())
71  , m_SaveIntermediateImages(false)
72  , IntermediateFilesOutputDirectory("")
73  , PlotIntensityProfile(false)
74  , m_SignalTimeRangeMin(0.0)
75  , m_SignalTimeRangeMax(-1.0)
76 {
77  m_ClipRectangleOrigin[0] = 0;
78  m_ClipRectangleOrigin[1] = 0;
79  m_ClipRectangleSize[0] = 0;
80  m_ClipRectangleSize[1] = 0;
81 }
82 
83 //----------------------------------------------------------------------------
85 {
86 }
87 
88 //-----------------------------------------------------------------------------
89 void vtkPlusLineSegmentationAlgo::SetTrackedFrameList(vtkIGSIOTrackedFrameList& aTrackedFrameList)
90 {
91  m_TrackedFrameList->Clear();
92  m_TrackedFrameList->AddTrackedFrameList(&aTrackedFrameList);
93 }
94 
95 //----------------------------------------------------------------------------
96 void vtkPlusLineSegmentationAlgo::SetTrackedFrame(igsioTrackedFrame& aTrackedFrame)
97 {
98  m_TrackedFrameList->Clear();
99  m_TrackedFrameList->AddTrackedFrame(&aTrackedFrame);
100 }
101 
102 //-----------------------------------------------------------------------------
103 void vtkPlusLineSegmentationAlgo::SetSignalTimeRange(double rangeMin, double rangeMax)
104 {
105  m_SignalTimeRangeMin = rangeMin;
106  m_SignalTimeRangeMax = rangeMax;
107 }
108 
109 //-----------------------------------------------------------------------------
111 {
112  m_SaveIntermediateImages = saveIntermediateImages;
113 }
114 
115 //-----------------------------------------------------------------------------
117 {
118  IntermediateFilesOutputDirectory = outputDirectory;
119 }
120 
121 //-----------------------------------------------------------------------------
123 {
124  // Make sure video frame list is not empty
125  if (m_TrackedFrameList->GetNumberOfTrackedFrames() == 0)
126  {
127  LOG_ERROR("vtkPlusLineSegmentationAlgo video input data verification failed: no frames");
128  return PLUS_FAIL;
129  }
130 
131  // Check if TrackedFrameList is MF oriented BRIGHTNESS image
132  if (vtkIGSIOTrackedFrameList::VerifyProperties(m_TrackedFrameList, US_IMG_ORIENT_MF, US_IMG_BRIGHTNESS) != PLUS_SUCCESS)
133  {
134  LOG_ERROR("vtkPlusLineSegmentationAlgo video input data verification failed: video data orientation or type is not supported (MF orientation, BRIGHTNESS type is expected)");
135  return PLUS_FAIL;
136  }
137 
138  // Check if there are enough valid consecutive video frames
139  int totalNumberOfInvalidVideoFrames = 0;
140  int greatestNumberOfConsecutiveInvalidVideoFrames = 0;
141  int currentNumberOfConsecutiveInvalidVideoFrames = 0;
142  bool signalTimeRangeDefined = (m_SignalTimeRangeMin <= m_SignalTimeRangeMax);
143  for (unsigned int i = 0 ; i < m_TrackedFrameList->GetNumberOfTrackedFrames(); ++i)
144  {
145  igsioTrackedFrame* trackedFrame = m_TrackedFrameList->GetTrackedFrame(i);
146  if (signalTimeRangeDefined && (trackedFrame->GetTimestamp() < m_SignalTimeRangeMin || trackedFrame->GetTimestamp() > m_SignalTimeRangeMax))
147  {
148  // frame is out of the specified signal range
149  continue;
150  }
151  if (!trackedFrame->GetImageData()->IsImageValid())
152  {
153  ++totalNumberOfInvalidVideoFrames;
154  ++currentNumberOfConsecutiveInvalidVideoFrames;
155  }
156  else
157  {
158  if (currentNumberOfConsecutiveInvalidVideoFrames > greatestNumberOfConsecutiveInvalidVideoFrames)
159  {
160  greatestNumberOfConsecutiveInvalidVideoFrames = currentNumberOfConsecutiveInvalidVideoFrames ;
161  }
162  currentNumberOfConsecutiveInvalidVideoFrames = 0;
163  }
164  }
165 
166  double percentageOfInvalidVideoFrames = totalNumberOfInvalidVideoFrames / static_cast<double>(m_TrackedFrameList->GetNumberOfTrackedFrames());
167  if (percentageOfInvalidVideoFrames > MAX_PERCENTAGE_OF_INVALID_VIDEO_FRAMES)
168  {
169  LOG_WARNING("In vtkPlusLineSegmentationAlgo " << 100 * percentageOfInvalidVideoFrames << "% of the video frames were invalid. This warning " <<
170  "gets issued whenever more than " << 100 * MAX_PERCENTAGE_OF_INVALID_VIDEO_FRAMES << "% of the video frames are invalid because the " <<
171  "accuracy of the computed time offset may be marginalised");
172  }
173 
174  if (greatestNumberOfConsecutiveInvalidVideoFrames > MAX_CONSECUTIVE_INVALID_VIDEO_FRAMES)
175  {
176  LOG_WARNING("In vtkPlusLineSegmentationAlgo there were " << greatestNumberOfConsecutiveInvalidVideoFrames << " invalid video frames in a row. This warning " <<
177  "gets issued whenever there are more than " << MAX_CONSECUTIVE_INVALID_VIDEO_FRAMES << " invalid frames in a row because the " <<
178  "accuracy of the computed time offset may be marginalised");
179  }
180 
181  return PLUS_SUCCESS;
182 }
183 
184 //-----------------------------------------------------------------------------
186 {
187  m_SignalValues.clear();
188  m_SignalTimestamps.clear();
189 
190  LineParameters nonDetectedLineParams;
191  nonDetectedLineParams.lineDetected = false;
192  nonDetectedLineParams.lineOriginPoint_Image[0] = 0;
193  nonDetectedLineParams.lineOriginPoint_Image[1] = 0;
194  nonDetectedLineParams.lineDirectionVector_Image[0] = 0;
195  nonDetectedLineParams.lineDirectionVector_Image[1] = 1;
196  m_LineParameters.assign(m_TrackedFrameList->GetNumberOfTrackedFrames(), nonDetectedLineParams);
197 
198  // For each video frame, detect line and extract mindpoint and slope parameters
199  int numberOfSuccessfulLineSegmentations = 0;
200  bool signalTimeRangeDefined = (m_SignalTimeRangeMin <= m_SignalTimeRangeMax);
201  for (unsigned int frameNumber = 0; frameNumber < m_TrackedFrameList->GetNumberOfTrackedFrames(); ++frameNumber)
202  {
203  LOG_TRACE("Calculating video position metric for frame " << frameNumber);
204  igsioTrackedFrame* trackedFrame = m_TrackedFrameList->GetTrackedFrame(frameNumber);
205  if (signalTimeRangeDefined && (trackedFrame->GetTimestamp() < m_SignalTimeRangeMin || trackedFrame->GetTimestamp() > m_SignalTimeRangeMax))
206  {
207  // frame is out of the specified signal range
208  LOG_TRACE("Skip frame, it is out of the valid signal range");
209  continue;
210  }
211 
212  // Get current image
213  if (trackedFrame->GetImageData()->GetVTKScalarPixelType() != VTK_UNSIGNED_CHAR)
214  {
215  LOG_ERROR("vtkPlusLineSegmentationAlgo::ComputeVideoPositionMetric only supports 8-bit images");
216  continue;
217  }
218  auto localImage = CharImageType::New();
219  PlusCommon::DeepCopyVtkVolumeToItkImage<CharPixelType>(trackedFrame->GetImageData()->GetImage(), localImage);
220  if (localImage.IsNull())
221  {
222  // Dropped frame
223  LOG_ERROR("vtkPlusLineSegmentationAlgo::ComputeVideoPositionMetric failed to retrieve image data from frame");
224  continue;
225  }
226 
227  // Create an image duplicator to copy the original image
228  typedef itk::ImageDuplicator<CharImageType> DuplicatorType;
229  auto duplicator = DuplicatorType::New();
230  CharImageType::Pointer scanlineImage;
231  if (m_SaveIntermediateImages == true)
232  {
233  duplicator->SetInputImage(localImage);
234  duplicator->Update();
235 
236  // Create an image copy to draw the scanlines on
237  scanlineImage = duplicator->GetOutput();
238  }
239 
240  std::vector<itk::Point<double, 2> > intensityPeakPositions;
241  CharImageType::RegionType region = localImage->GetLargestPossibleRegion();
242  LimitToClipRegion(region);
243 
244  int numOfValidScanlines = 0;
245 
246  for (int currScanlineNum = 0; currScanlineNum < NUMBER_OF_SCANLINES; ++currScanlineNum)
247  {
248  // Set the scanline start pixel
249  CharImageType::IndexType startPixel;
250  double scanlineSpacingPix = static_cast<double>(region.GetSize()[0] - 1) / (NUMBER_OF_SCANLINES - 1);
251  startPixel[0] = region.GetIndex()[0] + scanlineSpacingPix * (currScanlineNum);
252  startPixel[1] = region.GetIndex()[1];
253 
254  // Set the scanline end pixel
255  CharImageType::IndexType endPixel;
256  endPixel[0] = startPixel[0];
257  endPixel[1] = startPixel[1] + region.GetSize()[1] - 1;
258 
259  std::deque<int> intensityProfile; // Holds intensity profile of the line
260  itk::LineIterator<CharImageType> it(localImage, startPixel, endPixel);
261  it.GoToBegin();
262 
263  itk::LineIterator<CharImageType>* itScanlineImage = NULL;
264  if (m_SaveIntermediateImages == true)
265  {
266  // Iterator for the scanline image copy
267  // it's time-consuming to instantiate this iterator, so only do it if intermediate image saving is requested
268  itScanlineImage = new itk::LineIterator<CharImageType>(scanlineImage, startPixel, endPixel);
269  itScanlineImage->GoToBegin();
270  }
271 
272  while (!it.IsAtEnd())
273  {
274  intensityProfile.push_back((int)it.Get());
275  if (m_SaveIntermediateImages == true)
276  {
277  // Set the pixels on the scanline image copy to white
278  itScanlineImage->Set(255);
279  ++(*itScanlineImage);
280  }
281  ++it;
282  }
283 
284  // Delete the iterator declared with new()
285  if (itScanlineImage != NULL)
286  {
287  delete itScanlineImage;
288  itScanlineImage = NULL;
289  }
290 
291  if (this->PlotIntensityProfile)
292  {
293  // Plot the intensity profile
294  PlotIntArray(intensityProfile);
295  }
296 
297  // Find the max intensity value from the peak with the largest area
298  int maxFromLargestArea = -1;
299  int maxFromLargestAreaIndex = -1;
300  int startOfMaxArea = -1;
301  if (FindLargestPeak(intensityProfile, maxFromLargestArea, maxFromLargestAreaIndex, startOfMaxArea) == PLUS_SUCCESS)
302  {
303  double currPeakPos_y = -1;
304  switch (PEAK_POS_METRIC)
305  {
306  case PEAK_POS_COG:
307  {
308  /* Use center-of-gravity (COG) as peak-position metric*/
309  if (ComputeCenterOfGravity(intensityProfile, startOfMaxArea, currPeakPos_y) != PLUS_SUCCESS)
310  {
311  // unable to compute center-of-gravity; this scanline is invalid
312  continue;
313  }
314  break;
315  }
316  case PEAK_POS_START:
317  {
318  /* Use peak start as peak-position metric*/
319  if (FindPeakStart(intensityProfile, maxFromLargestArea, startOfMaxArea, currPeakPos_y) != PLUS_SUCCESS)
320  {
321  // unable to compute peak start; this scanline is invalid
322  continue;
323  }
324  break;
325  }
326  }
327 
328  itk::Point<double, 2> currPeakPos;
329  currPeakPos[0] = static_cast<double>(startPixel[0]);
330  currPeakPos[1] = startPixel[1] + currPeakPos_y;
331  intensityPeakPositions.push_back(currPeakPos);
332  ++numOfValidScanlines;
333 
334  } // end if() found intensity peak
335 
336  } // end currScanlineNum loop
337 
338  if (numOfValidScanlines < MINIMUM_NUMBER_OF_VALID_SCANLINES)
339  {
340  //TODO: drop the frame from the analysis
341  LOG_DEBUG("Only " << numOfValidScanlines << " valid scanlines; this is less than the required " << MINIMUM_NUMBER_OF_VALID_SCANLINES << ". Skipping frame" << frameNumber);
342  }
343 
344  LineParameters params;
345  ComputeLineParameters(intensityPeakPositions, params);
346  if (!params.lineDetected)
347  {
348  LOG_DEBUG("Unable to compute line parameters for frame " << frameNumber);
349  continue;
350  }
352  {
353  // Line is close to vertical, skip frame because intersection of
354  // line with image's horizontal half point is unstable
355  LOG_TRACE("Line on frame " << frameNumber << " is too close to vertical, skip the frame");
356  continue;
357  }
358 
359  ++numberOfSuccessfulLineSegmentations;
360  m_LineParameters[frameNumber] = params;
361 
362  // Store the y-value of the line, when the line's x-value is half of the image's width
363  double t = (region.GetIndex()[0] + 0.5 * region.GetSize()[0] - params.lineOriginPoint_Image[0]) / params.lineDirectionVector_Image[0];
364  m_SignalValues.push_back(std::abs(params.lineOriginPoint_Image[1] + t * params.lineDirectionVector_Image[1]));
365 
366  // Store timestamp for image frame
367  m_SignalTimestamps.push_back(m_TrackedFrameList->GetTrackedFrame(frameNumber)->GetTimestamp());
368 
369  if (m_SaveIntermediateImages == true)
370  {
371  SaveIntermediateImage(frameNumber, scanlineImage,
373  numOfValidScanlines, intensityPeakPositions);
374  }
375 
376  } // end frameNum loop
377 
378  double segmentationSuccessRate = double(numberOfSuccessfulLineSegmentations) / m_TrackedFrameList->GetNumberOfTrackedFrames();
379  if (segmentationSuccessRate < EXPECTED_LINE_SEGMENTATION_SUCCESS_RATE)
380  {
381  LOG_WARNING("Line segmentation success rate is very low (" << segmentationSuccessRate * 100 << "%): a line could only be detected on " << numberOfSuccessfulLineSegmentations << " frames out of " << m_TrackedFrameList->GetNumberOfTrackedFrames());
382  }
383 
384  bool plotVideoMetric = vtkPlusLogger::Instance()->GetLogLevel() >= vtkPlusLogger::LOG_LEVEL_TRACE;
385  if (plotVideoMetric)
386  {
388  }
389 
390  return PLUS_SUCCESS;
391 
392 } // End LineDetection
393 
394 //-----------------------------------------------------------------------------
395 PlusStatus vtkPlusLineSegmentationAlgo::FindPeakStart(std::deque<int>& intensityProfile, int maxFromLargestArea, int startOfMaxArea, double& startOfPeak)
396 {
397  // Start of peak is defined as the location at which it reaches 50% of its maximum value.
398  double startPeakValue = maxFromLargestArea * 0.5;
399 
400  int pixelIndex = startOfMaxArea;
401 
402  while (intensityProfile.at(pixelIndex) <= startPeakValue)
403  {
404  ++pixelIndex;
405  }
406 
407  startOfPeak = --pixelIndex;
408 
409  return PLUS_SUCCESS;
410 }
411 
412 //-----------------------------------------------------------------------------
413 PlusStatus vtkPlusLineSegmentationAlgo::FindLargestPeak(std::deque<int>& intensityProfile, int& maxFromLargestArea, int& maxFromLargestAreaIndex, int& startOfMaxArea)
414 {
415  int currentLargestArea = 0;
416  int currentArea = 0;
417  int currentMaxFromLargestArea = 0;
418  int currentMaxFromLargestAreaIndex = 0;
419  int currentMax = 0;
420  int currentMaxIndex = 0;
421  bool underPeak = false;
422  int currentStartOfMaxArea = 0;
423  int currentStart = 0;
424 
425  if (intensityProfile.size() == 0)
426  {
427  LOG_ERROR("Intensity contains no elements");
428  return PLUS_FAIL;
429  }
430 
431  double intensityMax = intensityProfile.at(0);
432  for (unsigned int pixelLoc = 1; pixelLoc < intensityProfile.size(); ++pixelLoc)
433  {
434  if (intensityProfile.at(pixelLoc) > intensityMax)
435  {
436  intensityMax = intensityProfile.at(pixelLoc);
437  }
438  }
439 
440  double peakIntensityThreshold = intensityMax * INTESNITY_THRESHOLD_PERCENTAGE_OF_PEAK;
441 
442  for (unsigned int pixelLoc = 0; pixelLoc < intensityProfile.size(); ++pixelLoc)
443  {
444  if (intensityProfile.at(pixelLoc) > peakIntensityThreshold && !underPeak)
445  {
446  // reached start of the peak
447  underPeak = true;
448  currentMax = intensityProfile.at(pixelLoc);
449  currentMaxIndex = pixelLoc;
450  currentArea = intensityProfile.at(pixelLoc);
451  currentStart = pixelLoc;
452  }
453  else if (intensityProfile.at(pixelLoc) > peakIntensityThreshold && underPeak)
454  {
455  // still under the the peak, cumulate the area
456  currentArea += intensityProfile.at(pixelLoc);
457 
458  if (intensityProfile.at(pixelLoc) > currentMax)
459  {
460  currentMax = intensityProfile.at(pixelLoc);
461  currentMaxIndex = pixelLoc;
462  }
463  }
464  else if (intensityProfile.at(pixelLoc) < peakIntensityThreshold && underPeak)
465  {
466  // exited the peak area
467  underPeak = false;
468  if (currentArea > currentLargestArea)
469  {
470  currentLargestArea = currentArea;
471  currentMaxFromLargestArea = currentMax;
472  currentMaxFromLargestAreaIndex = currentMaxIndex;
473  currentStartOfMaxArea = currentStart;
474  }
475  }
476  } //end loop through intensity profile
477 
478  maxFromLargestArea = currentMaxFromLargestArea;
479  maxFromLargestAreaIndex = currentMaxFromLargestAreaIndex;
480  startOfMaxArea = currentStartOfMaxArea;
481 
482  if (currentLargestArea == 0)
483  {
484  // No peak for this scanline
485  return PLUS_FAIL;
486  }
487 
488  return PLUS_SUCCESS;
489 }
490 //-----------------------------------------------------------------------------
491 
492 PlusStatus vtkPlusLineSegmentationAlgo::ComputeCenterOfGravity(std::deque<int>& intensityProfile, int startOfMaxArea, double& centerOfGravity)
493 {
494  if (intensityProfile.size() == 0)
495  {
496  return PLUS_FAIL;
497  }
498 
499  double intensityMax = intensityProfile.at(0);
500  for (unsigned int pixelLoc = 1; pixelLoc < intensityProfile.size(); ++pixelLoc)
501  {
502  if (intensityProfile.at(pixelLoc) > intensityMax)
503  {
504  intensityMax = intensityProfile.at(pixelLoc);
505  }
506  }
507 
508  double peakIntensityThreshold = intensityMax * INTESNITY_THRESHOLD_PERCENTAGE_OF_PEAK;
509 
510  int pixelLoc = startOfMaxArea;
511  int pointsInPeak = 0;
512  double intensitySum = 0;
513  while (intensityProfile.at(pixelLoc) > peakIntensityThreshold)
514  {
515  intensitySum += pixelLoc * intensityProfile.at(pixelLoc);
516  pointsInPeak += intensityProfile.at(pixelLoc);
517  ++pixelLoc;
518  }
519 
520  if (pointsInPeak == 0)
521  {
522  return PLUS_FAIL;
523  }
524 
525  centerOfGravity = intensitySum / pointsInPeak;
526 
527  return PLUS_SUCCESS;
528 }
529 
530 //-----------------------------------------------------------------------------
531 void vtkPlusLineSegmentationAlgo::ComputeLineParameters(std::vector<itk::Point<double, 2> >& data, LineParameters& outputParameters)
532 {
533  outputParameters.lineDetected = false;
534 
535  std::vector<double> ransacParameterResult;
536  typedef itk::PlaneParametersEstimator<DIMENSION> PlaneEstimatorType;
537  typedef itk::RANSAC<itk::Point<double, DIMENSION>, double> RANSACType;
538 
539  //create and initialize the parameter estimator
540  double maximalDistanceFromPlane = 0.5;
541  auto planeEstimator = PlaneEstimatorType::New();
542  planeEstimator->SetDelta(maximalDistanceFromPlane);
543  planeEstimator->LeastSquaresEstimate(data, ransacParameterResult);
544  if (ransacParameterResult.empty())
545  {
546  LOG_DEBUG("Unable to fit line through points with least squares estimation");
547  }
548  else
549  {
550  LOG_TRACE("Least squares line parameters (n, a):");
551  for (unsigned int i = 0; i < (2 * DIMENSION - 1); i++)
552  {
553  LOG_TRACE(" LS parameter: " << ransacParameterResult[i]);
554  }
555  }
556 
557  //create and initialize the RANSAC algorithm
558  double desiredProbabilityForNoOutliers = 0.999;
559  auto ransacEstimator = RANSACType::New();
560 
561  try
562  {
563  ransacEstimator->SetData(data);
564  }
565  catch (std::exception& e)
566  {
567  LOG_DEBUG(e.what());
568  return;
569  }
570 
571  try
572  {
573  ransacEstimator->SetParametersEstimator(planeEstimator.GetPointer());
574  }
575  catch (std::exception& e)
576  {
577  LOG_DEBUG(e.what());
578  return;
579  }
580 
581 
582  try
583  {
584  ransacEstimator->Compute(ransacParameterResult, desiredProbabilityForNoOutliers);
585  }
586  catch (std::exception& e)
587  {
588  LOG_DEBUG(e.what());
589  return;
590  }
591 
592  if (ransacParameterResult.empty())
593  {
594  LOG_DEBUG("Unable to fit line through points with RANSAC, line segmentation failed");
595  return;
596  }
597 
598  LOG_TRACE("RANSAC line fitting parameters (n, a):");
599 
600  for (unsigned int i = 0; i < (2 * DIMENSION - 1); i++)
601  {
602  LOG_TRACE(" RANSAC parameter: " << ransacParameterResult[i]);
603  }
604 
605  outputParameters.lineDetected = true;
606  outputParameters.lineDirectionVector_Image[0] = -ransacParameterResult[1];
607  outputParameters.lineDirectionVector_Image[1] = ransacParameterResult[0];
608  outputParameters.lineOriginPoint_Image[0] = ransacParameterResult[2];
609  outputParameters.lineOriginPoint_Image[1] = ransacParameterResult[3];
610 }
611 
612 //-----------------------------------------------------------------------------
613 void vtkPlusLineSegmentationAlgo::SaveIntermediateImage(int frameNumber, CharImageType::Pointer scanlineImage, double x_0, double y_0, double r_x, double r_y, int numOfValidScanlines, const std::vector<itk::Point<double, 2> >& intensityPeakPositions)
614 {
615  // The scanlineImage already contains the vertical lines, but not the segmented line.
616 
617  typedef itk::RGBPixel<unsigned char> rgbPixelType;
618  typedef itk::Image<rgbPixelType, 2> rgbImageType;
619  auto rgbImageCopy = rgbImageType::New();
620 
621  CharImageType::RegionType fullImageRegion = scanlineImage->GetLargestPossibleRegion();
622 
623  rgbImageType::IndexType start;
624  start[0] = fullImageRegion.GetIndex()[0]; // first index on X
625  start[1] = fullImageRegion.GetIndex()[0]; // first index on Y
626  rgbImageType::SizeType size;
627  size[0] = fullImageRegion.GetSize()[0]; // size along X
628  size[1] = fullImageRegion.GetSize()[1]; // size along Y
629  rgbImageType::RegionType region;
630  region.SetIndex(start);
631  region.SetSize(size);
632  rgbImageCopy->SetRegions(region);
633  rgbImageCopy->Allocate();
634 
635  // Copy grayscale image to an RGB image to allow drawing of annotations in color
636  for (unsigned int x_coord = region.GetIndex()[0]; x_coord < region.GetSize()[0]; ++x_coord)
637  {
638  for (unsigned int y_coord = region.GetIndex()[1]; y_coord < region.GetSize()[1]; ++y_coord)
639  {
640  rgbImageType::IndexType currRgbImageIndex;
641  currRgbImageIndex[0] = x_coord;
642  currRgbImageIndex[1] = y_coord;
643 
644  CharImageType::IndexType currLocalImageIndex;
645  currLocalImageIndex[0] = x_coord;
646  currLocalImageIndex[1] = y_coord;
647 
648  CharPixelType currLocalImagePixelVal = scanlineImage->GetPixel(currLocalImageIndex);
649  rgbPixelType currRgbImagePixelVal;
650  currRgbImagePixelVal.Set(currLocalImagePixelVal, currLocalImagePixelVal, currLocalImagePixelVal);
651  rgbImageCopy->SetPixel(currRgbImageIndex, currRgbImagePixelVal);
652  }
653  }
654 
655  float diag = sqrtf((float)(size[0] * size[0] + size[1] * size[1]));
656 
657  // Draw detected line
658  for (int i = static_cast<int>(-diag); i < static_cast<int>(diag); ++i)
659  {
660  rgbImageType::IndexType currIndex;
661  currIndex[0] = static_cast<int>(x_0 + i * r_x);
662  currIndex[1] = static_cast<int>(y_0 + i * r_y);
663 
664  if (fullImageRegion.IsInside(currIndex))
665  {
666  rgbPixelType currRgbImagePixelVal;
667  currRgbImagePixelVal.Set(0, 0, 255);
668  rgbImageCopy->SetPixel(currIndex, currRgbImagePixelVal);
669  }
670  }
671 
672  // Draw intensity peaks (as squares)
673  for (int scanLineIndex = 0; scanLineIndex < numOfValidScanlines; ++scanLineIndex)
674  {
675  unsigned int sqareCenterCoordX = static_cast<unsigned int>(intensityPeakPositions.at(scanLineIndex).GetElement(0));
676  unsigned int sqareCenterCoordY = static_cast<unsigned int>(intensityPeakPositions.at(scanLineIndex).GetElement(1));
677 
678  for (unsigned int x = sqareCenterCoordX - 3; x < sqareCenterCoordX + 3; ++x)
679  {
680  for (unsigned int y = sqareCenterCoordY - 3; y < sqareCenterCoordY + 3; ++y)
681  {
682  rgbImageType::IndexType currIndex;
683  currIndex[0] = x; // index on X
684  currIndex[1] = y; // index on Y
685 
686  rgbPixelType currRgbImagePixelVal;
687  currRgbImagePixelVal.Set(0, 0, 255);
688 
689  rgbImageCopy->SetPixel(currIndex, currRgbImagePixelVal);
690  }
691  }
692  }
693 
694  std::ostringstream rgbImageFilename;
696  {
697  rgbImageFilename << IntermediateFilesOutputDirectory << "/";
698  }
699  rgbImageFilename << "LineSegmentationResult_" << std::setw(3) << std::setfill('0') << frameNumber << ".png" << std::ends;
700 
701  typedef itk::ImageFileWriter<rgbImageType> rgbImageWriterType;
702  auto rgbImageWriter = rgbImageWriterType::New();
703 
704  rgbImageWriter->SetFileName(rgbImageFilename.str());
705  rgbImageWriter->SetInput(rgbImageCopy);
706  try
707  {
708  rgbImageWriter->Update();
709  }
710  catch (itk::ExceptionObject& e)
711  {
712  LOG_ERROR("Failed to write intermediate image in line segmentation algorithm, check if the destination directory exists: " << rgbImageFilename.str() << "\n" << e);
713  return;
714  }
715 
716  LOG_DEBUG("Line segmentation intermediate image is saved to: " << rgbImageFilename.str());
717 }
718 
719 //----------------------------------------------------------------------------
721 {
723  {
724  return PLUS_FAIL;
725  }
727  {
728  return PLUS_FAIL;
729  }
730  return PLUS_SUCCESS;
731 }
732 
733 //-----------------------------------------------------------------------------
734 void vtkPlusLineSegmentationAlgo::PlotIntArray(const std::deque<int>& intensityValues)
735 {
736 #ifdef PLUS_RENDERING_ENABLED
737  // Create table
738  vtkSmartPointer<vtkTable> table = vtkSmartPointer<vtkTable>::New();
739 
740  // Create array corresponding to the time values of the tracker plot
741  vtkSmartPointer<vtkIntArray> arrPixelPositions = vtkSmartPointer<vtkIntArray>::New();
742  arrPixelPositions->SetName("Pixel Positions");
743  table->AddColumn(arrPixelPositions);
744 
745  // Create array corresponding to the metric values of the tracker plot
746  vtkSmartPointer<vtkIntArray> arrIntensityProfile = vtkSmartPointer<vtkIntArray>::New();
747  arrIntensityProfile->SetName("Intensity Profile");
748  table->AddColumn(arrIntensityProfile);
749 
750  // Set the tracker data
751  table->SetNumberOfRows(intensityValues.size());
752  for (unsigned int i = 0; i < intensityValues.size(); ++i)
753  {
754  table->SetValue(i, 0, i);
755  table->SetValue(i, 1, (intensityValues.at(i)));
756  }
757 
758  // Set up the view
759  vtkSmartPointer<vtkContextView> view = vtkSmartPointer<vtkContextView>::New();
760  view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
761 
762  // Add the two line plots
763  vtkSmartPointer<vtkChartXY> chart = vtkSmartPointer<vtkChartXY>::New();
764  view->GetScene()->AddItem(chart);
765  vtkPlot* line = chart->AddPlot(vtkChart::LINE);
766 
767  line->SetInputData(table, 0, 1);
768 
769  line->SetColor(0, 255, 0, 255);
770  line->SetWidth(1.0);
771  line = chart->AddPlot(vtkChart::LINE);
772 
773  // Start interactor
774  view->GetInteractor()->Initialize();
775  view->GetInteractor()->Start();
776 #else
777  LOG_ERROR("Function not available when VTK_RENDERING_BACKEND is None!");
778 #endif
779 }
780 
781 //-----------------------------------------------------------------------------
782 void vtkPlusLineSegmentationAlgo::PlotDoubleArray(const std::deque<double>& intensityValues)
783 {
784 #ifdef PLUS_RENDERING_ENABLED
785  // Create table
786  vtkSmartPointer<vtkTable> table = vtkSmartPointer<vtkTable>::New();
787 
788  // Create array corresponding to the time values of the tracker plot
789  vtkSmartPointer<vtkDoubleArray> arrPixelPositions = vtkSmartPointer<vtkDoubleArray>::New();
790  arrPixelPositions->SetName("Pixel Positions");
791  table->AddColumn(arrPixelPositions);
792 
793  // Create array corresponding to the metric values of the tracker plot
794  vtkSmartPointer<vtkDoubleArray> arrIntensityProfile = vtkSmartPointer<vtkDoubleArray>::New();
795  arrIntensityProfile->SetName("Intensity Profile");
796  table->AddColumn(arrIntensityProfile);
797 
798  // Set the tracker data
799  table->SetNumberOfRows(intensityValues.size());
800  for (unsigned int i = 0; i < intensityValues.size(); ++i)
801  {
802  table->SetValue(i, 0, i);
803  table->SetValue(i, 1, (intensityValues.at(i)));
804  }
805 
806  // Set up the view
807  vtkSmartPointer<vtkContextView> view = vtkSmartPointer<vtkContextView>::New();
808  view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
809 
810  // Add the two line plots
811  vtkSmartPointer<vtkChartXY> chart = vtkSmartPointer<vtkChartXY>::New();
812  view->GetScene()->AddItem(chart);
813  vtkPlot* line = chart->AddPlot(vtkChart::LINE);
814 
815  line->SetInputData(table, 0, 1);
816 
817  line->SetColor(0, 255, 0, 255);
818  line->SetWidth(1.0);
819  line = chart->AddPlot(vtkChart::LINE);
820 
821  // Start interactor
822  view->GetInteractor()->Initialize();
823  view->GetInteractor()->Start();
824 #else
825  LOG_ERROR("Function not available when VTK_RENDERING_BACKEND is None!");
826 #endif
827 }
828 
829 //-----------------------------------------------------------------------------
830 void vtkPlusLineSegmentationAlgo::GetDetectedTimestamps(std::deque<double>& timestamps)
831 {
832  timestamps = m_SignalTimestamps;
833 }
834 
835 //-----------------------------------------------------------------------------
836 void vtkPlusLineSegmentationAlgo::GetDetectedPositions(std::deque<double>& positions)
837 {
838  positions = m_SignalValues;
839 }
840 
841 //-----------------------------------------------------------------------------
842 void vtkPlusLineSegmentationAlgo::GetDetectedLineParameters(std::vector<LineParameters>& parameters)
843 {
844  parameters = m_LineParameters;
845 }
846 
847 //-----------------------------------------------------------------------------
848 void vtkPlusLineSegmentationAlgo::SetClipRectangle(int clipRectangleOriginPix[2], int clipRectangleSizePix[2])
849 {
850  m_ClipRectangleOrigin[0] = clipRectangleOriginPix[0];
851  m_ClipRectangleOrigin[1] = clipRectangleOriginPix[1];
852  m_ClipRectangleSize[0] = clipRectangleSizePix[0];
853  m_ClipRectangleSize[1] = clipRectangleSizePix[1];
854 }
855 
856 //----------------------------------------------------------------------------
857 void vtkPlusLineSegmentationAlgo::LimitToClipRegion(CharImageType::RegionType& region)
858 {
859  if ((m_ClipRectangleSize[0] <= 0) || (m_ClipRectangleSize[1] <= 0))
860  {
861  // no clipping
862  return;
863  }
864 
865  // Clipping enabled
866  CharImageType::IndexValueType clipRectangleOrigin[2] =
867  {
870  };
871  CharImageType::SizeValueType clipRectangleSize[2] =
872  {
875  };
876 
877  // Adjust clipping region origin and size to fit inside the frame region
878  CharImageType::IndexType imageOrigin = region.GetIndex();
879  CharImageType::SizeType imageSize = region.GetSize();
880  if (clipRectangleOrigin[0] < imageOrigin[0]
881  || clipRectangleOrigin[1] < imageOrigin[1]
882  || clipRectangleOrigin[0] >= imageOrigin[0] + static_cast<CharImageType::OffsetValueType>(imageSize[0])
883  || clipRectangleOrigin[1] >= imageOrigin[1] + static_cast<CharImageType::OffsetValueType>(imageSize[1]))
884  {
885  LOG_WARNING("ClipRectangleOrigin is invalid (" << clipRectangleOrigin[0] << ", " << clipRectangleOrigin[1] <<
886  "). The frame size is "
887  << imageSize[0] << "x" << imageSize[1] <<
888  ". Using ("
889  << imageOrigin[0] << "," << imageOrigin[1] <<
890  ") as ClipRectangleOrigin.");
891  clipRectangleOrigin[0] = 0;
892  clipRectangleOrigin[1] = 0;
893  }
894  if (clipRectangleOrigin[0] + clipRectangleSize[0] >= imageOrigin[0] + imageSize[0])
895  {
896  // rectangle size is out of the framSize bounds, clip it to the available size
897  clipRectangleSize[0] = imageOrigin[0] + imageSize[0] - clipRectangleOrigin[0];
898  LOG_WARNING("Adjusting ClipRectangleSize x to " << clipRectangleSize[0]);
899  }
900  if (clipRectangleOrigin[1] + clipRectangleSize[1] > imageSize[1])
901  {
902  // rectangle size is out of the framSize bounds, clip it to the available size
903  clipRectangleSize[1] = imageOrigin[1] + imageSize[1] - clipRectangleOrigin[1];
904  LOG_WARNING("Adjusting ClipRectangleSize y to " << clipRectangleSize[1]);
905  }
906 
907  if ((clipRectangleSize[0] <= 0) || (clipRectangleSize[1] <= 0))
908  {
909  // after the adjustment it seems that there is no clipping is needed
910  return;
911  }
912 
913  // Save updated clipping parameters to the region
914  imageOrigin[0] = clipRectangleOrigin[0];
915  imageOrigin[1] = clipRectangleOrigin[1];
916  imageSize[0] = clipRectangleSize[0];
917  imageSize[1] = clipRectangleSize[1];
918  region.SetIndex(imageOrigin);
919  region.SetSize(imageSize);
920 }
921 
922 //----------------------------------------------------------------------------
924 {
925  XML_FIND_NESTED_ELEMENT_REQUIRED(lineSegmentationElement, aConfig, "vtkPlusLineSegmentationAlgo");
926 
927  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(SaveIntermediateImages, lineSegmentationElement);
928  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(PlotIntensityProfile, lineSegmentationElement);
929 
931  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(IntermediateFilesOutputDirectory, lineSegmentationElement);
932 
933  int clipRectangleOrigin[2];
934  int clipRectangleSize[2];
935  if (!lineSegmentationElement->GetVectorAttribute("ClipRectangleOrigin", 2, clipRectangleOrigin) ||
936  !lineSegmentationElement->GetVectorAttribute("ClipRectangleSize", 2, clipRectangleSize))
937  {
938  LOG_WARNING("Cannot find ClipRectangleOrigin or ClipRectangleSize attribute in the vtkPlusLineSegmentationAlgo configuration file; Using the largest ROI possible.");
939  m_ClipRectangleOrigin[0] = 0;
940  m_ClipRectangleOrigin[1] = 0;
941  m_ClipRectangleSize[0] = -1;
942  m_ClipRectangleSize[1] = -1;
943  }
944  else
945  {
946  if (clipRectangleOrigin[0] < 0)
947  {
948  clipRectangleOrigin[0] = 0;
949  }
950  if (clipRectangleOrigin[1] < 0)
951  {
952  clipRectangleOrigin[1] = 0;
953  }
954  if (clipRectangleSize[0] < 0)
955  {
956  clipRectangleSize[0] = -1;
957  }
958  if (clipRectangleSize[1] < 0)
959  {
960  clipRectangleSize[1] = -1;
961  }
962 
963  m_ClipRectangleOrigin[0] = clipRectangleOrigin[0];
964  m_ClipRectangleOrigin[1] = clipRectangleOrigin[1];
965  m_ClipRectangleSize[0] = clipRectangleSize[0];
966  m_ClipRectangleSize[1] = clipRectangleSize[1];
967  }
968 
969  double signalTimeRange[2] = {0};
970  if (lineSegmentationElement->GetVectorAttribute("SignalTimeRange", 2, signalTimeRange))
971  {
972  m_SignalTimeRangeMin = signalTimeRange[0];
973  m_SignalTimeRangeMax = signalTimeRange[1];
974  }
975 
976  return PLUS_SUCCESS;
977 }
978 
979 //----------------------------------------------------------------------------
981 {
982  m_SignalValues.clear();
983  m_SignalTimestamps.clear();
984  m_LineParameters.clear();
985 
986  return PLUS_SUCCESS;
987 }
const uint32_t * data
Definition: phidget22.h:3971
CharImageType::SizeValueType m_ClipRectangleSize[2]
void SetClipRectangle(int clipRectangleOriginPix[2], int clipRectangleSizePix[2])
PlusStatus FindPeakStart(std::deque< int > &intensityProfile, int maxFromLargestArea, int startOfMaxArea, double &startOfPeak)
const char int line
Definition: phidget22.h:2458
void SaveIntermediateImage(int frameNumber, CharImageType::Pointer scanlineImage, double x_0, double y_0, double r_x, double r_y, int numOfValidScanlines, const std::vector< itk::Point< double, 2 > > &intensityPeakPositions)
vtkStandardNewMacro(vtkPlusLineSegmentationAlgo)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
igsioStatus PlusStatus
Definition: PlusCommon.h:40
void SetIntermediateFilesOutputDirectory(const std::string &outputDirectory)
PlusStatus ComputeCenterOfGravity(std::deque< int > &intensityProfile, int startOfMaxArea, double &centerOfGravity)
std::vector< LineParameters > m_LineParameters
for i
static const double MAX_CONSECUTIVE_INVALID_VIDEO_FRAMES
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
void GetDetectedPositions(std::deque< double > &positions)
static const double MIN_X_SLOPE_COMPONENT_FOR_DETECTED_LINE
void SetTrackedFrameList(vtkIGSIOTrackedFrameList &aTrackedFrameList)
static const double MAX_PERCENTAGE_OF_INVALID_VIDEO_FRAMES
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
static const unsigned int DIMENSION
std::string GetOutputDirectory()
static const double EXPECTED_LINE_SEGMENTATION_SUCCESS_RATE
PlusStatus ReadConfiguration(vtkXMLDataElement *aConfig)
vtkSmartPointer< vtkIGSIOTrackedFrameList > m_TrackedFrameList
const PEAK_POS_METRIC_TYPE PEAK_POS_METRIC
PlusStatus FindLargestPeak(std::deque< int > &intensityProfile, int &maxFromLargestArea, int &maxFromLargestAreaIndex, int &startOfMaxArea)
static const int NUMBER_OF_SCANLINES
const char * start
Definition: phidget22.h:5116
CharImageType::IndexValueType m_ClipRectangleOrigin[2]
int x
Definition: phidget22.h:4265
void SetSignalTimeRange(double rangeMin, double rangeMax)
void GetDetectedLineParameters(std::vector< LineParameters > &parameters)
static vtkIGSIOLogger * Instance()
void LimitToClipRegion(CharImageType::RegionType &region)
static const int MINIMUM_NUMBER_OF_VALID_SCANLINES
void ComputeLineParameters(std::vector< itk::Point< double, 2 > > &data, LineParameters &outputParameters)
void SetSaveIntermediateImages(bool saveIntermediateImages)
Direction vectors of rods y
Definition: algo3.m:15
void PlotDoubleArray(const std::deque< double > &intensityValues)
static const double INTESNITY_THRESHOLD_PERCENTAGE_OF_PEAK
for t
Definition: exploreFolders.m:9
void GetDetectedTimestamps(std::deque< double > &timestamps)
void PlotIntArray(const std::deque< int > &intensityValues)
void SetTrackedFrame(igsioTrackedFrame &aTrackedFrame)
Detect the position of a line (image of a plane) in an US image sequence.